@abtnode/blocklet-services 1.17.4-beta-20251201-225048-b1682a09 → 1.17.4-beta-20251202-122551-267b614d

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 (211) hide show
  1. package/api/routes/user.js +2 -0
  2. package/api/services/notification/queue.js +190 -102
  3. package/api/socket/channel/did.js +80 -34
  4. package/api/util/user-util.js +6 -1
  5. package/dist/assets/{AdapterDayjs-OZNn2tpO.js → AdapterDayjs-BuAhqT20.js} +1 -1
  6. package/dist/assets/{Google-PoHjNVzr.js → Google-Dp7qGnxg.js} +1 -1
  7. package/dist/assets/{access-control-BpDf5eCc.js → access-control-B9EJ5tQF.js} +1 -1
  8. package/dist/assets/{actions-CIp6hu8_.js → actions-DhSvOHy-.js} +1 -1
  9. package/dist/assets/{add-component-core-DZMleaf7.js → add-component-core-VVqL08Uq.js} +21 -21
  10. package/dist/assets/{add-resource-fReirUEj.js → add-resource-DPXu2LFD.js} +1 -1
  11. package/dist/assets/{addon-CD2tT3Vk.js → addon-DimuRKXu.js} +1 -1
  12. package/dist/assets/{advanced-D_HYmeDL.js → advanced-C-M8n1zk.js} +1 -1
  13. package/dist/assets/{aigne-DKzLdZPY.js → aigne-Blnb_Xmg.js} +1 -1
  14. package/dist/assets/{appearance-DoI_K1yx.js → appearance-DcbXeaDh.js} +1 -1
  15. package/dist/assets/ar-C89Z16Ql.js +7 -0
  16. package/dist/assets/{arrow-down.svg-mj2eIUoA.js → arrow-down.svg-CNh-MHKv.js} +1 -1
  17. package/dist/assets/{audit-logs-CBeb46aa.js → audit-logs-D0mYj6Ut.js} +1 -1
  18. package/dist/assets/{authorize-BQTDaD7S.js → authorize-CZdmLVec.js} +1 -1
  19. package/dist/assets/{base-chart-DvlyWvB2.js → base-chart-DJyGks5q.js} +1 -1
  20. package/dist/assets/{base32-AX-I0UaR.js → base32-CnxuWLTI.js} +1 -1
  21. package/dist/assets/{bind-account--wlCwoIh.js → bind-account-GnACtKqD.js} +1 -1
  22. package/dist/assets/{branding-Sd6X0YqE.js → branding-CFP8OuF1.js} +1 -1
  23. package/dist/assets/branding-CihykCDo.js +1 -0
  24. package/dist/assets/{branding-CMNxsyrp.js → branding-DmNLEN0a.js} +4 -4
  25. package/dist/assets/{bundle-avatar-DACPWlAj.js → bundle-avatar-D3mFZG-3.js} +1 -1
  26. package/dist/assets/button-pTTwNqRf.js +1 -0
  27. package/dist/assets/{click-to-copy-DZ_4RDjR.js → click-to-copy-CAd3nEo4.js} +1 -1
  28. package/dist/assets/{collapse-CHg4yEHL.js → collapse-CzqKk-Uw.js} +1 -1
  29. package/dist/assets/{complete-BIrSCTBJ.js → complete-BDYaBiHT.js} +1 -1
  30. package/dist/assets/{component-BrH7uZv7.js → component-D_iXTtDp.js} +2 -2
  31. package/dist/assets/{config-BbbJtsEK.js → config--X9TgCMx.js} +1 -1
  32. package/dist/assets/{config-C0IukdMa.js → config-C_UzRk_x.js} +3 -3
  33. package/dist/assets/{config-navigation-CPqSm2b8.js → config-navigation-zOEBEK_N.js} +2 -2
  34. package/dist/assets/{config-space-B6ikfnoY.js → config-space-D1vOxWrR.js} +1 -1
  35. package/dist/assets/{config-BlvlFPnu.js → config-xLf7B5vm.js} +1 -1
  36. package/dist/assets/{confirm-Dge1BR0a.js → confirm-CC1M9sYT.js} +1 -1
  37. package/dist/assets/{connect-DwftqrTP.js → connect-4mXUuWwC.js} +1 -1
  38. package/dist/assets/{connect-DwfU2nBg.js → connect-BpoYmTzp.js} +1 -1
  39. package/dist/assets/{connect-to-D3Fxg-mH.js → connect-to-YDMOcSyq.js} +1 -1
  40. package/dist/assets/{content-layout-RghQFMLL.js → content-layout-B-2tfq3i.js} +1 -1
  41. package/dist/assets/{create-passport-svg-BZVXsAoP.js → create-passport-svg-BLBQxsEK.js} +1 -1
  42. package/dist/assets/{createClass-dlbhjEVd.js → createClass-mqcDPFI4.js} +1 -1
  43. package/dist/assets/{dashboard-BZMyxtap.js → dashboard-DeaTlS85.js} +2 -2
  44. package/dist/assets/de-BsCDrfBl.js +7 -0
  45. package/dist/assets/{delete-confirm-CaK42Rqi.js → delete-confirm-DF03c_mf.js} +1 -1
  46. package/dist/assets/{did-address-CihvGtuj.js → did-address-BOsJWqi1.js} +1 -1
  47. package/dist/assets/{domain-DZhOKUFR.js → domain-DdkBc4bt.js} +1 -1
  48. package/dist/assets/{domain-action-card-KBcfeUDt.js → domain-action-card-B8tM4_TO.js} +1 -1
  49. package/dist/assets/domains-CdpvlDLA.js +1 -0
  50. package/dist/assets/{email-mM-fSS-l.js → email-DOKW1a_7.js} +1 -1
  51. package/dist/assets/{empty-spinner-2ylB84wv.js → empty-spinner-G6osPzkH.js} +1 -1
  52. package/dist/assets/engine-BsqbrwXE.js +1 -0
  53. package/dist/assets/es-BJh_XsEm.js +9 -0
  54. package/dist/assets/{exchange-passport-CL0-QMoV.js → exchange-passport-Dnbu3e66.js} +1 -1
  55. package/dist/assets/{form-BQvsWiOt.js → form-mmswDYpL.js} +1 -1
  56. package/dist/assets/form-text-input-BFJU5Psq.js +11 -0
  57. package/dist/assets/{form-wrapper-CqFSijKV.js → form-wrapper-CU8kJXy5.js} +1 -1
  58. package/dist/assets/fr-DRJMwn7V.js +7 -0
  59. package/dist/assets/{fuel-BpMa6xEp.js → fuel-CpCUHgkW.js} +1 -1
  60. package/dist/assets/{gen-access-key-BLtBtR5W.js → gen-access-key-OHc0npoi.js} +1 -1
  61. package/dist/assets/{gen-simple-access-key-9tFWfYxA.js → gen-simple-access-key-C6tIufX7.js} +1 -1
  62. package/dist/assets/get-safe-url-DUB5fSqh.js +1 -0
  63. package/dist/assets/hi-h-9Yl5g9.js +5 -0
  64. package/dist/assets/{home-BG4Bn0-p.js → home-BFI-HNBw.js} +1 -1
  65. package/dist/assets/id-BDWggRsa.js +7 -0
  66. package/dist/assets/{iframe-BjsS1KQR.js → iframe-DB0OcQ6i.js} +1 -1
  67. package/dist/assets/{index-B00Da_zr.js → index-3hHmW6B5.js} +1 -1
  68. package/dist/assets/{index-DsQDfClt.js → index-7uZYu-Ep.js} +1 -1
  69. package/dist/assets/{index-bbu7QFpz.js → index-BIJNkmLb.js} +1 -1
  70. package/dist/assets/{index-sfOXn4Gu.js → index-BMkJtBkv.js} +2 -2
  71. package/dist/assets/{index-C40tsw4I.js → index-Bj8J6jnM.js} +1 -1
  72. package/dist/assets/{index-D6MFQa2J.js → index-Bksy_mMO.js} +1 -1
  73. package/dist/assets/{index-B8mnhcds.js → index-Bns8G6yV.js} +2 -2
  74. package/dist/assets/{index-DEDj0oVc.js → index-BphmxdkH.js} +1 -1
  75. package/dist/assets/index-BsP0-mXD.js +124 -0
  76. package/dist/assets/{index-B8uWH9ks.js → index-BuIBrsUM.js} +1 -1
  77. package/dist/assets/index-Bw9XS3Zt.js +7 -0
  78. package/dist/assets/{index-CxJOg5jI.js → index-C3BK6ln_.js} +1 -1
  79. package/dist/assets/{index-B9Igf2rm.js → index-C3cD5fjJ.js} +1 -1
  80. package/dist/assets/{index-h0Os_ox8.js → index-CDE0c6ul.js} +1 -1
  81. package/dist/assets/{index-BmuOS9l2.js → index-CDFYQ7q2.js} +1 -1
  82. package/dist/assets/{index-5hr4oNRS.js → index-CEyMJtzZ.js} +3 -3
  83. package/dist/assets/{index-KUb_kVWp.js → index-CHbWK92_.js} +2 -2
  84. package/dist/assets/{index-BF7MtybM.js → index-Ca4Ccmqm.js} +1 -1
  85. package/dist/assets/{index-lFDZOk_k.js → index-CeWTwSLE.js} +66 -66
  86. package/dist/assets/{index-B4Q4y_7n.js → index-CtouYMiN.js} +1 -1
  87. package/dist/assets/{index-D9UpcFKi.js → index-D0pDhWRx.js} +1 -1
  88. package/dist/assets/{index-BVYUGxzR.js → index-D8w8SYuc.js} +1 -1
  89. package/dist/assets/{index-Cy73v3o9.js → index-DUFdTykg.js} +1 -1
  90. package/dist/assets/{index-BIlfbBZ2.js → index-DWuLUiY3.js} +1 -1
  91. package/dist/assets/index-Duwa2sz8.js +1 -0
  92. package/dist/assets/{index-CO3X6G_g.js → index-Dx6HKVtJ.js} +1 -1
  93. package/dist/assets/{index-yGC4DbEU.js → index-Dy3yCmIK.js} +1 -1
  94. package/dist/assets/{index-BUomsang.js → index-V1TR40-z.js} +1 -1
  95. package/dist/assets/{index-BoeLorfn.js → index-WCk612vr.js} +1 -1
  96. package/dist/assets/{index-BzCDxVOL.js → index-Xg-It3qI.js} +1 -1
  97. package/dist/assets/{index-CgTWMV4T.js → index-ohq48KFN.js} +1 -1
  98. package/dist/assets/{index-V83VNrab.js → index-wS_uPsSR.js} +1 -1
  99. package/dist/assets/{invitation-LJOuA09f.js → invitation-BqyhoSaQ.js} +1 -1
  100. package/dist/assets/{invitations-DtmYY9Tr.js → invitations-Dmx5LkD1.js} +1 -1
  101. package/dist/assets/{invite-DrIYCYzj.js → invite-E-ZdmNWy.js} +1 -1
  102. package/dist/assets/{isURL-fcz3vuMN.js → isURL-HS60va5G.js} +1 -1
  103. package/dist/assets/{issue-passport-Dv9gH2LH.js → issue-passport-DhA2Aw2n.js} +1 -1
  104. package/dist/assets/{item-weuCOdTL.js → item-B8ah25gH.js} +1 -1
  105. package/dist/assets/ja-R9EF3xbB.js +9 -0
  106. package/dist/assets/ko-C_QLPZyk.js +9 -0
  107. package/dist/assets/{landing-page-Djj9jDve.js → landing-page-DQBBERn_.js} +1 -1
  108. package/dist/assets/{launch-result-message-AcBN2VCj.js → launch-result-message-CO-R7GIB.js} +1 -1
  109. package/dist/assets/{layout-0zU7a-3v.js → layout-DUsIwJhf.js} +1 -1
  110. package/dist/assets/{list-DXe9E4ws.js → list-C6OYD0M-.js} +3 -3
  111. package/dist/assets/{list-Co62gn7d.js → list-CLMUEykk.js} +1 -1
  112. package/dist/assets/{list-header-CA-QkwGq.js → list-header-Cv_gaaAc.js} +1 -1
  113. package/dist/assets/localization-CutEJG9u.js +1 -0
  114. package/dist/assets/{log-BCC7blv9.js → log-CaIfb_mj.js} +1 -1
  115. package/dist/assets/logger-CjpdOowp.js +1 -0
  116. package/dist/assets/{login-DlWL3WVr.js → login-N9lwWO_e.js} +1 -1
  117. package/dist/assets/{login-oauth-callback-CQFS2s22.js → login-oauth-callback-U2rdasmV.js} +1 -1
  118. package/dist/assets/{logo-uploader-Dz85eY-W.js → logo-uploader-BgXq5jHQ.js} +1 -1
  119. package/dist/assets/{lost-passport-pRlBbp2M.js → lost-passport-CPwExoAp.js} +1 -1
  120. package/dist/assets/{observability-Bzq6nAvX.js → observability-SEfRhcXj.js} +1 -1
  121. package/dist/assets/{open-window-X4W6wF0y.js → open-window-BPgl9r9H.js} +1 -1
  122. package/dist/assets/{over-due-invoice-payment-CvKa0suc.js → over-due-invoice-payment-Hi-FNl9O.js} +1 -1
  123. package/dist/assets/{overview-d6xffU9N.js → overview-DPJo9CyG.js} +2 -2
  124. package/dist/assets/{page-header-B4MDj6gy.js → page-header-BkgoSN-0.js} +1 -1
  125. package/dist/assets/{passport-DHiNUeVn.js → passport-DMZiwbhL.js} +1 -1
  126. package/dist/assets/{passport-item-BkmVlAdr.js → passport-item-yqBy6NcY.js} +1 -1
  127. package/dist/assets/{permission-B3_OQqtH.js → permission-CiiVpApl.js} +1 -1
  128. package/dist/assets/{playground-C71u8_tm.js → playground-Bo63Spph.js} +1 -1
  129. package/dist/assets/preferences-YgFBqLbX.js +1 -0
  130. package/dist/assets/profile-embed-C35dSucs.js +1 -0
  131. package/dist/assets/pt-DpYO8djd.js +5 -0
  132. package/dist/assets/{publish-resource-Aoo5xCZX.js → publish-resource-DUDgKqzf.js} +1 -1
  133. package/dist/assets/{react-beautiful-dnd.esm--C1KYPc1.js → react-beautiful-dnd.esm-Clor-N66.js} +1 -1
  134. package/dist/assets/{react-stripe.esm-DlJMzZK2.js → react-stripe.esm-C-04j7I2.js} +1 -1
  135. package/dist/assets/{required-glXJjYm5.js → required-Btg6mEmX.js} +1 -1
  136. package/dist/assets/ru-DkZtar3s.js +5 -0
  137. package/dist/assets/{runtime-xK-SJ1cq.js → runtime-B_yu4wL5.js} +1 -1
  138. package/dist/assets/sdk-D9qYMy_G.js +1 -0
  139. package/dist/assets/{section-4gVAq_Fp.js → section-Cyr0YvGa.js} +1 -1
  140. package/dist/assets/{security-B3q0KDVH.js → security-YhrYNktK.js} +1 -1
  141. package/dist/assets/{session-DR7jqIW5.js → session-CJQ7HeL8.js} +1 -1
  142. package/dist/assets/{setup-CPrn9v2_.js → setup-DVxC6L1D.js} +1 -1
  143. package/dist/assets/{shorten-label-Cf--RqV-.js → shorten-label-D6jbe4l0.js} +1 -1
  144. package/dist/assets/{simple-select-SyYgbCc2.js → simple-select-BW6-FLlf.js} +1 -1
  145. package/dist/assets/{spaces-C_pXJSzm.js → spaces-DB-xtAK9.js} +1 -1
  146. package/dist/assets/{start-BUxgx8Ah.js → start-CnOduVio.js} +1 -1
  147. package/dist/assets/{starting-progress-BP_Sqg1X.js → starting-progress-Qp2T3eDZ.js} +1 -1
  148. package/dist/assets/{status-CE6iXPs_.js → status-B2iiuI6t.js} +1 -1
  149. package/dist/assets/{step-actions-tRQyAl6A.js → step-actions-D7ku0W0o.js} +1 -1
  150. package/dist/assets/{studio-bbXFMut7.js → studio-DtnYEBjZ.js} +1 -1
  151. package/dist/assets/switch-CjOUxynL.js +1 -0
  152. package/dist/assets/{switch-control-DdOZdvsu.js → switch-control-BFsTsyuN.js} +1 -1
  153. package/dist/assets/{table-tips-cVuoMOXE.js → table-tips-EHWVzPWe.js} +1 -1
  154. package/dist/assets/{team-KWOyNaWn.js → team-CKeHAIQC.js} +1 -1
  155. package/dist/assets/th-04iX5CrT.js +5 -0
  156. package/dist/assets/{traffic-ChddnVTm.js → traffic-NcU-KhYi.js} +1 -1
  157. package/dist/assets/{transfer-CcCi6t4X.js → transfer-CB2s8PFZ.js} +1 -1
  158. package/dist/assets/{unsubscribe-Bp5VQCnR.js → unsubscribe-a8AJuSFd.js} +1 -1
  159. package/dist/assets/{use-app-logo-hGlkw2d_.js → use-app-logo-D38VoM1R.js} +1 -1
  160. package/dist/assets/{use-mobile-BpPz1bKz.js → use-mobile-C5RjvO7g.js} +1 -1
  161. package/dist/assets/{use-mobile-DoKVMODs.js → use-mobile-ajdJIsoX.js} +1 -1
  162. package/dist/assets/{use-server-logo-UGU40YsW.js → use-server-logo-D4Yh4atf.js} +1 -1
  163. package/dist/assets/{use-window-close-CbUhh9L6.js → use-window-close-Bo92fPIm.js} +1 -1
  164. package/dist/assets/{useAsyncRetry-DHTJ61Jg.js → useAsyncRetry-a2Wewn3F.js} +1 -1
  165. package/dist/assets/{useLocalStorage-UFpv-3wa.js → useLocalStorage-BBhMedc2.js} +1 -1
  166. package/dist/assets/{user-center-CXduwafJ.js → user-center-BNMJJTvG.js} +2 -2
  167. package/dist/assets/{user-follower-CmGz7Inx.js → user-follower-BXm4RUI2.js} +1 -1
  168. package/dist/assets/{util-MbDHkO8z.js → util-DPC-rfKa.js} +1 -1
  169. package/dist/assets/{util-D-cKMKUM.js → util-MwdZAHoO.js} +1 -1
  170. package/dist/assets/{vendor-arcblock-3xKfH3F1.js → vendor-arcblock-BL_VyzmH.js} +1 -1
  171. package/dist/assets/{vendor-mui-core-DHT36AZL.js → vendor-mui-core-BPQ6AEW_.js} +2 -2
  172. package/dist/assets/{vendor-mui-x-B4OOX5Es.js → vendor-mui-x-CWNb8fsW.js} +1 -1
  173. package/dist/assets/{vendor-ux-sqP3hEhY.js → vendor-ux-D507n6h6.js} +1 -1
  174. package/dist/assets/vi-kOP0E9Ga.js +5 -0
  175. package/dist/assets/{wait-connect-DDyEqw4h.js → wait-connect-Of13xq9u.js} +1 -1
  176. package/dist/assets/{wizard-B588KidT.js → wizard-BXTkkerl.js} +1 -1
  177. package/dist/assets/{wizard-components-DP7Z_TTm.js → wizard-components-BZ6jYzgt.js} +1 -1
  178. package/dist/assets/wrap-locale-CPp4ppiN.js +1 -0
  179. package/dist/assets/{zh-C_L8CY4f.js → zh-8l1HXCDx.js} +1 -1
  180. package/dist/assets/{zh-98nxdLeR.js → zh-DZI4Yy2m.js} +3 -3
  181. package/dist/assets/{zh-tw-BdDE7n5E.js → zh-tw-BLapUUeq.js} +2 -2
  182. package/dist/index.html +1 -1
  183. package/dist/service-worker.js +1 -1
  184. package/package.json +23 -23
  185. package/dist/assets/ar-BJZY7v5z.js +0 -7
  186. package/dist/assets/branding-Bs3FNZ79.js +0 -1
  187. package/dist/assets/button-8Nchte_6.js +0 -1
  188. package/dist/assets/de-BF0NHTXz.js +0 -7
  189. package/dist/assets/domains-Dr63r27w.js +0 -1
  190. package/dist/assets/engine-BCYKeHn7.js +0 -1
  191. package/dist/assets/es-Dv2m4LXp.js +0 -9
  192. package/dist/assets/form-text-input-CBL3JAev.js +0 -11
  193. package/dist/assets/fr-BGhg9oO3.js +0 -7
  194. package/dist/assets/get-safe-url-CxrqaBih.js +0 -1
  195. package/dist/assets/hi-CPfDhW8g.js +0 -5
  196. package/dist/assets/id-9TUzKF6k.js +0 -7
  197. package/dist/assets/index-88gdbkao.js +0 -1
  198. package/dist/assets/index-B6-5SXSy.js +0 -7
  199. package/dist/assets/index-CNE3uN5c.js +0 -124
  200. package/dist/assets/ja-DT5bRsBF.js +0 -9
  201. package/dist/assets/ko-CJn9PyPz.js +0 -9
  202. package/dist/assets/localization-CufkU3et.js +0 -1
  203. package/dist/assets/logger-DQUu6ucJ.js +0 -1
  204. package/dist/assets/preferences-D321ZAU1.js +0 -1
  205. package/dist/assets/profile-embed-Ba3F6nXm.js +0 -1
  206. package/dist/assets/pt-K9FK6Ube.js +0 -5
  207. package/dist/assets/ru-C_RwukUu.js +0 -5
  208. package/dist/assets/sdk-DAuiCSLy.js +0 -1
  209. package/dist/assets/th-Bhii_FfA.js +0 -5
  210. package/dist/assets/vi-DVF2tjOG.js +0 -5
  211. package/dist/assets/wrap-locale-CFMie_Pc.js +0 -1
@@ -661,6 +661,8 @@ const notificationConfigSetSchema = Joi.object({
661
661
  Joi.object({
662
662
  url: Joi.string().uri(),
663
663
  type: Joi.string().valid('slack', 'api'),
664
+ enabled: Joi.boolean().optional().default(true),
665
+ consecutiveFailures: Joi.number().optional().default(0),
664
666
  })
665
667
  )
666
668
  .optional(),
@@ -17,9 +17,11 @@ const {
17
17
  NOTIFICATION_SEND_STATUS,
18
18
  NOTIFICATION_SEND_FAILED_REASON,
19
19
  } = require('@abtnode/constant');
20
+ const sleep = require('@abtnode/util/lib/sleep');
20
21
  const { nanoid } = require('@blocklet/meta/lib/util');
21
22
  const get = require('lodash/get');
22
23
  const uniqBy = require('lodash/uniqBy');
24
+ const { getQueueConcurrencyByMem } = require('@abtnode/core/lib/util');
23
25
  const { getBlockletInfo } = require('../../cache');
24
26
  const { updateNotificationSendStatus } = require('../../socket/channel/did');
25
27
  const eventHub =
@@ -29,6 +31,8 @@ const logger = require('../../libs/logger')('blocklet-services:notification-queu
29
31
 
30
32
  const emailSchema = Joi.string().email().required();
31
33
 
34
+ const concurrency = getQueueConcurrencyByMem();
35
+
32
36
  /**
33
37
  *
34
38
  * 校验是否是有效的 passthrough 消息
@@ -58,10 +62,14 @@ const createNotificationQueue = (name, options, handler) => {
58
62
  maxRetries: 3,
59
63
  retryDelay: 10 * 1000,
60
64
  maxTimeout: 60 * 1000, // throw timeout error after 1 minutes
65
+ concurrency,
61
66
  ...(options ?? {}),
62
67
  },
63
68
  onJob: async (job) => {
64
69
  await handler(job);
70
+ if (options.delay) {
71
+ await sleep(options.delay * 1000);
72
+ }
65
73
  },
66
74
  });
67
75
  };
@@ -73,7 +81,15 @@ const init = ({ node, notificationService }) => {
73
81
  const getServerWebhooks = async () => {
74
82
  const webhookList = (await webhookState.list()) ?? [];
75
83
  return webhookList.flatMap((item) =>
76
- item.params.filter((param) => param.name === 'url').map((param) => ({ type: item.type, url: param.value }))
84
+ item.params
85
+ .filter((param) => param.name === 'url' && param.value)
86
+ .map((param) => ({
87
+ id: item.id,
88
+ type: item.type,
89
+ url: param.value,
90
+ enabled: param.enabled ?? true,
91
+ consecutiveFailures: param.consecutiveFailures || 0,
92
+ }))
77
93
  );
78
94
  };
79
95
 
@@ -85,6 +101,7 @@ const init = ({ node, notificationService }) => {
85
101
  {
86
102
  maxRetries: 1,
87
103
  retryDelay: 0,
104
+ enableScheduledJob: true,
88
105
  },
89
106
  async (job) => {
90
107
  try {
@@ -104,6 +121,7 @@ const init = ({ node, notificationService }) => {
104
121
  });
105
122
  } catch (error) {
106
123
  logger.error('Failed to send to app', { notificationId: job.notification.id, receiver: job.receiver, error });
124
+ throw error;
107
125
  }
108
126
  }
109
127
  );
@@ -111,28 +129,42 @@ const init = ({ node, notificationService }) => {
111
129
  /**
112
130
  * Push Kit 推送队列
113
131
  */
114
- const pushKitPushQueue = createNotificationQueue('send-notification-push', {}, async (job) => {
115
- try {
116
- const { notification, receiver, sender } = job;
132
+ const pushKitPushQueue = createNotificationQueue(
133
+ 'send-notification-push',
134
+ {
135
+ enableScheduledJob: true,
136
+ },
137
+ async (job) => {
138
+ try {
139
+ const { notification, receiver, sender } = job;
117
140
 
118
- if (!receiver) {
119
- throw new Error('Invalid receiver');
120
- }
141
+ if (!receiver) {
142
+ throw new Error('Invalid receiver');
143
+ }
121
144
 
122
- await notificationService.sendToPush.exec({
123
- sender,
124
- receiver,
125
- notification,
126
- pushOnly: job.pushOnly,
127
- });
128
- } catch (error) {
129
- if (error.logLevel === 'debug') {
130
- logger.debug('Failed to send to push', { notificationId: job.notification.id, receiver: job.receiver, error });
131
- } else {
132
- logger.error('Failed to send to push', { notificationId: job.notification.id, receiver: job.receiver, error });
145
+ await notificationService.sendToPush.exec({
146
+ sender,
147
+ receiver,
148
+ notification,
149
+ pushOnly: job.pushOnly,
150
+ });
151
+ } catch (error) {
152
+ if (error.logLevel === 'debug') {
153
+ logger.debug('Failed to send to push', {
154
+ notificationId: job.notification.id,
155
+ receiver: job.receiver,
156
+ error,
157
+ });
158
+ } else {
159
+ logger.error('Failed to send to push', {
160
+ notificationId: job.notification.id,
161
+ receiver: job.receiver,
162
+ error,
163
+ });
164
+ }
133
165
  }
134
166
  }
135
- });
167
+ );
136
168
 
137
169
  /**
138
170
  * email 推送队列
@@ -184,7 +216,6 @@ const init = ({ node, notificationService }) => {
184
216
  : null;
185
217
 
186
218
  logger.info('Start send to email', {
187
- email,
188
219
  notificationId: job.notificationId,
189
220
  });
190
221
  await notificationService.sendToMail.exec({
@@ -206,7 +237,6 @@ const init = ({ node, notificationService }) => {
206
237
  options,
207
238
  });
208
239
  logger.info('End send to email', {
209
- email,
210
240
  notificationId: job.notificationId,
211
241
  });
212
242
  } catch (error) {
@@ -225,6 +255,7 @@ const init = ({ node, notificationService }) => {
225
255
  error,
226
256
  });
227
257
  }
258
+ throw error;
228
259
  }
229
260
  }
230
261
  );
@@ -285,6 +316,7 @@ const init = ({ node, notificationService }) => {
285
316
  receivers: job.input.receivers.join(','),
286
317
  error,
287
318
  });
319
+ throw error;
288
320
  }
289
321
  }
290
322
  );
@@ -381,7 +413,7 @@ const init = ({ node, notificationService }) => {
381
413
  options,
382
414
  },
383
415
  },
384
- delay: 5,
416
+ delay: 8,
385
417
  });
386
418
  } else {
387
419
  // eslint-disable-next-line no-lonely-if
@@ -403,8 +435,40 @@ const init = ({ node, notificationService }) => {
403
435
  }
404
436
  };
405
437
 
438
+ /**
439
+ * 批量更新 webhook 发送失败状态(并行处理)
440
+ * @param {Array} webhooks - webhook 列表
441
+ * @param {string} failedReason - 失败原因
442
+ * @param {object} params - 公共参数
443
+ * @returns {Promise<void>}
444
+ */
445
+ const batchUpdateWebhookFailedStatus = (webhooks, failedReason, params) => {
446
+ const updatePromises = webhooks.map((webhook) => {
447
+ const { url, type } = webhook;
448
+ const webhookParams = {
449
+ [url]: {
450
+ type,
451
+ sendAt: new Date(),
452
+ status: NOTIFICATION_SEND_STATUS.FAILED,
453
+ failedReason,
454
+ },
455
+ };
456
+
457
+ return updateNotificationSendStatus({
458
+ ...params,
459
+ channel: NOTIFICATION_SEND_CHANNEL.WEBHOOK,
460
+ status: NOTIFICATION_SEND_STATUS.FAILED,
461
+ webhookParams,
462
+ }).catch((err) => {
463
+ logger.debug('update webhook failed status error', { err, url });
464
+ });
465
+ });
466
+
467
+ return Promise.all(updatePromises);
468
+ };
469
+
406
470
  const insertToWebhookPushQueue = async (props, nodeInfo, isResend) => {
407
- const { channels, notification, sender, userInfo, teamDid } = props;
471
+ const { channels, notification, sender, userInfo, teamDid, pushOnly } = props;
408
472
 
409
473
  const receiverDid = userInfo.did;
410
474
 
@@ -412,6 +476,7 @@ const init = ({ node, notificationService }) => {
412
476
  node,
413
477
  teamDid: teamDid ?? nodeInfo.did,
414
478
  notificationId: notification.id,
479
+ receivers: [receiverDid],
415
480
  };
416
481
 
417
482
  const isServer = teamDid === nodeInfo.did;
@@ -423,20 +488,34 @@ const init = ({ node, notificationService }) => {
423
488
 
424
489
  const webhookList = uniqBy(webhooks, 'url');
425
490
 
426
- // 如果类型不一致,则不进行推送
491
+ // 分类 webhook:有效、禁用、类型不匹配
427
492
  const validWebhookList = [];
493
+ const invalidWebhooks = []; // { webhook, reason }
494
+
428
495
  for (const webhook of webhookList) {
429
- const { url, type } = webhook;
430
- if (type === 'api' && isUrl(url)) {
496
+ const { url, type, enabled = true } = webhook;
497
+
498
+ // 检查是否禁用
499
+ if (!enabled) {
500
+ invalidWebhooks.push({ webhook, reason: 'Current webhook is disabled' });
501
+ } else if (type === 'api' && isUrl(url)) {
502
+ // api 类型且 URL 有效
431
503
  validWebhookList.push(webhook);
432
504
  } else if (type === 'slack' && isUrl(url) && isSlackWebhookUrl(url)) {
505
+ // slack 类型且 URL 有效
433
506
  validWebhookList.push(webhook);
507
+ } else {
508
+ // 类型不支持或 URL 无效
509
+ invalidWebhooks.push({ webhook, reason: 'Webhook type is not supported or URL is invalid' });
434
510
  }
435
511
  }
436
512
 
437
- if (validWebhookList.length > 0 && channels.includes(NOTIFICATION_SEND_CHANNEL.WEBHOOK)) {
438
- for (const webhook of webhookList) {
439
- const { url, type } = webhook;
513
+ const isWebhookChannelEnabled = channels.includes(NOTIFICATION_SEND_CHANNEL.WEBHOOK);
514
+
515
+ // 如果有有效的 webhook channel 启用,推送到队列
516
+ if (validWebhookList.length > 0 && isWebhookChannelEnabled) {
517
+ for (const webhook of validWebhookList) {
518
+ const { url } = webhook;
440
519
  logger.info('Insert to webhook push queue', {
441
520
  notificationId: notification.id,
442
521
  receiver: receiverDid,
@@ -452,45 +531,50 @@ const init = ({ node, notificationService }) => {
452
531
  receivers: [receiverDid],
453
532
  isResend,
454
533
  pushOnly: props.pushOnly && !isResend,
455
- webhook: {
456
- url,
457
- type,
458
- },
534
+ webhook,
459
535
  },
460
536
  },
461
537
  delay: 5,
462
538
  });
463
539
  }
464
- } else {
465
- // eslint-disable-next-line no-lonely-if
466
- if (webhookList.length > 0 && !isResend) {
467
- const webhookParams = {};
540
+ }
468
541
 
469
- let reason = '';
470
- if (validWebhookList.length > 0) {
471
- reason = NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED;
472
- } else {
473
- reason = 'Webhook type is not supported';
542
+ // 如果不是重发,需要更新失败状态(并行处理)
543
+ if (!isResend && !pushOnly) {
544
+ const updatePromises = [];
545
+
546
+ // 处理无效的 webhook(按失败原因分组并行更新)
547
+ if (invalidWebhooks.length > 0) {
548
+ // 按失败原因分组
549
+ const groupedByReason = {};
550
+ for (const { webhook, reason } of invalidWebhooks) {
551
+ if (!groupedByReason[reason]) {
552
+ groupedByReason[reason] = [];
553
+ }
554
+ groupedByReason[reason].push(webhook);
474
555
  }
475
556
 
476
- for (const { url, type } of webhookList) {
477
- webhookParams[url] = {
478
- type,
479
- sendAt: new Date(),
480
- status: NOTIFICATION_SEND_STATUS.FAILED,
481
- failedReason: reason,
482
- };
483
-
484
- // eslint-disable-next-line no-await-in-loop
485
- await updateNotificationSendStatus({
486
- ...commonParams,
487
- receivers: [receiverDid],
488
- channel: NOTIFICATION_SEND_CHANNEL.WEBHOOK,
489
- status: NOTIFICATION_SEND_STATUS.FAILED,
490
- webhookParams: { [url]: webhookParams[url] },
491
- });
557
+ // 每组并行更新
558
+ for (const [reason, webhookGroup] of Object.entries(groupedByReason)) {
559
+ updatePromises.push(batchUpdateWebhookFailedStatus(webhookGroup, reason, commonParams));
492
560
  }
493
561
  }
562
+
563
+ // 处理有效但 channel 禁用的 webhook
564
+ if (validWebhookList.length > 0 && !isWebhookChannelEnabled) {
565
+ updatePromises.push(
566
+ batchUpdateWebhookFailedStatus(
567
+ validWebhookList,
568
+ NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
569
+ commonParams
570
+ )
571
+ );
572
+ }
573
+
574
+ // 等待所有更新完成(不阻塞主流程)
575
+ Promise.all(updatePromises).catch((err) => {
576
+ logger.error('batch update webhook failed status error', { err });
577
+ });
494
578
  }
495
579
  };
496
580
 
@@ -516,12 +600,15 @@ const init = ({ node, notificationService }) => {
516
600
  receiver: receiverDid,
517
601
  });
518
602
  pushKitPushQueue.push({
519
- notification,
520
- sender,
521
- options,
522
- receiver: receiverDid,
523
- isResend,
524
- pushOnly: props.pushOnly && !isResend,
603
+ job: {
604
+ notification,
605
+ sender,
606
+ options,
607
+ receiver: receiverDid,
608
+ isResend,
609
+ pushOnly: props.pushOnly && !isResend,
610
+ },
611
+ delay: 10,
525
612
  });
526
613
  } else {
527
614
  // eslint-disable-next-line no-lonely-if
@@ -557,13 +644,16 @@ const init = ({ node, notificationService }) => {
557
644
  receiver: receiverDid,
558
645
  });
559
646
  walletPushQueue.push({
560
- notification,
561
- sender,
562
- options,
563
- receiver: receiverDid,
564
- isResend,
565
- pushOnly: props.pushOnly && !isResend,
566
- source,
647
+ job: {
648
+ notification,
649
+ sender,
650
+ options,
651
+ receiver: receiverDid,
652
+ isResend,
653
+ pushOnly: props.pushOnly && !isResend,
654
+ source,
655
+ },
656
+ delay: 5,
567
657
  });
568
658
  } else {
569
659
  // 如果是重发的消息,只需要更新推送状态,不需要更新 channel状态
@@ -628,7 +718,7 @@ const init = ({ node, notificationService }) => {
628
718
  * }
629
719
  */
630
720
  const queue = createNotificationQueue('notification-receivers', {}, async (job) => {
631
- const { teamDid, channels, receiver, sender, notification, nodeInfo, userInfo, ...rest } = job;
721
+ const { teamDid, channels, receiver, sender, notification, nodeInfo, ...rest } = job;
632
722
 
633
723
  logger.info('notification start insert to queue', {
634
724
  teamDid,
@@ -636,6 +726,17 @@ const init = ({ node, notificationService }) => {
636
726
  receiver,
637
727
  });
638
728
  try {
729
+ const selection = {
730
+ did: 1,
731
+ fullName: 1,
732
+ email: 1,
733
+ extra: 1,
734
+ };
735
+ const userInfo = await node.getUser({
736
+ teamDid,
737
+ user: { did: receiver },
738
+ options: { enableConnectedAccount: true, selection, includePassports: false, includeConnectedAccounts: false },
739
+ });
639
740
  if (!userInfo) {
640
741
  throw new Error(`Invalid receiver user: ${receiver}`);
641
742
  }
@@ -654,26 +755,22 @@ const init = ({ node, notificationService }) => {
654
755
  });
655
756
  // websocket 通知
656
757
  const receiverDid = userInfo.did;
657
- try {
658
- websocketQueue.push({
659
- input: {
660
- notification: {
661
- ...notification,
662
- entityType: rest.entityType,
663
- entityId: rest.entityId,
664
- componentDid: rest.componentDid,
665
- source: rest.source,
666
- createdAt: rest.createdAt,
667
- ...(rest.actorInfo ? { actorInfo: rest.actorInfo } : {}),
668
- },
669
- receiver: receiverDid,
670
- teamDid,
671
- isServices: rest.isServices,
758
+ websocketQueue.push({
759
+ input: {
760
+ notification: {
761
+ ...notification,
762
+ entityType: rest.entityType,
763
+ entityId: rest.entityId,
764
+ componentDid: rest.componentDid,
765
+ source: rest.source,
766
+ createdAt: rest.createdAt,
767
+ ...(rest.actorInfo ? { actorInfo: rest.actorInfo } : {}),
672
768
  },
673
- });
674
- } catch (error) {
675
- logger.error('Failed to insert to websocket push queue', { error });
676
- }
769
+ receiver: receiverDid,
770
+ teamDid,
771
+ isServices: rest.isServices,
772
+ },
773
+ });
677
774
  }
678
775
 
679
776
  notification.type = notification.type || 'notification';
@@ -727,7 +824,6 @@ const init = ({ node, notificationService }) => {
727
824
  logger.info('notification start insert to queue:', {
728
825
  teamDid: data?.teamDid,
729
826
  notificationId: data?.notification?.id,
730
- receivers: data?.receivers?.join(','),
731
827
  });
732
828
  if (isInstanceWorker()) {
733
829
  return;
@@ -753,13 +849,7 @@ const init = ({ node, notificationService }) => {
753
849
  type: 'server',
754
850
  });
755
851
 
756
- const users = await node.getNotificationReceivers({
757
- teamDid,
758
- userDids: receivers,
759
- includeConnectedAccounts: true,
760
- });
761
-
762
- if (users.length === 0) {
852
+ if (receivers.length === 0) {
763
853
  throw new Error('No users found');
764
854
  }
765
855
  // 如果 notification 没有 id,则生成一个, wallet 要基于这唯一个ID进行处理
@@ -771,8 +861,7 @@ const init = ({ node, notificationService }) => {
771
861
  notification.id = `NOTIF-${nanoid()}`;
772
862
  }
773
863
 
774
- users.forEach((userInfo) => {
775
- const receiverDid = userInfo.did;
864
+ receivers.forEach((receiverDid) => {
776
865
  queue.push({
777
866
  ...rest,
778
867
  notification,
@@ -781,7 +870,6 @@ const init = ({ node, notificationService }) => {
781
870
  receiver: receiverDid,
782
871
  sender,
783
872
  nodeInfo,
784
- userInfo,
785
873
  });
786
874
  });
787
875
  } catch (error) {
@@ -164,40 +164,38 @@ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options,
164
164
 
165
165
  const isServer = !teamDid || teamDid === nodeInfo.did;
166
166
 
167
- try {
168
- const result = await Promise.all(
169
- notifications.map((_notification) => {
170
- // 如果类型不存在或者是 notification 类型是才进行保存数据库,其他类型只需要通知到用户即可
171
- // eg: type = 'passthrough', 'hi', 'connect', 'feed', 默认只需要通知 wallet
172
- const pushOnly = _notification.type && _notification.type.toLowerCase() !== NOTIFICATION_TYPES.NOTIFICATION;
173
-
174
- // 通知渠道的确定有两种方式 1. 用户传入的 channels 2. 根据 notification 类型确定
175
- // 如果是 hi connect, 只需要通知 wallet
176
- // passthrough 类型,用于 discuss kit 的 chat channel
177
- // 1. push kit: native 通知快速跳转到 chat channel
178
- // 2. app: 钱包消息,可以查看到接收到消息
179
-
180
- return node.createNotification({
181
- teamDid,
182
- ...(pushOnly ? { notification: _notification } : { ..._notification }),
183
- receiver: rawDid,
184
- componentDid: sender.componentDid,
185
- // 这两个字段用于 socket 通知
186
- entityType: _notification.entityType || 'blocklet',
187
- entityId: _notification.entityId || sender.appDid,
188
- source: isServer ? 'system' : _notification.source || 'component',
189
- channels: CHANNEL_MAP[_notification.type] || channels,
190
- sender,
191
- options: { ...rest },
192
- pushOnly,
193
- });
194
- })
195
- );
196
- return result;
197
- } catch (error) {
198
- logger.error('Failed to create notification', { error });
199
- return undefined;
200
- }
167
+ // 开始执行 service 消息的推送,不需要同步执行
168
+ Promise.all(
169
+ notifications.map((_notification) => {
170
+ // 如果类型不存在或者是 notification 类型是才进行保存数据库,其他类型只需要通知到用户即可
171
+ // eg: type = 'passthrough', 'hi', 'connect', 'feed', 默认只需要通知 wallet
172
+ const pushOnly = _notification.type && _notification.type.toLowerCase() !== NOTIFICATION_TYPES.NOTIFICATION;
173
+
174
+ // 通知渠道的确定有两种方式 1. 用户传入的 channels 2. 根据 notification 类型确定
175
+ // 如果是 hi connect, 只需要通知 wallet
176
+ // passthrough 类型,用于 discuss kit 的 chat channel
177
+ // 1. push kit: native 通知快速跳转到 chat channel
178
+ // 2. app: 钱包消息,可以查看到接收到消息
179
+
180
+ return node.createNotification({
181
+ teamDid,
182
+ ...(pushOnly ? { notification: _notification } : { ..._notification }),
183
+ receiver: rawDid,
184
+ componentDid: sender.componentDid,
185
+ // 这两个字段用于 socket 通知
186
+ entityType: _notification.entityType || 'blocklet',
187
+ entityId: _notification.entityId || sender.appDid,
188
+ source: isServer ? 'system' : _notification.source || 'component',
189
+ channels: CHANNEL_MAP[_notification.type] || channels,
190
+ sender,
191
+ options: { ...rest },
192
+ pushOnly,
193
+ });
194
+ })
195
+ ).catch((error) => {
196
+ logger.error('Failed to send notification', { error });
197
+ });
198
+ return true;
201
199
  };
202
200
 
203
201
  // server send notification to app
@@ -352,6 +350,36 @@ const onAuthenticate = async ({ channel, did, payload }) => {
352
350
  }
353
351
  };
354
352
 
353
+ /**
354
+ * 安全地更新 webhook 状态,不影响主流程
355
+ * @param {object} params - 更新参数
356
+ * @param {object} params.node - node 实例
357
+ * @param {object} params.webhook - webhook 对象
358
+ * @param {string} params.teamDid - team DID
359
+ * @param {boolean} params.isService - 是否为服务
360
+ * @param {Array<string>} params.receivers - 接收者列表
361
+ * @param {number|undefined} params.consecutiveFailures - 连续失败次数,0=重置,undefined=自动+1
362
+ */
363
+ const safeUpdateWebhookState = async ({ node, webhook, teamDid, isService, receivers, consecutiveFailures }) => {
364
+ try {
365
+ await node.updateWebHookState({
366
+ webhook,
367
+ teamDid,
368
+ isService,
369
+ userDids: receivers,
370
+ consecutiveFailures,
371
+ });
372
+ } catch (error) {
373
+ logger.debug('Failed to update webhook state', {
374
+ error,
375
+ teamDid,
376
+ receivers,
377
+ webhook: webhook.url,
378
+ consecutiveFailures,
379
+ });
380
+ }
381
+ };
382
+
355
383
  const sendToWebhook = async ({ sender, receiver, notification, node, pushOnly }) => {
356
384
  const teamDid = sender.appDid;
357
385
  const { id, appInfo, ...rest } = notification;
@@ -371,6 +399,7 @@ const sendToWebhook = async ({ sender, receiver, notification, node, pushOnly })
371
399
 
372
400
  const senderInfo = await ensureSenderApp({ sender, node, nodeInfo });
373
401
 
402
+ const isService = teamDid && teamDid !== nodeInfo.did;
374
403
  // 发送消息前要添加 sender 内容。否则邮件中没有 blocklet 的信息
375
404
  const notifications = parseNotification({ id, ...rest }, senderInfo);
376
405
  const webhookSenderMap = new Map();
@@ -405,6 +434,15 @@ const sendToWebhook = async ({ sender, receiver, notification, node, pushOnly })
405
434
  webhookParams,
406
435
  });
407
436
  }
437
+ // 发送成功,重置失败次数
438
+ await safeUpdateWebhookState({
439
+ node,
440
+ webhook,
441
+ teamDid,
442
+ isService,
443
+ receivers,
444
+ consecutiveFailures: 0,
445
+ });
408
446
  return res;
409
447
  } catch (error) {
410
448
  // 使用 debug 级别记录详细执行信息,避免与上层业务日志重复
@@ -424,6 +462,14 @@ const sendToWebhook = async ({ sender, receiver, notification, node, pushOnly })
424
462
  webhookParams,
425
463
  });
426
464
  }
465
+ // 发送失败,不传入 consecutiveFailures,让底层自动 +1
466
+ await safeUpdateWebhookState({
467
+ node,
468
+ webhook,
469
+ teamDid,
470
+ isService,
471
+ receivers,
472
+ });
427
473
  const err = new Error('Failed to send webhook');
428
474
  err.details = error;
429
475
  throw err;
@@ -315,7 +315,12 @@ const validateWebhooks = async ({ webhooks, user }) => {
315
315
  }
316
316
 
317
317
  // 去重:webhooks 包含全量的数据,去重时会把历史数据存在的重复数据清理掉
318
- const uniqueWebhooks = uniqBy(webhooks, 'url');
318
+ const uniqueWebhooks = uniqBy(webhooks, 'url').map((webhook) => {
319
+ return {
320
+ ...webhook,
321
+ consecutiveFailures: webhook.enabled ? webhook.consecutiveFailures : 0,
322
+ };
323
+ });
319
324
 
320
325
  return { value: uniqueWebhooks, error: null };
321
326
  };