@dereekb/firebase-server 12.7.0 → 13.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/LICENSE +1 -1
  2. package/index.cjs.default.js +1 -0
  3. package/index.cjs.js +2859 -0
  4. package/index.cjs.mjs +2 -0
  5. package/index.esm.js +2699 -0
  6. package/mailgun/index.cjs.default.js +1 -0
  7. package/mailgun/{src/lib/auth.mailgun.js → index.cjs.js} +6 -6
  8. package/mailgun/index.cjs.mjs +2 -0
  9. package/mailgun/index.esm.js +39 -0
  10. package/mailgun/package.json +20 -5
  11. package/model/index.cjs.default.js +1 -0
  12. package/model/index.cjs.js +4767 -0
  13. package/model/index.cjs.mjs +2 -0
  14. package/model/index.d.ts +1 -0
  15. package/model/index.esm.js +4630 -0
  16. package/model/package.json +28 -4
  17. package/model/src/lib/notification/notification.module.d.ts +10 -10
  18. package/model/src/lib/storagefile/storagefile.task.service.handler.d.ts +3 -14
  19. package/package.json +49 -47
  20. package/src/lib/auth/auth.context.d.ts +1 -1
  21. package/src/lib/auth/auth.service.d.ts +1 -1
  22. package/src/lib/function/assert.d.ts +2 -2
  23. package/src/lib/function/context.d.ts +1 -1
  24. package/src/lib/function/error.d.ts +0 -8
  25. package/src/lib/index.d.ts +1 -0
  26. package/src/lib/nest/app.d.ts +1 -1
  27. package/src/lib/nest/development/development.app.function.d.ts +1 -2
  28. package/src/lib/nest/function/call.d.ts +1 -1
  29. package/src/lib/nest/function/context.d.ts +0 -4
  30. package/src/lib/nest/function/index.d.ts +0 -1
  31. package/src/lib/nest/function/nest.d.ts +1 -1
  32. package/src/lib/nest/function/v2/blocking.d.ts +3 -2
  33. package/src/lib/nest/model/crud.assert.function.d.ts +0 -4
  34. package/src/lib/storage/storage.d.ts +1 -1
  35. package/src/lib/type.d.ts +9 -0
  36. package/test/index.cjs.default.js +1 -0
  37. package/test/index.cjs.js +1258 -0
  38. package/test/index.cjs.mjs +2 -0
  39. package/test/index.d.ts +1 -0
  40. package/test/index.esm.js +1181 -0
  41. package/test/package.json +31 -4
  42. package/test/src/lib/firebase/firebase.admin.auth.d.ts +22 -13
  43. package/test/src/lib/firebase/firebase.admin.collection.d.ts +6 -6
  44. package/test/src/lib/firebase/firebase.admin.d.ts +10 -10
  45. package/test/src/lib/firebase/firebase.admin.function.d.ts +9 -9
  46. package/test/src/lib/firebase/firebase.admin.nest.d.ts +8 -8
  47. package/test/src/lib/firebase/firebase.admin.nest.function.d.ts +9 -9
  48. package/test/src/lib/firebase/firebase.test.d.ts +30 -0
  49. package/test/src/lib/firebase/index.d.ts +1 -1
  50. package/test/src/lib/firestore/firestore.admin.d.ts +3 -3
  51. package/test/src/lib/firestore/firestore.d.ts +2 -2
  52. package/test/src/lib/storage/storage.admin.d.ts +3 -3
  53. package/test/src/lib/storage/storage.d.ts +2 -2
  54. package/zoho/LICENSE +1 -1
  55. package/zoho/index.cjs.default.js +1 -0
  56. package/zoho/index.cjs.js +82 -85
  57. package/zoho/index.cjs.mjs +2 -0
  58. package/zoho/index.d.ts +1 -0
  59. package/zoho/index.esm.js +84 -87
  60. package/zoho/package.json +15 -15
  61. package/CHANGELOG.md +0 -2237
  62. package/mailgun/src/index.js +0 -5
  63. package/mailgun/src/index.js.map +0 -1
  64. package/mailgun/src/lib/auth.mailgun.js.map +0 -1
  65. package/mailgun/src/lib/index.js +0 -5
  66. package/mailgun/src/lib/index.js.map +0 -1
  67. package/model/src/index.js +0 -5
  68. package/model/src/index.js.map +0 -1
  69. package/model/src/lib/index.js +0 -7
  70. package/model/src/lib/index.js.map +0 -1
  71. package/model/src/lib/mailgun/index.js +0 -5
  72. package/model/src/lib/mailgun/index.js.map +0 -1
  73. package/model/src/lib/mailgun/notification.send.service.mailgun.js +0 -68
  74. package/model/src/lib/mailgun/notification.send.service.mailgun.js.map +0 -1
  75. package/model/src/lib/notification/index.js +0 -20
  76. package/model/src/lib/notification/index.js.map +0 -1
  77. package/model/src/lib/notification/notification.action.init.service.js +0 -230
  78. package/model/src/lib/notification/notification.action.init.service.js.map +0 -1
  79. package/model/src/lib/notification/notification.action.service.js +0 -1487
  80. package/model/src/lib/notification/notification.action.service.js.map +0 -1
  81. package/model/src/lib/notification/notification.config.js +0 -13
  82. package/model/src/lib/notification/notification.config.js.map +0 -1
  83. package/model/src/lib/notification/notification.config.service.js +0 -60
  84. package/model/src/lib/notification/notification.config.service.js.map +0 -1
  85. package/model/src/lib/notification/notification.create.run.js +0 -59
  86. package/model/src/lib/notification/notification.create.run.js.map +0 -1
  87. package/model/src/lib/notification/notification.error.js +0 -87
  88. package/model/src/lib/notification/notification.error.js.map +0 -1
  89. package/model/src/lib/notification/notification.expedite.service.js +0 -112
  90. package/model/src/lib/notification/notification.expedite.service.js.map +0 -1
  91. package/model/src/lib/notification/notification.module.js +0 -106
  92. package/model/src/lib/notification/notification.module.js.map +0 -1
  93. package/model/src/lib/notification/notification.send.js +0 -3
  94. package/model/src/lib/notification/notification.send.js.map +0 -1
  95. package/model/src/lib/notification/notification.send.service.js +0 -10
  96. package/model/src/lib/notification/notification.send.service.js.map +0 -1
  97. package/model/src/lib/notification/notification.send.service.notificationsummary.js +0 -104
  98. package/model/src/lib/notification/notification.send.service.notificationsummary.js.map +0 -1
  99. package/model/src/lib/notification/notification.send.service.text.js +0 -29
  100. package/model/src/lib/notification/notification.send.service.text.js.map +0 -1
  101. package/model/src/lib/notification/notification.task.service.handler.js +0 -65
  102. package/model/src/lib/notification/notification.task.service.handler.js.map +0 -1
  103. package/model/src/lib/notification/notification.task.service.js +0 -10
  104. package/model/src/lib/notification/notification.task.service.js.map +0 -1
  105. package/model/src/lib/notification/notification.task.service.util.js +0 -27
  106. package/model/src/lib/notification/notification.task.service.util.js.map +0 -1
  107. package/model/src/lib/notification/notification.task.subtask.handler.js +0 -256
  108. package/model/src/lib/notification/notification.task.subtask.handler.js.map +0 -1
  109. package/model/src/lib/notification/notification.util.js +0 -478
  110. package/model/src/lib/notification/notification.util.js.map +0 -1
  111. package/model/src/lib/storagefile/index.js +0 -12
  112. package/model/src/lib/storagefile/index.js.map +0 -1
  113. package/model/src/lib/storagefile/storagefile.action.init.service.js +0 -155
  114. package/model/src/lib/storagefile/storagefile.action.init.service.js.map +0 -1
  115. package/model/src/lib/storagefile/storagefile.action.server.js +0 -797
  116. package/model/src/lib/storagefile/storagefile.action.server.js.map +0 -1
  117. package/model/src/lib/storagefile/storagefile.error.js +0 -106
  118. package/model/src/lib/storagefile/storagefile.error.js.map +0 -1
  119. package/model/src/lib/storagefile/storagefile.module.js +0 -64
  120. package/model/src/lib/storagefile/storagefile.module.js.map +0 -1
  121. package/model/src/lib/storagefile/storagefile.task.service.handler.js +0 -287
  122. package/model/src/lib/storagefile/storagefile.task.service.handler.js.map +0 -1
  123. package/model/src/lib/storagefile/storagefile.upload.service.initializer.js +0 -180
  124. package/model/src/lib/storagefile/storagefile.upload.service.initializer.js.map +0 -1
  125. package/model/src/lib/storagefile/storagefile.upload.service.js +0 -10
  126. package/model/src/lib/storagefile/storagefile.upload.service.js.map +0 -1
  127. package/model/src/lib/storagefile/storagefile.util.js +0 -54
  128. package/model/src/lib/storagefile/storagefile.util.js.map +0 -1
  129. package/src/index.js +0 -5
  130. package/src/index.js.map +0 -1
  131. package/src/lib/auth/auth.context.js +0 -13
  132. package/src/lib/auth/auth.context.js.map +0 -1
  133. package/src/lib/auth/auth.service.error.js +0 -34
  134. package/src/lib/auth/auth.service.error.js.map +0 -1
  135. package/src/lib/auth/auth.service.js +0 -427
  136. package/src/lib/auth/auth.service.js.map +0 -1
  137. package/src/lib/auth/auth.util.js +0 -23
  138. package/src/lib/auth/auth.util.js.map +0 -1
  139. package/src/lib/auth/index.js +0 -8
  140. package/src/lib/auth/index.js.map +0 -1
  141. package/src/lib/env/env.service.js +0 -7
  142. package/src/lib/env/env.service.js.map +0 -1
  143. package/src/lib/env/index.js +0 -5
  144. package/src/lib/env/index.js.map +0 -1
  145. package/src/lib/firestore/array.js +0 -34
  146. package/src/lib/firestore/array.js.map +0 -1
  147. package/src/lib/firestore/driver.accessor.batch.js +0 -93
  148. package/src/lib/firestore/driver.accessor.batch.js.map +0 -1
  149. package/src/lib/firestore/driver.accessor.default.js +0 -62
  150. package/src/lib/firestore/driver.accessor.default.js.map +0 -1
  151. package/src/lib/firestore/driver.accessor.js +0 -50
  152. package/src/lib/firestore/driver.accessor.js.map +0 -1
  153. package/src/lib/firestore/driver.accessor.transaction.js +0 -96
  154. package/src/lib/firestore/driver.accessor.transaction.js.map +0 -1
  155. package/src/lib/firestore/driver.js +0 -14
  156. package/src/lib/firestore/driver.js.map +0 -1
  157. package/src/lib/firestore/driver.query.js +0 -55
  158. package/src/lib/firestore/driver.query.js.map +0 -1
  159. package/src/lib/firestore/firestore.js +0 -10
  160. package/src/lib/firestore/firestore.js.map +0 -1
  161. package/src/lib/firestore/increment.js +0 -17
  162. package/src/lib/firestore/increment.js.map +0 -1
  163. package/src/lib/firestore/index.js +0 -9
  164. package/src/lib/firestore/index.js.map +0 -1
  165. package/src/lib/function/assert.js +0 -68
  166. package/src/lib/function/assert.js.map +0 -1
  167. package/src/lib/function/context.js +0 -14
  168. package/src/lib/function/context.js.map +0 -1
  169. package/src/lib/function/error.auth.js +0 -25
  170. package/src/lib/function/error.auth.js.map +0 -1
  171. package/src/lib/function/error.js +0 -221
  172. package/src/lib/function/error.js.map +0 -1
  173. package/src/lib/function/index.js +0 -9
  174. package/src/lib/function/index.js.map +0 -1
  175. package/src/lib/function/type.js +0 -3
  176. package/src/lib/function/type.js.map +0 -1
  177. package/src/lib/index.js +0 -11
  178. package/src/lib/index.js.map +0 -1
  179. package/src/lib/nest/app.js +0 -114
  180. package/src/lib/nest/app.js.map +0 -1
  181. package/src/lib/nest/auth/auth.module.js +0 -60
  182. package/src/lib/nest/auth/auth.module.js.map +0 -1
  183. package/src/lib/nest/auth/auth.util.js +0 -72
  184. package/src/lib/nest/auth/auth.util.js.map +0 -1
  185. package/src/lib/nest/auth/index.js +0 -6
  186. package/src/lib/nest/auth/index.js.map +0 -1
  187. package/src/lib/nest/development/development.app.function.js +0 -38
  188. package/src/lib/nest/development/development.app.function.js.map +0 -1
  189. package/src/lib/nest/development/development.assert.function.js +0 -3
  190. package/src/lib/nest/development/development.assert.function.js.map +0 -1
  191. package/src/lib/nest/development/development.function.js +0 -41
  192. package/src/lib/nest/development/development.function.js.map +0 -1
  193. package/src/lib/nest/development/development.schedule.function.error.js +0 -35
  194. package/src/lib/nest/development/development.schedule.function.error.js.map +0 -1
  195. package/src/lib/nest/development/development.schedule.function.js +0 -54
  196. package/src/lib/nest/development/development.schedule.function.js.map +0 -1
  197. package/src/lib/nest/development/index.js +0 -9
  198. package/src/lib/nest/development/index.js.map +0 -1
  199. package/src/lib/nest/env/env.service.js +0 -19
  200. package/src/lib/nest/env/env.service.js.map +0 -1
  201. package/src/lib/nest/env/env.util.js +0 -12
  202. package/src/lib/nest/env/env.util.js.map +0 -1
  203. package/src/lib/nest/env/index.js +0 -6
  204. package/src/lib/nest/env/index.js.map +0 -1
  205. package/src/lib/nest/firebase/firebase.module.js +0 -17
  206. package/src/lib/nest/firebase/firebase.module.js.map +0 -1
  207. package/src/lib/nest/firebase/index.js +0 -5
  208. package/src/lib/nest/firebase/index.js.map +0 -1
  209. package/src/lib/nest/firestore/firestore.module.js +0 -86
  210. package/src/lib/nest/firestore/firestore.module.js.map +0 -1
  211. package/src/lib/nest/firestore/index.js +0 -5
  212. package/src/lib/nest/firestore/index.js.map +0 -1
  213. package/src/lib/nest/function/call.js +0 -46
  214. package/src/lib/nest/function/call.js.map +0 -1
  215. package/src/lib/nest/function/context.js +0 -79
  216. package/src/lib/nest/function/context.js.map +0 -1
  217. package/src/lib/nest/function/index.js +0 -10
  218. package/src/lib/nest/function/index.js.map +0 -1
  219. package/src/lib/nest/function/nest.js +0 -17
  220. package/src/lib/nest/function/nest.js.map +0 -1
  221. package/src/lib/nest/function/schedule.js +0 -8
  222. package/src/lib/nest/function/schedule.js.map +0 -1
  223. package/src/lib/nest/function/v1/call.d.ts +0 -59
  224. package/src/lib/nest/function/v1/call.js +0 -55
  225. package/src/lib/nest/function/v1/call.js.map +0 -1
  226. package/src/lib/nest/function/v1/event.d.ts +0 -80
  227. package/src/lib/nest/function/v1/event.js +0 -52
  228. package/src/lib/nest/function/v1/event.js.map +0 -1
  229. package/src/lib/nest/function/v1/index.d.ts +0 -3
  230. package/src/lib/nest/function/v1/index.js +0 -7
  231. package/src/lib/nest/function/v1/index.js.map +0 -1
  232. package/src/lib/nest/function/v1/schedule.d.ts +0 -47
  233. package/src/lib/nest/function/v1/schedule.js +0 -68
  234. package/src/lib/nest/function/v1/schedule.js.map +0 -1
  235. package/src/lib/nest/function/v2/blocking.js +0 -38
  236. package/src/lib/nest/function/v2/blocking.js.map +0 -1
  237. package/src/lib/nest/function/v2/call.js +0 -31
  238. package/src/lib/nest/function/v2/call.js.map +0 -1
  239. package/src/lib/nest/function/v2/event.js +0 -25
  240. package/src/lib/nest/function/v2/event.js.map +0 -1
  241. package/src/lib/nest/function/v2/index.js +0 -9
  242. package/src/lib/nest/function/v2/index.js.map +0 -1
  243. package/src/lib/nest/function/v2/schedule.js +0 -56
  244. package/src/lib/nest/function/v2/schedule.js.map +0 -1
  245. package/src/lib/nest/function/v2/taskqueue.js +0 -26
  246. package/src/lib/nest/function/v2/taskqueue.js.map +0 -1
  247. package/src/lib/nest/index.js +0 -15
  248. package/src/lib/nest/index.js.map +0 -1
  249. package/src/lib/nest/middleware/appcheck.decorator.js +0 -12
  250. package/src/lib/nest/middleware/appcheck.decorator.js.map +0 -1
  251. package/src/lib/nest/middleware/appcheck.js +0 -3
  252. package/src/lib/nest/middleware/appcheck.js.map +0 -1
  253. package/src/lib/nest/middleware/appcheck.middleware.js +0 -74
  254. package/src/lib/nest/middleware/appcheck.middleware.js.map +0 -1
  255. package/src/lib/nest/middleware/appcheck.module.js +0 -21
  256. package/src/lib/nest/middleware/appcheck.module.js.map +0 -1
  257. package/src/lib/nest/middleware/globalprefix.js +0 -11
  258. package/src/lib/nest/middleware/globalprefix.js.map +0 -1
  259. package/src/lib/nest/middleware/index.js +0 -10
  260. package/src/lib/nest/middleware/index.js.map +0 -1
  261. package/src/lib/nest/middleware/rawbody.middleware.js +0 -16
  262. package/src/lib/nest/middleware/rawbody.middleware.js.map +0 -1
  263. package/src/lib/nest/middleware/webhook.js +0 -24
  264. package/src/lib/nest/middleware/webhook.js.map +0 -1
  265. package/src/lib/nest/model/call.model.function.js +0 -73
  266. package/src/lib/nest/model/call.model.function.js.map +0 -1
  267. package/src/lib/nest/model/create.model.function.js +0 -27
  268. package/src/lib/nest/model/create.model.function.js.map +0 -1
  269. package/src/lib/nest/model/crud.assert.function.js +0 -3
  270. package/src/lib/nest/model/crud.assert.function.js.map +0 -1
  271. package/src/lib/nest/model/delete.model.function.js +0 -27
  272. package/src/lib/nest/model/delete.model.function.js.map +0 -1
  273. package/src/lib/nest/model/index.js +0 -11
  274. package/src/lib/nest/model/index.js.map +0 -1
  275. package/src/lib/nest/model/permission.error.js +0 -24
  276. package/src/lib/nest/model/permission.error.js.map +0 -1
  277. package/src/lib/nest/model/read.model.function.js +0 -27
  278. package/src/lib/nest/model/read.model.function.js.map +0 -1
  279. package/src/lib/nest/model/specifier.function.js +0 -35
  280. package/src/lib/nest/model/specifier.function.js.map +0 -1
  281. package/src/lib/nest/model/update.model.function.js +0 -27
  282. package/src/lib/nest/model/update.model.function.js.map +0 -1
  283. package/src/lib/nest/nest.provider.js +0 -89
  284. package/src/lib/nest/nest.provider.js.map +0 -1
  285. package/src/lib/nest/storage/index.js +0 -5
  286. package/src/lib/nest/storage/index.js.map +0 -1
  287. package/src/lib/nest/storage/storage.module.js +0 -112
  288. package/src/lib/nest/storage/storage.module.js.map +0 -1
  289. package/src/lib/storage/driver.accessor.js +0 -299
  290. package/src/lib/storage/driver.accessor.js.map +0 -1
  291. package/src/lib/storage/driver.js +0 -12
  292. package/src/lib/storage/driver.js.map +0 -1
  293. package/src/lib/storage/index.js +0 -8
  294. package/src/lib/storage/index.js.map +0 -1
  295. package/src/lib/storage/storage.js +0 -20
  296. package/src/lib/storage/storage.js.map +0 -1
  297. package/src/lib/storage/storage.service.js +0 -26
  298. package/src/lib/storage/storage.service.js.map +0 -1
  299. package/test/src/index.js +0 -5
  300. package/test/src/index.js.map +0 -1
  301. package/test/src/lib/firebase/firebase.admin.auth.js +0 -260
  302. package/test/src/lib/firebase/firebase.admin.auth.js.map +0 -1
  303. package/test/src/lib/firebase/firebase.admin.collection.js +0 -108
  304. package/test/src/lib/firebase/firebase.admin.collection.js.map +0 -1
  305. package/test/src/lib/firebase/firebase.admin.function.js +0 -132
  306. package/test/src/lib/firebase/firebase.admin.function.js.map +0 -1
  307. package/test/src/lib/firebase/firebase.admin.js +0 -174
  308. package/test/src/lib/firebase/firebase.admin.js.map +0 -1
  309. package/test/src/lib/firebase/firebase.admin.nest.function.callable.context.js +0 -42
  310. package/test/src/lib/firebase/firebase.admin.nest.function.callable.context.js.map +0 -1
  311. package/test/src/lib/firebase/firebase.admin.nest.function.cloud.context.js +0 -40
  312. package/test/src/lib/firebase/firebase.admin.nest.function.cloud.context.js.map +0 -1
  313. package/test/src/lib/firebase/firebase.admin.nest.function.js +0 -64
  314. package/test/src/lib/firebase/firebase.admin.nest.function.js.map +0 -1
  315. package/test/src/lib/firebase/firebase.admin.nest.js +0 -107
  316. package/test/src/lib/firebase/firebase.admin.nest.js.map +0 -1
  317. package/test/src/lib/firebase/firebase.admin.test.server.js +0 -37
  318. package/test/src/lib/firebase/firebase.admin.test.server.js.map +0 -1
  319. package/test/src/lib/firebase/firebase.function.js +0 -58
  320. package/test/src/lib/firebase/firebase.function.js.map +0 -1
  321. package/test/src/lib/firebase/firebase.jest.d.ts +0 -21
  322. package/test/src/lib/firebase/firebase.jest.js +0 -45
  323. package/test/src/lib/firebase/firebase.jest.js.map +0 -1
  324. package/test/src/lib/firebase/firebase.js +0 -74
  325. package/test/src/lib/firebase/firebase.js.map +0 -1
  326. package/test/src/lib/firebase/index.js +0 -15
  327. package/test/src/lib/firebase/index.js.map +0 -1
  328. package/test/src/lib/firestore/firestore.admin.js +0 -21
  329. package/test/src/lib/firestore/firestore.admin.js.map +0 -1
  330. package/test/src/lib/firestore/firestore.js +0 -57
  331. package/test/src/lib/firestore/firestore.js.map +0 -1
  332. package/test/src/lib/firestore/index.js +0 -6
  333. package/test/src/lib/firestore/index.js.map +0 -1
  334. package/test/src/lib/index.js +0 -7
  335. package/test/src/lib/index.js.map +0 -1
  336. package/test/src/lib/storage/index.js +0 -6
  337. package/test/src/lib/storage/index.js.map +0 -1
  338. package/test/src/lib/storage/storage.admin.js +0 -21
  339. package/test/src/lib/storage/storage.admin.js.map +0 -1
  340. package/test/src/lib/storage/storage.js +0 -59
  341. package/test/src/lib/storage/storage.js.map +0 -1
  342. /package/{zoho/index.cjs.d.ts → index.d.ts} +0 -0
  343. /package/{zoho/index.esm.d.ts → mailgun/index.d.ts} +0 -0
@@ -0,0 +1,4767 @@
1
+ 'use strict';
2
+
3
+ var util = require('@dereekb/util');
4
+ var date = require('@dereekb/date');
5
+ var firebase = require('@dereekb/firebase');
6
+ var firebaseServer = require('@dereekb/firebase-server');
7
+ var dateFns = require('date-fns');
8
+ var common = require('@nestjs/common');
9
+ var config = require('@nestjs/config');
10
+ var makeError = require('make-error');
11
+ var archiver = require('archiver');
12
+
13
+ const MAILGUN_NOTIFICATION_EMAIL_SEND_SERVICE_DEFAULT_MAX_BATCH_SIZE_PER_REQUEST = 50;
14
+ function mailgunNotificationEmailSendService(config) {
15
+ const { mailgunService, defaultSendTemplateName, maxBatchSizePerRequest: inputMaxBatchSizePerRequest, messageBuilders: inputMessageBuilders } = config;
16
+ const lowercaseKeysMessageBuilders = util.mapObjectKeysToLowercase(inputMessageBuilders);
17
+ const maxBatchSizePerRequest = inputMaxBatchSizePerRequest ?? MAILGUN_NOTIFICATION_EMAIL_SEND_SERVICE_DEFAULT_MAX_BATCH_SIZE_PER_REQUEST;
18
+ const sendService = {
19
+ async buildSendInstanceForEmailNotificationMessages(notificationMessages) {
20
+ const templateMap = util.multiValueMapBuilder();
21
+ // group by templates
22
+ notificationMessages.forEach((x) => {
23
+ const sendTemplateName = x.emailContent?.sendTemplateName ?? x.content.sendTemplateName ?? defaultSendTemplateName;
24
+ if (sendTemplateName == null) {
25
+ throw new Error(`mailgunNotificationEmailSendService(): A sendTemplateName for a message was not available and no default was provided. Consider configuring a default send template.`);
26
+ }
27
+ templateMap.add(sendTemplateName, x);
28
+ });
29
+ // build send batches
30
+ const messageSendBatches = templateMap.entries().flatMap(([templateType, messages]) => {
31
+ return util.batch(messages, maxBatchSizePerRequest).map((x) => [templateType, x]);
32
+ });
33
+ // create the template requests
34
+ const templateRequestArrays = await Promise.all(messageSendBatches.map(async ([sendTemplateName, messages]) => {
35
+ const sendTemplateNameToLowercase = sendTemplateName.toLowerCase();
36
+ const builderForKey = lowercaseKeysMessageBuilders[sendTemplateNameToLowercase];
37
+ if (!builderForKey) {
38
+ throw new Error(`mailgunNotificationEmailSendService(): A template builder was not available for template type "${sendTemplateName}".`);
39
+ }
40
+ else {
41
+ const input = { mailgunService, sendTemplateName, messages };
42
+ return builderForKey(input);
43
+ }
44
+ }));
45
+ const templateRequests = templateRequestArrays.flat();
46
+ const sendFn = async () => {
47
+ const success = [];
48
+ const failed = [];
49
+ // send the template emails
50
+ await util.runAsyncTasksForValues(templateRequests, (x) => {
51
+ const recipients = util.asArray(x.to).map((z) => z.email);
52
+ return mailgunService
53
+ .sendTemplateEmail(x)
54
+ .then(() => {
55
+ util.pushArrayItemsIntoArray(success, recipients);
56
+ })
57
+ .catch((e) => {
58
+ util.pushArrayItemsIntoArray(failed, recipients);
59
+ console.error('mailgunNotificationEmailSendService(): failed sending template emails', e);
60
+ // suppress error
61
+ });
62
+ }, { maxParallelTasks: 3 });
63
+ const result = {
64
+ success,
65
+ failed,
66
+ ignored: []
67
+ };
68
+ return result;
69
+ };
70
+ return sendFn;
71
+ }
72
+ };
73
+ return sendService;
74
+ }
75
+
76
+ function createNotificationIdRequiredError() {
77
+ return firebaseServer.preconditionConflictError({
78
+ message: `The required id was not present when attempting to create a Notification.`,
79
+ code: firebase.CREATE_NOTIFICATION_ID_REQUIRED_ERROR_CODE
80
+ });
81
+ }
82
+ function notificationModelAlreadyInitializedError() {
83
+ return firebaseServer.preconditionConflictError({
84
+ message: `This model has already been initialized.`,
85
+ code: firebase.NOTIFICATION_MODEL_ALREADY_INITIALIZED_ERROR_CODE
86
+ });
87
+ }
88
+ function notificationBoxUnregistredModelTypeInitializationError(key) {
89
+ return firebaseServer.preconditionConflictError({
90
+ message: `This NotificationBox is associated with an unregistered model type.`,
91
+ code: firebase.NOTIFICATION_MODEL_ALREADY_INITIALIZED_ERROR_CODE,
92
+ data: {
93
+ key
94
+ }
95
+ });
96
+ }
97
+ function notificationBoxDoesNotExist() {
98
+ return firebaseServer.preconditionConflictError({
99
+ message: `A NotificationBox does not exist for this model.`,
100
+ code: firebase.NOTIFICATION_BOX_DOES_NOT_EXIST_ERROR_CODE
101
+ });
102
+ }
103
+ function notificationBoxExclusionTargetInvalidError() {
104
+ return firebaseServer.preconditionConflictError({
105
+ message: `The target for exclusion is invalid. The target recipient on the NotificationBox must be exist on the NotificationBox and have a uid to be excluded.`,
106
+ code: firebase.NOTIFICATION_BOX_EXCLUSION_TARGET_INVALID_ERROR_CODE
107
+ });
108
+ }
109
+ function notificationBoxExistsForModelError() {
110
+ return firebaseServer.preconditionConflictError({
111
+ message: `A NotificationBox already exists for this model.`,
112
+ code: firebase.NOTIFICATION_BOX_EXISTS_FOR_MODEL_ERROR_CODE
113
+ });
114
+ }
115
+ function notificationBoxRecipientDoesNotExistsError() {
116
+ return firebaseServer.preconditionConflictError({
117
+ message: `An existing NotificationBox recipient for the target does not exist. You must pass insert=true to create a new recipient.`,
118
+ code: firebase.NOTIFICATION_BOX_RECIPIENT_DOES_NOT_EXIST_ERROR_CODE
119
+ });
120
+ }
121
+ function notificationUserInvalidUidForCreateError(uid) {
122
+ return firebaseServer.preconditionConflictError({
123
+ message: `The user with the uid '${uid}' does not exist. Cannot create a NotificationUser for them.`,
124
+ code: firebase.NOTIFICATION_USER_INVALID_UID_FOR_CREATE_ERROR_CODE,
125
+ data: {
126
+ uid
127
+ }
128
+ });
129
+ }
130
+ function notificationUserBlockedFromBeingAddedToRecipientsError(uid) {
131
+ return firebaseServer.preconditionConflictError({
132
+ message: `The user with the uid '${uid}' has blocked themselves from from being added recipients.`,
133
+ code: firebase.NOTIFICATION_USER_BLOCKED_FROM_BEING_ADD_TO_RECIPIENTS_ERROR_CODE,
134
+ data: {
135
+ uid
136
+ }
137
+ });
138
+ }
139
+ function notificationUserLockedConfigFromBeingUpdatedError(uid) {
140
+ return firebaseServer.preconditionConflictError({
141
+ message: `The user with the uid '${uid}' has locked their config from being updated.`,
142
+ code: firebase.NOTIFICATION_USER_LOCKED_CONFIG_FROM_BEING_UPDATED_ERROR_CODE,
143
+ data: {
144
+ uid
145
+ }
146
+ });
147
+ }
148
+
149
+ // MARK: Create NotificationSummary
150
+ function makeNewNotificationSummaryTemplate(model) {
151
+ return {
152
+ cat: new Date(),
153
+ m: model,
154
+ o: firebase.firestoreDummyKey(),
155
+ s: true,
156
+ n: []
157
+ };
158
+ }
159
+ /**
160
+ * "Expands" the input into recipients for emails, texts, etc.
161
+ *
162
+ * Recipients may come from the NotificationBox, Notification or from the global recipients.
163
+ *
164
+ * Recipients are each configurable and may be defined with as little info as a single contact info, or have multiple contact info pieces associated with them.
165
+ *
166
+ * @param input
167
+ * @returns
168
+ */
169
+ async function expandNotificationRecipients(input) {
170
+ const { notificationUserAccessor, authService, notification, notificationBox, globalRecipients: inputGlobalRecipients, recipientFlagOverride, notificationSummaryIdForUid: inputNotificationSummaryIdForUid, onlySendToExplicitlyEnabledRecipients: inputOnlySendToExplicitlyEnabledRecipients, onlyTextExplicitlyEnabledRecipients: inputOnlyTextExplicitlyEnabledRecipients } = input;
171
+ const notificationBoxId = notificationBox?.id;
172
+ const notificationSummaryIdForUid = inputNotificationSummaryIdForUid ?? (() => undefined);
173
+ const notificationTemplateType = notification.n.t || firebase.DEFAULT_NOTIFICATION_TEMPLATE_TYPE;
174
+ const recipientFlag = recipientFlagOverride ?? notification.rf ?? firebase.NotificationRecipientSendFlag.NORMAL;
175
+ const onlyTextExplicitlyEnabledRecipients = inputOnlyTextExplicitlyEnabledRecipients !== false; // defaults to true
176
+ const onlySendToExplicitlyEnabledRecipients = inputOnlySendToExplicitlyEnabledRecipients === true; // defaults to false
177
+ const onlyEmailExplicitlyEnabledRecipients = onlySendToExplicitlyEnabledRecipients;
178
+ const onlySendNotificationSummaryExplicitlyEnabledRecipients = onlySendToExplicitlyEnabledRecipients;
179
+ const { canSendToGlobalRecipients, canSendToBoxRecipients, canSendToExplicitRecipients } = firebase.allowedNotificationRecipients(recipientFlag);
180
+ const initialExplicitRecipients = canSendToExplicitRecipients ? notification.r : [];
181
+ const initialGlobalRecipients = canSendToGlobalRecipients && inputGlobalRecipients ? inputGlobalRecipients : [];
182
+ const explicitRecipients = initialExplicitRecipients.map((x) => ({
183
+ ...x,
184
+ ...firebase.effectiveNotificationBoxRecipientTemplateConfig(x)
185
+ }));
186
+ const globalRecipients = initialGlobalRecipients.map((x) => ({
187
+ ...x,
188
+ ...firebase.effectiveNotificationBoxRecipientTemplateConfig(x)
189
+ }));
190
+ const explicitAndGlobalRecipients = [...explicitRecipients, ...globalRecipients];
191
+ const allBoxRecipientConfigs = canSendToBoxRecipients && notificationBox ? notificationBox.r : [];
192
+ const recipientUids = new Set();
193
+ const relevantBoxRecipientConfigs = [];
194
+ // find all recipients in the NotificationBox with the target template type flagged for them.
195
+ allBoxRecipientConfigs.forEach((x) => {
196
+ // ignore opt-out flagged recipients and excluded recipients
197
+ if (!x.f && !x.x) {
198
+ const relevantConfig = x.c[notificationTemplateType];
199
+ const effectiveTemplateConfig = relevantConfig ? firebase.effectiveNotificationBoxRecipientTemplateConfig(relevantConfig) : undefined;
200
+ if (!effectiveTemplateConfig || effectiveTemplateConfig.st || effectiveTemplateConfig.se || effectiveTemplateConfig.sp || effectiveTemplateConfig.st) {
201
+ relevantBoxRecipientConfigs.push({
202
+ recipient: x,
203
+ effectiveTemplateConfig
204
+ });
205
+ if (x.uid) {
206
+ recipientUids.add(x.uid);
207
+ }
208
+ }
209
+ }
210
+ });
211
+ // add other recipients to the map
212
+ const nonNotificationBoxUidRecipientConfigs = new Map();
213
+ explicitAndGlobalRecipients.forEach((x) => {
214
+ const { uid } = x;
215
+ if (uid && !recipientUids.has(uid)) {
216
+ // if already in recipientUids then they are a box recipient and we don't have to try and load them.
217
+ nonNotificationBoxUidRecipientConfigs.set(uid, x);
218
+ }
219
+ });
220
+ const otherNotificationUserUidOptOuts = new Set();
221
+ const otherNotificationUserUidSendExclusions = new Set();
222
+ const notificationUserRecipientConfigs = new Map();
223
+ if (nonNotificationBoxUidRecipientConfigs.size > 0) {
224
+ const nonNotificationBoxRecipientUids = Array.from(nonNotificationBoxUidRecipientConfigs.keys());
225
+ const notificationUserDocuments = firebase.loadDocumentsForIds(notificationUserAccessor, nonNotificationBoxRecipientUids);
226
+ // Attempt to load the NotificationUser for each uid.
227
+ // Not guranteed to exist, but those that do we want to their configurations to decide opt-in/opt-out, as well as override the input recipient configuration for the Notification.
228
+ const notificationUsers = await firebase.getDocumentSnapshotDataPairsWithData(notificationUserDocuments);
229
+ notificationUsers.forEach((x) => {
230
+ const { data: notificationUser } = x;
231
+ const { x: exclusions, dc, gc } = notificationUser;
232
+ const canSendNotification = firebase.notificationSendExclusionCanSendFunction(exclusions);
233
+ const effectiveConfig = firebase.mergeNotificationUserDefaultNotificationBoxRecipientConfig(dc, gc);
234
+ const uid = x.document.id;
235
+ notificationUserRecipientConfigs.set(uid, effectiveConfig);
236
+ // check if flagged for opt out on the global/default config
237
+ if (effectiveConfig.f) {
238
+ // if flagged for opt out, add to set
239
+ otherNotificationUserUidOptOuts.add(uid);
240
+ }
241
+ const isAllowedToSend = notificationBoxId ? canSendNotification(notificationBoxId) : true;
242
+ if (!isAllowedToSend) {
243
+ otherNotificationUserUidSendExclusions.add(uid);
244
+ }
245
+ });
246
+ }
247
+ /**
248
+ * Other NotificationRecipientWithConfig
249
+ */
250
+ const otherRecipientConfigs = new Map();
251
+ const explicitOtherRecipientEmailAddresses = new Map();
252
+ const explicitOtherRecipientTextNumbers = new Map();
253
+ const explicitOtherRecipientNotificationSummaryIds = new Map();
254
+ explicitAndGlobalRecipients.forEach((x) => {
255
+ const uid = x.uid;
256
+ if (uid) {
257
+ if (otherNotificationUserUidOptOuts.has(uid) || otherNotificationUserUidSendExclusions.has(uid)) {
258
+ return; // do not add to the recipients at all, user has opted out or send is excluded
259
+ }
260
+ const notificationUserRecipientConfig = notificationUserRecipientConfigs.get(uid);
261
+ if (notificationUserRecipientConfig != null) {
262
+ const userTemplateTypeConfig = notificationUserRecipientConfig.c[notificationTemplateType] ?? {};
263
+ const templateConfig = firebase.mergeNotificationBoxRecipientTemplateConfigs(firebase.effectiveNotificationBoxRecipientTemplateConfig(userTemplateTypeConfig), x);
264
+ // replace the input NotificationRecipientWithConfig with the user's config
265
+ x = {
266
+ ...notificationUserRecipientConfig,
267
+ ...firebase.effectiveNotificationBoxRecipientTemplateConfig(templateConfig),
268
+ uid
269
+ };
270
+ }
271
+ recipientUids.add(uid);
272
+ otherRecipientConfigs.set(uid, x);
273
+ }
274
+ if (x.e) {
275
+ explicitOtherRecipientEmailAddresses.set(x.e.toLowerCase(), x);
276
+ }
277
+ if (x.t) {
278
+ explicitOtherRecipientTextNumbers.set(x.t, x);
279
+ }
280
+ if (x.s) {
281
+ explicitOtherRecipientNotificationSummaryIds.set(x.s, x);
282
+ }
283
+ });
284
+ // load user details from auth service
285
+ const allUserDetails = await Promise.all(Array.from(recipientUids).map((uid) => authService
286
+ .userContext(uid)
287
+ .loadDetails()
288
+ .then((details) => [uid, details])
289
+ .catch(() => [uid, undefined])));
290
+ const userDetailsMap = new Map(allUserDetails);
291
+ const _internal = {
292
+ userDetailsMap,
293
+ explicitRecipients,
294
+ globalRecipients,
295
+ allBoxRecipientConfigs,
296
+ relevantBoxRecipientConfigs,
297
+ recipientUids,
298
+ otherRecipientConfigs,
299
+ explicitOtherRecipientEmailAddresses,
300
+ explicitOtherRecipientTextNumbers,
301
+ explicitOtherRecipientNotificationSummaryIds,
302
+ otherNotificationUserUidOptOuts,
303
+ otherNotificationUserUidSendExclusions,
304
+ nonNotificationBoxUidRecipientConfigs,
305
+ notificationUserRecipientConfigs
306
+ };
307
+ // make all email recipients
308
+ const emails = [];
309
+ const emailUidsSet = new Set();
310
+ function checkShouldSendEmail(sendEmailEnabled) {
311
+ return (!onlyEmailExplicitlyEnabledRecipients && sendEmailEnabled !== false) || (onlyEmailExplicitlyEnabledRecipients && sendEmailEnabled === true);
312
+ }
313
+ // start with all box recipients
314
+ relevantBoxRecipientConfigs.forEach((x) => {
315
+ const { recipient } = x;
316
+ const { uid, e: overrideRecipientEmail, n: overrideRecipientName } = recipient;
317
+ const userDetails = uid ? userDetailsMap.get(uid) : undefined;
318
+ const otherRecipientForUser = uid ? otherRecipientConfigs.get(uid) : undefined;
319
+ const sendEmailEnabled = x.effectiveTemplateConfig?.se;
320
+ const shouldSendEmail = checkShouldSendEmail(sendEmailEnabled);
321
+ if (shouldSendEmail && !emailUidsSet.has(uid ?? '')) {
322
+ const e = overrideRecipientEmail ?? userDetails?.email; // use override email or the default email
323
+ if (e) {
324
+ const n = overrideRecipientName ?? userDetails?.displayName;
325
+ const emailAddress = e.toLowerCase();
326
+ explicitOtherRecipientEmailAddresses.delete(emailAddress); // don't double-send to the same email
327
+ const emailRecipient = {
328
+ emailAddress,
329
+ name: n,
330
+ boxRecipient: x,
331
+ otherRecipient: otherRecipientForUser
332
+ };
333
+ emails.push(emailRecipient);
334
+ if (uid) {
335
+ emailUidsSet.add(uid);
336
+ }
337
+ }
338
+ }
339
+ });
340
+ otherRecipientConfigs.forEach((x, uid) => {
341
+ // add users who existing in the system at this step, then other recipients in the next step
342
+ const userDetails = userDetailsMap.get(uid);
343
+ if (userDetails) {
344
+ const { email: userEmailAddress, displayName } = userDetails;
345
+ const sendEmailEnabled = x.se;
346
+ const shouldSendEmail = checkShouldSendEmail(sendEmailEnabled);
347
+ if (userEmailAddress && shouldSendEmail && !emailUidsSet.has(uid)) {
348
+ const emailAddress = userEmailAddress.toLowerCase();
349
+ const name = displayName || x.n;
350
+ const emailRecipient = {
351
+ emailAddress,
352
+ name,
353
+ otherRecipient: x
354
+ };
355
+ emails.push(emailRecipient);
356
+ emailUidsSet.add(uid);
357
+ explicitOtherRecipientEmailAddresses.delete(emailAddress);
358
+ }
359
+ }
360
+ });
361
+ explicitOtherRecipientEmailAddresses.forEach((x, emailAddress) => {
362
+ const sendEmailEnabled = x.se;
363
+ const shouldSendEmail = checkShouldSendEmail(sendEmailEnabled);
364
+ if (shouldSendEmail) {
365
+ const emailRecipient = {
366
+ emailAddress: emailAddress,
367
+ name: x.n,
368
+ otherRecipient: x
369
+ };
370
+ emails.push(emailRecipient);
371
+ }
372
+ });
373
+ // make all text recipients
374
+ // text recipients should be explicitly enabled, or marked true
375
+ const texts = [];
376
+ const textUidsSet = new Set();
377
+ function checkShouldSendText(sendTextEnabled) {
378
+ return (onlyTextExplicitlyEnabledRecipients && sendTextEnabled === true) || (!onlyTextExplicitlyEnabledRecipients && sendTextEnabled !== false);
379
+ }
380
+ relevantBoxRecipientConfigs.forEach((x) => {
381
+ const { recipient } = x;
382
+ const { uid } = recipient;
383
+ const userDetails = uid ? userDetailsMap.get(uid) : undefined;
384
+ const otherRecipientForUser = uid ? otherRecipientConfigs.get(uid) : undefined;
385
+ // only send a text if explicitly enabled
386
+ const sendTextEnabled = x.effectiveTemplateConfig?.st;
387
+ const shouldSendText = checkShouldSendText(sendTextEnabled);
388
+ if (shouldSendText && !textUidsSet.has(uid ?? '')) {
389
+ const t = x.recipient.t ?? userDetails?.phoneNumber; // use override phoneNumber or the default phone
390
+ if (t) {
391
+ const name = userDetails?.displayName ?? x.recipient.n;
392
+ const phoneNumber = t;
393
+ explicitOtherRecipientTextNumbers.delete(phoneNumber); // don't double-send to the same text phone number
394
+ const textRecipient = {
395
+ phoneNumber,
396
+ name,
397
+ boxRecipient: x,
398
+ otherRecipient: otherRecipientForUser
399
+ };
400
+ texts.push(textRecipient);
401
+ if (uid) {
402
+ textUidsSet.add(uid);
403
+ }
404
+ }
405
+ }
406
+ });
407
+ otherRecipientConfigs.forEach((x, uid) => {
408
+ // add users who existing in the system at this step, then other recipients in the next step
409
+ const userDetails = userDetailsMap.get(uid);
410
+ if (userDetails) {
411
+ const { phoneNumber, displayName } = userDetails;
412
+ const sendTextEnabled = x.st;
413
+ const sendText = checkShouldSendText(sendTextEnabled);
414
+ if (phoneNumber != null && sendText && !textUidsSet.has(uid)) {
415
+ const name = displayName || x.n;
416
+ const textRecipient = {
417
+ phoneNumber: phoneNumber,
418
+ name,
419
+ otherRecipient: x
420
+ };
421
+ texts.push(textRecipient);
422
+ textUidsSet.add(uid);
423
+ explicitOtherRecipientTextNumbers.delete(phoneNumber); // don't double-send to the same text phone number
424
+ }
425
+ }
426
+ });
427
+ explicitOtherRecipientTextNumbers.forEach((x, t) => {
428
+ const sendTextEnabled = x.st;
429
+ const shouldSendText = checkShouldSendText(sendTextEnabled);
430
+ if (shouldSendText) {
431
+ const textRecipient = {
432
+ phoneNumber: t,
433
+ name: x.n,
434
+ otherRecipient: x
435
+ };
436
+ texts.push(textRecipient);
437
+ }
438
+ });
439
+ // TODO: Add push notification details...
440
+ // make all notification summary recipients
441
+ const notificationSummaries = [];
442
+ const notificationSummaryKeysSet = new Set();
443
+ const notificationSummaryUidsSet = new Set();
444
+ function checkShouldSendNotificationSummary(sendNotificationSummaryEnabled) {
445
+ return (!onlySendNotificationSummaryExplicitlyEnabledRecipients && sendNotificationSummaryEnabled !== false) || (onlySendNotificationSummaryExplicitlyEnabledRecipients && sendNotificationSummaryEnabled === true);
446
+ }
447
+ relevantBoxRecipientConfigs.forEach((x) => {
448
+ const { recipient } = x;
449
+ const { uid } = recipient;
450
+ const userDetails = uid ? userDetailsMap.get(uid) : undefined;
451
+ const otherRecipientForUser = uid ? otherRecipientConfigs.get(uid) : undefined;
452
+ const sendNotificationSummaryEnabled = x.effectiveTemplateConfig?.sn;
453
+ const shouldSendNotificationSummary = checkShouldSendNotificationSummary(sendNotificationSummaryEnabled);
454
+ if (shouldSendNotificationSummary) {
455
+ let notificationSummaryId;
456
+ if (uid) {
457
+ // only use the uid (and ignore recipient config) if uid is defined
458
+ notificationSummaryId = notificationSummaryIdForUid(uid);
459
+ notificationSummaryUidsSet.add(uid);
460
+ }
461
+ else if (x.recipient.s) {
462
+ notificationSummaryId = x.recipient.s;
463
+ }
464
+ if (notificationSummaryId) {
465
+ const name = userDetails?.displayName ?? x.recipient.n;
466
+ notificationSummaries.push({
467
+ notificationSummaryId,
468
+ boxRecipient: x,
469
+ otherRecipient: otherRecipientForUser,
470
+ name
471
+ });
472
+ explicitOtherRecipientNotificationSummaryIds.delete(notificationSummaryId); // don't double send
473
+ }
474
+ }
475
+ });
476
+ otherRecipientConfigs.forEach((x, uid) => {
477
+ const userDetails = userDetailsMap.get(uid);
478
+ if (userDetails) {
479
+ const { displayName } = userDetails;
480
+ const sendNotificationSummaryEnabled = x.sn;
481
+ const shouldSendNotificationSummary = checkShouldSendNotificationSummary(sendNotificationSummaryEnabled);
482
+ if (shouldSendNotificationSummary && !notificationSummaryUidsSet.has(uid ?? '')) {
483
+ let notificationSummaryId;
484
+ if (uid) {
485
+ notificationSummaryId = notificationSummaryIdForUid(uid);
486
+ notificationSummaryUidsSet.add(uid);
487
+ }
488
+ else if (x.s) {
489
+ notificationSummaryId = x.s;
490
+ }
491
+ if (notificationSummaryId) {
492
+ if (!notificationSummaryKeysSet.has(notificationSummaryId)) {
493
+ const name = displayName || x.n;
494
+ const notificationSummary = {
495
+ notificationSummaryId,
496
+ otherRecipient: x,
497
+ name
498
+ };
499
+ notificationSummaries.push(notificationSummary);
500
+ explicitOtherRecipientNotificationSummaryIds.delete(notificationSummaryId);
501
+ }
502
+ }
503
+ }
504
+ }
505
+ });
506
+ explicitOtherRecipientNotificationSummaryIds.forEach((x, notificationSummaryId) => {
507
+ const sendNotificationSummaryEnabled = x.sn;
508
+ const shouldSendNotificationSummary = checkShouldSendNotificationSummary(sendNotificationSummaryEnabled);
509
+ if (shouldSendNotificationSummary) {
510
+ const notificationSummary = {
511
+ notificationSummaryId,
512
+ otherRecipient: x,
513
+ name: x.n
514
+ };
515
+ notificationSummaries.push(notificationSummary);
516
+ }
517
+ });
518
+ // results
519
+ const result = {
520
+ _internal,
521
+ emails,
522
+ texts,
523
+ notificationSummaries
524
+ };
525
+ return result;
526
+ }
527
+ function updateNotificationUserNotificationBoxRecipientConfig(input) {
528
+ const { notificationBoxId, notificationUserId, notificationUser, insertingRecipientIntoNotificationBox, removeRecipientFromNotificationBox, notificationBoxRecipient } = input;
529
+ const currentNotificationUserBoxIndex = notificationUser.bc.findIndex((x) => x.nb === notificationBoxId);
530
+ const currentNotificationUserBoxIndexExists = currentNotificationUserBoxIndex !== -1;
531
+ const currentNotificationUserBoxGlobalConfig = notificationUser.gc;
532
+ const currentNotificationUserBoxConfig = notificationUser.bc[currentNotificationUserBoxIndex] ?? {};
533
+ /**
534
+ * If bc is updated then the user should be updated too
535
+ */
536
+ let updatedBc;
537
+ let updatedNotificationBoxRecipient;
538
+ if (removeRecipientFromNotificationBox) {
539
+ // flag as removed in the NotificationUser details if not already flagged as such
540
+ if (currentNotificationUserBoxIndexExists && currentNotificationUserBoxConfig.rm !== true) {
541
+ updatedBc = [...notificationUser.bc];
542
+ updatedBc[currentNotificationUserBoxIndex] = {
543
+ ...currentNotificationUserBoxConfig,
544
+ nb: notificationBoxId, // set the NotificationBox id
545
+ c: currentNotificationUserBoxConfig.c ?? {},
546
+ i: util.UNSET_INDEX_NUMBER, // index should be cleared and set to -1
547
+ ns: false, // sync'd
548
+ rm: true
549
+ };
550
+ }
551
+ }
552
+ else if (notificationBoxRecipient != null) {
553
+ const { ns: currentConfigNeedsSync, lk: lockedFromChanges, bk: blockedFromAdd } = {
554
+ ns: currentNotificationUserBoxConfig.ns,
555
+ lk: currentNotificationUserBoxGlobalConfig.lk ?? currentNotificationUserBoxConfig.lk,
556
+ bk: currentNotificationUserBoxGlobalConfig.bk ?? currentNotificationUserBoxConfig.bk
557
+ };
558
+ // if we're re-inserting, then take the prevous config and restore as it was and remove the rm tag
559
+ let updateWithNotificationBoxRecipient;
560
+ if (insertingRecipientIntoNotificationBox) {
561
+ // does not exist in the NotificationBox currently
562
+ if (blockedFromAdd) {
563
+ throw notificationUserBlockedFromBeingAddedToRecipientsError(notificationUserId);
564
+ }
565
+ else if (lockedFromChanges) {
566
+ // ignored the notificationBoxRecipient's updates
567
+ updateWithNotificationBoxRecipient = currentNotificationUserBoxConfig;
568
+ }
569
+ else {
570
+ updateWithNotificationBoxRecipient = firebase.mergeNotificationBoxRecipients(notificationBoxRecipient, currentNotificationUserBoxConfig);
571
+ }
572
+ }
573
+ else {
574
+ // if locked from changes, throw error
575
+ if (lockedFromChanges) {
576
+ throw notificationUserLockedConfigFromBeingUpdatedError(notificationUserId);
577
+ }
578
+ else if (currentConfigNeedsSync) {
579
+ // if needs sync, then merge changes from the config into the notificationBoxRecipient
580
+ updateWithNotificationBoxRecipient = firebase.mergeNotificationBoxRecipients(notificationBoxRecipient, currentNotificationUserBoxConfig);
581
+ }
582
+ else {
583
+ // use as-is
584
+ updateWithNotificationBoxRecipient = notificationBoxRecipient;
585
+ }
586
+ }
587
+ const updatedNotificationUserBoxEntry = firebase.mergeNotificationUserNotificationBoxRecipientConfigs({
588
+ ...currentNotificationUserBoxConfig,
589
+ i: notificationBoxRecipient.i,
590
+ c: currentNotificationUserBoxConfig.c ?? {},
591
+ nb: notificationBoxId, // set the NotificationBox id
592
+ rm: false // remove/clear the removed flag
593
+ }, updateWithNotificationBoxRecipient);
594
+ updatedBc = [...notificationUser.bc];
595
+ if (currentNotificationUserBoxIndexExists) {
596
+ updatedBc[currentNotificationUserBoxIndex] = updatedNotificationUserBoxEntry;
597
+ }
598
+ else {
599
+ updatedBc.push(updatedNotificationUserBoxEntry);
600
+ }
601
+ // re-apply exclusions to the updated config(s)
602
+ const withExclusions = firebase.applyExclusionsToNotificationUserNotificationBoxRecipientConfigs({
603
+ notificationUser,
604
+ bc: updatedBc,
605
+ recalculateNs: false
606
+ });
607
+ updatedBc = withExclusions.bc;
608
+ // sync index with input NotificationBoxRecipient
609
+ updatedNotificationUserBoxEntry.i = notificationBoxRecipient.i;
610
+ updatedNotificationBoxRecipient = updatedNotificationUserBoxEntry;
611
+ }
612
+ return {
613
+ updatedBc,
614
+ updatedNotificationBoxRecipient
615
+ };
616
+ }
617
+
618
+ /**
619
+ * Removes the completed checkpoints from the inputCompletions array based on the handleTaskResult.
620
+ *
621
+ * @param inputCompletions
622
+ * @param handleTaskResult
623
+ * @returns
624
+ */
625
+ function removeFromCompletionsArrayWithTaskResult(inputCompletions, handleTaskResult) {
626
+ const { removeAllCompletedCheckpoints, removeFromCompletedCheckpoints } = handleTaskResult;
627
+ let result;
628
+ if (removeAllCompletedCheckpoints) {
629
+ result = [];
630
+ }
631
+ else if (removeFromCompletedCheckpoints != null) {
632
+ const removeFromCompletionsSet = new Set(util.asArray(removeFromCompletedCheckpoints));
633
+ result = inputCompletions.filter((x) => !removeFromCompletionsSet.has(x));
634
+ }
635
+ else {
636
+ result = inputCompletions;
637
+ }
638
+ return result;
639
+ }
640
+
641
+ /**
642
+ * Injection token for the BaseNotificationServerActionsContext
643
+ */
644
+ const BASE_NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN = 'BASE_NOTIFICATION_SERVER_ACTION_CONTEXT';
645
+ /**
646
+ * Injection token for the NotificationServerActionsContext
647
+ */
648
+ const NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN = 'NOTIFICATION_SERVER_ACTION_CONTEXT';
649
+ class NotificationServerActions {
650
+ }
651
+ function notificationServerActions(context) {
652
+ return {
653
+ createNotificationUser: createNotificationUserFactory(context),
654
+ updateNotificationUser: updateNotificationUserFactory(context),
655
+ resyncNotificationUser: resyncNotificationUserFactory(context),
656
+ resyncAllNotificationUsers: resyncAllNotificationUsersFactory(context),
657
+ createNotificationSummary: createNotificationSummaryFactory(context),
658
+ updateNotificationSummary: updateNotificationSummaryFactory(context),
659
+ createNotificationBox: createNotificationBoxFactory(context),
660
+ updateNotificationBox: updateNotificationBoxFactory(context),
661
+ updateNotificationBoxRecipient: updateNotificationBoxRecipientFactory(context),
662
+ sendNotification: sendNotificationFactory(context),
663
+ sendQueuedNotifications: sendQueuedNotificationsFactory(context),
664
+ cleanupSentNotifications: cleanupSentNotificationsFactory(context)
665
+ };
666
+ }
667
+ // MARK: Actions
668
+ function createNotificationUserFactory(context) {
669
+ const { firebaseServerActionTransformFunctionFactory, notificationUserCollection, authService } = context;
670
+ return firebaseServerActionTransformFunctionFactory(firebase.CreateNotificationUserParams, async (params) => {
671
+ const { uid } = params;
672
+ return async () => {
673
+ // assert they exist in the auth system
674
+ const userContext = authService.userContext(uid);
675
+ const userExistsInAuth = await userContext.exists();
676
+ if (!userExistsInAuth) {
677
+ throw notificationUserInvalidUidForCreateError(uid);
678
+ }
679
+ const notificationUserDocument = notificationUserCollection.documentAccessor().loadDocumentForId(uid);
680
+ const newUserTemplate = {
681
+ uid,
682
+ x: [],
683
+ bc: [],
684
+ b: [],
685
+ dc: {
686
+ c: {}
687
+ },
688
+ gc: {
689
+ c: {}
690
+ }
691
+ };
692
+ await notificationUserDocument.create(newUserTemplate);
693
+ return notificationUserDocument;
694
+ };
695
+ });
696
+ }
697
+ function updateNotificationUserFactory(context) {
698
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, notificationUserCollection, appNotificationTemplateTypeInfoRecordService } = context;
699
+ return firebaseServerActionTransformFunctionFactory(firebase.UpdateNotificationUserParams, async (params) => {
700
+ const { gc: inputGc, dc: inputDc, bc: inputBc } = params;
701
+ return async (notificationUserDocument) => {
702
+ await firestoreContext.runTransaction(async (transaction) => {
703
+ const notificationUserDocumentInTransaction = notificationUserCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(notificationUserDocument);
704
+ const notificationUser = await firebaseServer.assertSnapshotData(notificationUserDocumentInTransaction);
705
+ const updateTemplate = {};
706
+ const allKnownNotificationTypes = appNotificationTemplateTypeInfoRecordService.getAllKnownTemplateTypes();
707
+ if (inputDc != null) {
708
+ updateTemplate.dc = firebase.updateNotificationUserDefaultNotificationBoxRecipientConfig(notificationUser.dc, inputDc, allKnownNotificationTypes);
709
+ }
710
+ if (inputGc != null) {
711
+ const nextGc = firebase.updateNotificationUserDefaultNotificationBoxRecipientConfig(notificationUser.gc, inputGc, allKnownNotificationTypes);
712
+ if (!util.areEqualPOJOValues(notificationUser.gc, nextGc)) {
713
+ updateTemplate.gc = nextGc;
714
+ // iterate and update any box config that has the effective recipient change
715
+ updateTemplate.bc = notificationUser.bc.map((currentConfig) => {
716
+ // check item isn't already marked for sync or marked as removed
717
+ if (currentConfig.ns === true || currentConfig.rm === true) {
718
+ return currentConfig;
719
+ }
720
+ const currentEffectiveRecipient = firebase.effectiveNotificationBoxRecipientConfig({
721
+ uid: notificationUser.uid,
722
+ appNotificationTemplateTypeInfoRecordService,
723
+ gc: notificationUser.gc,
724
+ boxConfig: currentConfig
725
+ });
726
+ const nextEffectiveRecipient = firebase.effectiveNotificationBoxRecipientConfig({
727
+ uid: notificationUser.uid,
728
+ appNotificationTemplateTypeInfoRecordService,
729
+ gc: nextGc,
730
+ boxConfig: currentConfig
731
+ });
732
+ const effectiveConfigChanged = !util.areEqualPOJOValues(currentEffectiveRecipient, nextEffectiveRecipient);
733
+ return effectiveConfigChanged ? { ...currentConfig, ns: true } : currentConfig;
734
+ });
735
+ }
736
+ }
737
+ if (inputBc != null) {
738
+ const updateTemplateBc = firebase.updateNotificationUserNotificationBoxRecipientConfigs(updateTemplate.bc ?? notificationUser.bc, inputBc, appNotificationTemplateTypeInfoRecordService);
739
+ if (updateTemplateBc != null) {
740
+ // re-apply exclusions to the updated configs
741
+ const withExclusions = firebase.applyExclusionsToNotificationUserNotificationBoxRecipientConfigs({
742
+ notificationUser,
743
+ bc: updateTemplateBc,
744
+ recalculateNs: false
745
+ });
746
+ updateTemplate.bc = withExclusions.bc;
747
+ updateTemplate.b = updateTemplateBc.map((x) => x.nb);
748
+ }
749
+ }
750
+ // if bc is being updated, then also update ns
751
+ if (updateTemplate.bc != null) {
752
+ updateTemplate.ns = firebase.calculateNsForNotificationUserNotificationBoxRecipientConfigs(updateTemplate.bc);
753
+ }
754
+ await notificationUserDocumentInTransaction.update(updateTemplate);
755
+ });
756
+ return notificationUserDocument;
757
+ };
758
+ });
759
+ }
760
+ const MAX_NOTIFICATION_BOXES_TO_UPDATE_PER_BATCH = 50;
761
+ function resyncNotificationUserFactory(context) {
762
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, notificationBoxCollection, notificationUserCollection, appNotificationTemplateTypeInfoRecordService } = context;
763
+ return firebaseServerActionTransformFunctionFactory(firebase.ResyncNotificationUserParams, async () => {
764
+ return async (notificationUserDocument) => {
765
+ // run updates in batches
766
+ let notificationBoxesUpdated = 0;
767
+ let hasMoreNotificationBoxesToSync = true;
768
+ while (hasMoreNotificationBoxesToSync) {
769
+ const batchResult = await firestoreContext.runTransaction(async (transaction) => {
770
+ const notificationUserDocumentInTransaction = notificationUserCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(notificationUserDocument);
771
+ const notificationUser = await firebaseServer.assertSnapshotData(notificationUserDocumentInTransaction);
772
+ const { gc } = notificationUser;
773
+ const notificationBoxConfigsToSync = notificationUser.bc.filter((x) => x.ns);
774
+ const notificationBoxConfigsToSyncInThisBatch = util.takeFront(notificationBoxConfigsToSync, MAX_NOTIFICATION_BOXES_TO_UPDATE_PER_BATCH);
775
+ /**
776
+ * These are the actual number of NotificationBox values that had recipients updated.
777
+ */
778
+ let notificationBoxesUpdatedInBatch = 0;
779
+ let hasUnsyncedNotificationBoxConfigs = false;
780
+ if (notificationBoxConfigsToSyncInThisBatch.length > 0) {
781
+ const notificationBoxConfigsToSyncInThisBatchMap = util.makeModelMap(notificationBoxConfigsToSyncInThisBatch, (x) => x.nb);
782
+ const notificationBoxIdsToSyncInThisBatch = Array.from(notificationBoxConfigsToSyncInThisBatchMap.keys());
783
+ const notificationBoxDocuments = firebase.loadDocumentsForIds(notificationBoxCollection.documentAccessorForTransaction(transaction), notificationBoxIdsToSyncInThisBatch);
784
+ const notificationBoxDocumentSnapshotDataPairs = await firebase.getDocumentSnapshotDataPairs(notificationBoxDocuments);
785
+ const notificationBoxConfigsToRemoveFromNotificationUser = new Set();
786
+ const notificationUserNotificationBoxConfigsToMarkAsRemoved = new Set();
787
+ const nextRecipientsMap = new Map();
788
+ // update each NotificationBoxDocument
789
+ await util.performAsyncTasks(notificationBoxDocumentSnapshotDataPairs, async (notificationBoxDocumentSnapshotDataPair) => {
790
+ const { data: notificationBox, document } = notificationBoxDocumentSnapshotDataPair;
791
+ const nb = document.id;
792
+ const notificationUserNotificationBoxConfig = notificationBoxConfigsToSyncInThisBatchMap.get(nb); // always exists
793
+ if (!notificationBox) {
794
+ // if the entire NotificationBox no longer exists, flag to remove it from the user as a cleanup measure
795
+ notificationBoxConfigsToRemoveFromNotificationUser.add(nb);
796
+ }
797
+ else {
798
+ // update in the NotificationBox
799
+ const recipientIndex = notificationBox.r.findIndex((x) => x.uid === notificationUser.uid);
800
+ let r;
801
+ if (recipientIndex === -1) {
802
+ // if they are not in the NotificationBox, then mark them as removed on the user
803
+ notificationUserNotificationBoxConfigsToMarkAsRemoved.add(nb);
804
+ }
805
+ else if (notificationUserNotificationBoxConfig.rm) {
806
+ // remove from the notification box if it is flagged
807
+ r = util.removeValuesAtIndexesFromArrayCopy(notificationBox.r, recipientIndex);
808
+ }
809
+ else {
810
+ const { m } = notificationBox;
811
+ const recipient = notificationBox.r[recipientIndex];
812
+ const nextRecipient = firebase.effectiveNotificationBoxRecipientConfig({
813
+ uid: notificationUser.uid,
814
+ m,
815
+ appNotificationTemplateTypeInfoRecordService,
816
+ gc,
817
+ boxConfig: notificationUserNotificationBoxConfig,
818
+ recipient
819
+ });
820
+ const recipientHasChange = !util.areEqualPOJOValues(nextRecipient, recipient);
821
+ // only update recipients if the next/new recipient is not equal to the existing one
822
+ if (recipientHasChange) {
823
+ r = [...notificationBox.r];
824
+ r[recipientIndex] = nextRecipient;
825
+ nextRecipientsMap.set(nb, nextRecipient);
826
+ }
827
+ else {
828
+ nextRecipientsMap.set(nb, recipient);
829
+ }
830
+ }
831
+ // update recipients if needed
832
+ if (r != null) {
833
+ await document.update({ r });
834
+ notificationBoxesUpdatedInBatch += 1;
835
+ }
836
+ }
837
+ });
838
+ // Update the NotificationUser
839
+ const notificationBoxIdsSynced = new Set(notificationBoxIdsToSyncInThisBatch);
840
+ // start nextConfigs off as a new array with none of the sync'd ids
841
+ const nextConfigs = notificationBoxConfigsToSyncInThisBatch.filter((x) => !notificationBoxIdsSynced.has(x.nb));
842
+ notificationBoxIdsToSyncInThisBatch.forEach((nb) => {
843
+ let nextConfig;
844
+ if (notificationBoxConfigsToRemoveFromNotificationUser.has(nb)) ;
845
+ else {
846
+ const existingConfig = notificationBoxConfigsToSyncInThisBatchMap.get(nb);
847
+ if (notificationUserNotificationBoxConfigsToMarkAsRemoved.has(nb) || existingConfig.rm) {
848
+ // if the recipient was being removed or is marked as removed, then update the config to confirm removal
849
+ nextConfig = {
850
+ ...existingConfig,
851
+ nb,
852
+ rm: true,
853
+ i: util.UNSET_INDEX_NUMBER
854
+ };
855
+ }
856
+ else {
857
+ // else, use the updated recipient and keep/copy the
858
+ const updatedRecipient = nextRecipientsMap.get(nb);
859
+ nextConfig = {
860
+ ...existingConfig,
861
+ nb,
862
+ rm: false, // mark as not removed
863
+ i: updatedRecipient.i ?? util.UNSET_INDEX_NUMBER
864
+ };
865
+ }
866
+ }
867
+ if (nextConfig != null) {
868
+ nextConfig.ns = false; // mark as synced
869
+ nextConfigs.push(nextConfig);
870
+ }
871
+ });
872
+ const ns = nextConfigs.some((x) => x.ns);
873
+ await notificationUserDocumentInTransaction.update({ bc: nextConfigs, ns });
874
+ hasUnsyncedNotificationBoxConfigs = ns;
875
+ }
876
+ const batchResult = {
877
+ hasMoreNotificationBoxesToSync: hasUnsyncedNotificationBoxConfigs,
878
+ notificationBoxesUpdatedInBatch
879
+ };
880
+ return batchResult;
881
+ });
882
+ hasMoreNotificationBoxesToSync = batchResult.hasMoreNotificationBoxesToSync;
883
+ notificationBoxesUpdated += batchResult.notificationBoxesUpdatedInBatch;
884
+ }
885
+ const result = {
886
+ notificationBoxesUpdated
887
+ };
888
+ return result;
889
+ };
890
+ });
891
+ }
892
+ function resyncAllNotificationUsersFactory(context) {
893
+ const { notificationUserCollection } = context;
894
+ const resyncNotificationUser = resyncNotificationUserFactory(context);
895
+ return async () => {
896
+ let notificationBoxesUpdated = 0;
897
+ const resyncNotificationUserParams = { key: firebase.firestoreDummyKey() };
898
+ const resyncNotificationUserInstance = await resyncNotificationUser(resyncNotificationUserParams);
899
+ const iterateResult = await firebase.iterateFirestoreDocumentSnapshotPairs({
900
+ documentAccessor: notificationUserCollection.documentAccessor(),
901
+ iterateSnapshotPair: async (snapshotPair) => {
902
+ const { document: notificationUserDocument } = snapshotPair;
903
+ const result = await resyncNotificationUserInstance(notificationUserDocument);
904
+ notificationBoxesUpdated += result.notificationBoxesUpdated;
905
+ },
906
+ constraintsFactory: () => firebase.notificationUsersFlaggedForNeedsSyncQuery(),
907
+ snapshotsPerformTasksConfig: {
908
+ // prevent NotificationUsers with the same NotificationBoxes from being updated/sync'd at the same time
909
+ nonConcurrentTaskKeyFactory: (x) => {
910
+ const notificationBoxIdsToSync = x
911
+ .data()
912
+ .bc.filter((x) => x.ns)
913
+ .map((x) => x.nb);
914
+ return notificationBoxIdsToSync;
915
+ }
916
+ },
917
+ queryFactory: notificationUserCollection,
918
+ batchSize: undefined,
919
+ performTasksConfig: {
920
+ maxParallelTasks: 10
921
+ }
922
+ });
923
+ const result = {
924
+ notificationUsersResynced: iterateResult.totalSnapshotsVisited,
925
+ notificationBoxesUpdated
926
+ };
927
+ return result;
928
+ };
929
+ }
930
+ function createNotificationSummaryFactory(context) {
931
+ const { firebaseServerActionTransformFunctionFactory, notificationSummaryCollection } = context;
932
+ return firebaseServerActionTransformFunctionFactory(firebase.CreateNotificationSummaryParams, async (params) => {
933
+ const { model } = params;
934
+ return async () => {
935
+ const notificationSummaryId = firebase.notificationSummaryIdForModel(model);
936
+ const notificationSummaryDocument = notificationSummaryCollection.documentAccessor().loadDocumentForId(notificationSummaryId);
937
+ const newSummaryTemplate = makeNewNotificationSummaryTemplate(model);
938
+ await notificationSummaryDocument.create(newSummaryTemplate);
939
+ return notificationSummaryDocument;
940
+ };
941
+ });
942
+ }
943
+ function updateNotificationSummaryFactory(context) {
944
+ const { firebaseServerActionTransformFunctionFactory } = context;
945
+ return firebaseServerActionTransformFunctionFactory(firebase.UpdateNotificationSummaryParams, async (params) => {
946
+ const { setReadAtTime, flagAllRead } = params;
947
+ return async (notificationSummaryDocument) => {
948
+ let updateTemplate;
949
+ if (setReadAtTime != null) {
950
+ updateTemplate = { rat: setReadAtTime };
951
+ }
952
+ else if (flagAllRead === true) {
953
+ updateTemplate = { rat: new Date() };
954
+ }
955
+ if (updateTemplate != null) {
956
+ await notificationSummaryDocument.update(updateTemplate);
957
+ }
958
+ return notificationSummaryDocument;
959
+ };
960
+ });
961
+ }
962
+ function createNotificationBoxInTransactionFactory(context) {
963
+ const { notificationBoxCollection } = context;
964
+ return async (params, transaction) => {
965
+ const { now: inputNow, skipCreate } = params;
966
+ const now = inputNow ?? new Date();
967
+ const notificationBoxDocument = firebase.loadNotificationBoxDocumentForReferencePair(params, notificationBoxCollection.documentAccessorForTransaction(transaction));
968
+ const notificationBoxTemplate = {
969
+ m: notificationBoxDocument.notificationBoxRelatedModelKey,
970
+ o: firebase.firestoreDummyKey(), // set during initialization
971
+ r: [],
972
+ cat: now,
973
+ w: date.yearWeekCode(now),
974
+ s: true // requires initialization
975
+ };
976
+ if (!skipCreate) {
977
+ await notificationBoxDocument.create(notificationBoxTemplate);
978
+ }
979
+ return {
980
+ notificationBoxTemplate,
981
+ notificationBoxDocument
982
+ };
983
+ };
984
+ }
985
+ function createNotificationBoxFactory(context) {
986
+ const { firestoreContext, notificationBoxCollection, firebaseServerActionTransformFunctionFactory } = context;
987
+ const createNotificationBoxInTransaction = createNotificationBoxInTransactionFactory(context);
988
+ return firebaseServerActionTransformFunctionFactory(firebase.CreateNotificationBoxParams, async (params) => {
989
+ const { model } = params;
990
+ return async () => {
991
+ const result = await firestoreContext.runTransaction(async (transaction) => {
992
+ const { notificationBoxDocument } = await createNotificationBoxInTransaction({ notificationBoxRelatedModelKey: model }, transaction);
993
+ return notificationBoxDocument;
994
+ });
995
+ return notificationBoxCollection.documentAccessor().loadDocumentFrom(result);
996
+ };
997
+ });
998
+ }
999
+ function updateNotificationBoxFactory({ firebaseServerActionTransformFunctionFactory }) {
1000
+ return firebaseServerActionTransformFunctionFactory(firebase.UpdateNotificationBoxParams, async () => {
1001
+ return async (notificationBoxDocument) => {
1002
+ // does nothing currently.
1003
+ return notificationBoxDocument;
1004
+ };
1005
+ });
1006
+ }
1007
+ function updateNotificationBoxRecipientExclusionInTransactionFactory(context) {
1008
+ const { notificationBoxCollection, notificationUserCollection } = context;
1009
+ return async (input, transaction) => {
1010
+ const { params } = input;
1011
+ const { uid: inputUid, i, setExclusion } = params;
1012
+ const notificationBoxDocument = firebase.loadNotificationBoxDocumentForReferencePair(input, notificationBoxCollection.documentAccessorForTransaction(transaction));
1013
+ let targetUid = inputUid;
1014
+ let result = undefined;
1015
+ if (setExclusion == null) {
1016
+ throw new Error('setExclusion was undefined. Maybe you wanted to call updateNotificationBoxRecipientInTransactionFactory() instead?');
1017
+ }
1018
+ else if (!inputUid && i != null) {
1019
+ // only load the notification box if targeting a recipient by index
1020
+ const notificationBox = await notificationBoxDocument.snapshotData();
1021
+ if (!notificationBox) {
1022
+ throw notificationBoxExclusionTargetInvalidError();
1023
+ }
1024
+ const targetRecipient = notificationBox.r.find((x) => x.i === i);
1025
+ if (!targetRecipient || !targetRecipient.uid) {
1026
+ throw notificationBoxExclusionTargetInvalidError();
1027
+ }
1028
+ else {
1029
+ targetUid = targetRecipient.uid;
1030
+ }
1031
+ }
1032
+ if (!targetUid) {
1033
+ throw notificationBoxExclusionTargetInvalidError();
1034
+ }
1035
+ const notificationUserDocument = await notificationUserCollection.documentAccessorForTransaction(transaction).loadDocumentForId(targetUid);
1036
+ const notificationUser = await notificationUserDocument.snapshotData();
1037
+ if (notificationUser) {
1038
+ // only update if the user exists
1039
+ const targetExclusions = [notificationBoxDocument.id];
1040
+ const { update: notificationUserUpdate } = firebase.updateNotificationUserNotificationSendExclusions({
1041
+ notificationUser,
1042
+ addExclusions: setExclusion ? targetExclusions : undefined,
1043
+ removeExclusions: setExclusion ? undefined : targetExclusions
1044
+ });
1045
+ await notificationUserDocument.update(notificationUserUpdate);
1046
+ result = {
1047
+ notificationUserUpdate
1048
+ };
1049
+ }
1050
+ return result;
1051
+ };
1052
+ }
1053
+ function updateNotificationBoxRecipientInTransactionFactory(context) {
1054
+ const { authService, notificationBoxCollection, notificationUserCollection } = context;
1055
+ const createNotificationBoxInTransaction = createNotificationBoxInTransactionFactory(context);
1056
+ return async (input, transaction) => {
1057
+ const { params, allowCreateNotificationBoxIfItDoesNotExist, throwErrorIfNotificationBoxDoesNotExist } = input;
1058
+ const { uid, i, insert, remove, configs: inputC, setExclusion } = params;
1059
+ const findRecipientFn = (x) => (uid != null && x.uid === uid) || (i != null && x.i === i);
1060
+ if (setExclusion != null) {
1061
+ throw new Error('exclusion update must be processed by updateNotificationBoxRecipientExclusionInTransactionFactory() function.');
1062
+ }
1063
+ const notificationBoxDocument = firebase.loadNotificationBoxDocumentForReferencePair(input, notificationBoxCollection.documentAccessorForTransaction(transaction));
1064
+ let notificationBox = await notificationBoxDocument.snapshotData();
1065
+ let createNotificationBox = false;
1066
+ let result = undefined;
1067
+ if (!notificationBox) {
1068
+ if (allowCreateNotificationBoxIfItDoesNotExist) {
1069
+ const { notificationBoxTemplate } = await createNotificationBoxInTransaction({
1070
+ notificationBoxDocument,
1071
+ skipCreate: true // don't create since we still need to read things for the transaction
1072
+ }, transaction);
1073
+ notificationBox = notificationBoxTemplate;
1074
+ createNotificationBox = true;
1075
+ }
1076
+ else if (throwErrorIfNotificationBoxDoesNotExist) {
1077
+ throw notificationBoxDoesNotExist();
1078
+ }
1079
+ }
1080
+ if (notificationBox) {
1081
+ const { m } = notificationBox;
1082
+ let r;
1083
+ let targetRecipientIndex = notificationBox.r.findIndex(findRecipientFn);
1084
+ const targetRecipient = notificationBox.r[targetRecipientIndex];
1085
+ let nextRecipient;
1086
+ if (remove) {
1087
+ if (targetRecipientIndex != null) {
1088
+ r = [...notificationBox.r]; // remove if they exist.
1089
+ delete r[targetRecipientIndex];
1090
+ }
1091
+ }
1092
+ else {
1093
+ if (!targetRecipient && !insert) {
1094
+ throw notificationBoxRecipientDoesNotExistsError();
1095
+ }
1096
+ const c = (inputC != null ? firebase.notificationBoxRecipientTemplateConfigArrayToRecord(inputC) : targetRecipient?.c) ?? {};
1097
+ nextRecipient = {
1098
+ uid,
1099
+ i: targetRecipient?.i ?? util.UNSET_INDEX_NUMBER,
1100
+ c,
1101
+ ...firebase.updateNotificationRecipient(targetRecipient ?? {}, params)
1102
+ };
1103
+ r = [...notificationBox.r];
1104
+ if (targetRecipient) {
1105
+ nextRecipient.i = targetRecipient.i;
1106
+ nextRecipient = firebase.mergeNotificationBoxRecipients(targetRecipient, nextRecipient);
1107
+ r[targetRecipientIndex] = nextRecipient; // override in the array
1108
+ }
1109
+ else {
1110
+ const nextI = util.computeNextFreeIndexOnSortedValuesFunction(util.readIndexNumber)(notificationBox.r); // r is sorted by index in ascending order, so the last value is the largest i
1111
+ nextRecipient.i = nextI;
1112
+ // should have the greatest i value, push to end
1113
+ r.push(nextRecipient);
1114
+ targetRecipientIndex = r.length - 1;
1115
+ }
1116
+ }
1117
+ // save changes to r if it has changed
1118
+ if (r != null) {
1119
+ const notificationUserId = targetRecipient?.uid ?? nextRecipient?.uid;
1120
+ // sync with the notification user's document, if it exists
1121
+ if (notificationUserId != null) {
1122
+ const notificationBoxId = notificationBoxDocument.id;
1123
+ const notificationUserDocument = await notificationUserCollection.documentAccessorForTransaction(transaction).loadDocumentForId(notificationUserId);
1124
+ let notificationUser = await notificationUserDocument.snapshotData();
1125
+ const createNotificationUser = !notificationUser && !remove && insert;
1126
+ if (createNotificationUser) {
1127
+ // assert they exist in the auth system
1128
+ const userContext = authService.userContext(notificationUserId);
1129
+ const userExistsInAuth = await userContext.exists();
1130
+ if (!userExistsInAuth) {
1131
+ throw notificationUserInvalidUidForCreateError(notificationUserId);
1132
+ }
1133
+ const notificationUserTemplate = {
1134
+ uid: notificationUserId,
1135
+ b: [],
1136
+ x: [],
1137
+ bc: [],
1138
+ ns: false,
1139
+ dc: {
1140
+ c: {}
1141
+ },
1142
+ gc: {
1143
+ c: {}
1144
+ }
1145
+ };
1146
+ notificationUser = notificationUserTemplate;
1147
+ }
1148
+ // if the user is being inserted or exists, then make updates
1149
+ if (notificationUser != null) {
1150
+ const { updatedBc, updatedNotificationBoxRecipient } = updateNotificationUserNotificationBoxRecipientConfig({
1151
+ notificationBoxId,
1152
+ notificationUserId,
1153
+ notificationUser,
1154
+ insertingRecipientIntoNotificationBox: insert,
1155
+ removeRecipientFromNotificationBox: remove,
1156
+ notificationBoxRecipient: nextRecipient
1157
+ });
1158
+ const updatedB = updatedBc ? updatedBc.map((x) => x.nb) : undefined;
1159
+ if (createNotificationUser) {
1160
+ const newUserTemplate = {
1161
+ ...notificationUser,
1162
+ bc: updatedBc ?? [],
1163
+ b: updatedB ?? []
1164
+ };
1165
+ await notificationUserDocument.create(newUserTemplate);
1166
+ }
1167
+ else if (updatedBc != null) {
1168
+ await notificationUserDocument.update({ bc: updatedBc, b: updatedB });
1169
+ }
1170
+ // Set if nextRecipient is updated/influence from existing configuration
1171
+ if (targetRecipientIndex != null && updatedNotificationBoxRecipient && !remove) {
1172
+ r[targetRecipientIndex] = updatedNotificationBoxRecipient; // set the updated value in r
1173
+ }
1174
+ }
1175
+ // else, if removing and they don't exist, nothing to update
1176
+ }
1177
+ const updatedNotificationBox = { ...notificationBox, r };
1178
+ let notificationBoxWasCreated = false;
1179
+ if (createNotificationBox) {
1180
+ await notificationBoxDocument.create(updatedNotificationBox);
1181
+ notificationBoxWasCreated = true;
1182
+ }
1183
+ else {
1184
+ await notificationBoxDocument.update({ r });
1185
+ }
1186
+ result = {
1187
+ updatedNotificationBox,
1188
+ notificationBoxWasCreated,
1189
+ notificationBoxDocument
1190
+ };
1191
+ }
1192
+ }
1193
+ return result;
1194
+ };
1195
+ }
1196
+ function updateNotificationBoxRecipientFactory(context) {
1197
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory } = context;
1198
+ const updateNotificationBoxRecipientInTransaction = updateNotificationBoxRecipientInTransactionFactory(context);
1199
+ const updateNotificationBoxRecipientExclusionInTransaction = updateNotificationBoxRecipientExclusionInTransactionFactory(context);
1200
+ return firebaseServerActionTransformFunctionFactory(firebase.UpdateNotificationBoxRecipientParams, async (params) => {
1201
+ return async (notificationBoxDocument) => {
1202
+ await firestoreContext.runTransaction(async (transaction) => {
1203
+ if (params.setExclusion != null) {
1204
+ await updateNotificationBoxRecipientExclusionInTransaction({
1205
+ params,
1206
+ notificationBoxDocument
1207
+ }, transaction);
1208
+ }
1209
+ else {
1210
+ await updateNotificationBoxRecipientInTransaction({
1211
+ params,
1212
+ throwErrorIfNotificationBoxDoesNotExist: true,
1213
+ notificationBoxDocument
1214
+ }, transaction);
1215
+ }
1216
+ });
1217
+ return notificationBoxDocument;
1218
+ };
1219
+ });
1220
+ }
1221
+ const UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY = 8;
1222
+ const UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = 1;
1223
+ const UNKNOWN_NOTIFICATION_TASK_TYPE_HOURS_DELAY = 8;
1224
+ const UNKNOWN_NOTIFICATION_TASK_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = 1;
1225
+ const KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY = UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY;
1226
+ const KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = 5;
1227
+ const NOTIFICATION_MAX_SEND_ATTEMPTS = 5;
1228
+ const NOTIFICATION_BOX_NOT_INITIALIZED_DELAY_MINUTES = 8;
1229
+ /**
1230
+ * Minimum time in minutes that a notification task can be attempted again
1231
+ */
1232
+ const NOTIFICATION_TASK_MINIMUM_SET_AT_THROTTLE_TIME_MINUTES = 1;
1233
+ const NOTIFICATION_TASK_TYPE_MAX_SEND_ATTEMPTS = 5;
1234
+ const NOTIFICATION_TASK_TYPE_FAILURE_DELAY_HOURS = 3;
1235
+ const NOTIFICATION_TASK_TYPE_FAILURE_DELAY_MS = dateFns.hoursToMilliseconds(NOTIFICATION_TASK_TYPE_FAILURE_DELAY_HOURS);
1236
+ function sendNotificationFactory(context) {
1237
+ const { appNotificationTemplateTypeInfoRecordService, notificationSendService, notificationTaskService, notificationTemplateService, authService, notificationBoxCollection, notificationCollectionGroup, notificationUserCollection, firestoreContext, firebaseServerActionTransformFunctionFactory } = context;
1238
+ const createNotificationBoxInTransaction = createNotificationBoxInTransactionFactory(context);
1239
+ const notificationUserAccessor = notificationUserCollection.documentAccessor();
1240
+ return firebaseServerActionTransformFunctionFactory(firebase.SendNotificationParams, async (params) => {
1241
+ const { ignoreSendAtThrottle } = params;
1242
+ return async (inputNotificationDocument) => {
1243
+ const now = new Date();
1244
+ // Load the notification document outside of any potential context (transaction, etc.)
1245
+ const notificationDocument = notificationCollectionGroup.documentAccessor().loadDocumentFrom(inputNotificationDocument);
1246
+ const { nextSat, throttled, tryRun, isNotificationTask, notificationTaskHandler, notification, createdBox, notificationBoxNeedsInitialization, notificationBox, notificationBoxModelKey, deletedNotification, templateInstance, isConfiguredTemplateType, isKnownTemplateType, onlySendToExplicitlyEnabledRecipients, onlyTextExplicitlyEnabledRecipients } = await firestoreContext.runTransaction(async (transaction) => {
1247
+ const notificationBoxDocument = notificationBoxCollection.documentAccessorForTransaction(transaction).loadDocument(notificationDocument.parent);
1248
+ const notificationDocumentInTransaction = notificationCollectionGroup.documentAccessorForTransaction(transaction).loadDocumentFrom(notificationDocument);
1249
+ let [notificationBox, notification] = await Promise.all([firebase.getDocumentSnapshotData(notificationBoxDocument), firebase.getDocumentSnapshotData(notificationDocumentInTransaction)]);
1250
+ const model = firebase.inferKeyFromTwoWayFlatFirestoreModelKey(notificationBoxDocument.id);
1251
+ const isNotificationTask = notification?.st === firebase.NotificationSendType.TASK_NOTIFICATION;
1252
+ let tryRun = true;
1253
+ let throttled = false;
1254
+ let nextSat;
1255
+ if (!notification) {
1256
+ tryRun = false;
1257
+ }
1258
+ else if (!ignoreSendAtThrottle) {
1259
+ tryRun = !dateFns.isFuture(notification.sat);
1260
+ if (!tryRun) {
1261
+ throttled = true;
1262
+ }
1263
+ }
1264
+ // always set nextSat if tryRun is true
1265
+ if (tryRun) {
1266
+ if (isNotificationTask) {
1267
+ // can try to run the task again in 1 minute
1268
+ nextSat = dateFns.addMinutes(now, NOTIFICATION_TASK_MINIMUM_SET_AT_THROTTLE_TIME_MINUTES);
1269
+ }
1270
+ else {
1271
+ // update the next send type of non-tasks to try being sent again in 10 minutes, if they fail
1272
+ nextSat = dateFns.addMinutes(now, 10);
1273
+ }
1274
+ }
1275
+ let createdBox = false;
1276
+ let deletedNotification = false;
1277
+ let notificationBoxNeedsInitialization = false;
1278
+ let isKnownTemplateType;
1279
+ let isConfiguredTemplateType;
1280
+ let onlySendToExplicitlyEnabledRecipients;
1281
+ let onlyTextExplicitlyEnabledRecipients;
1282
+ let templateInstance;
1283
+ let notificationTaskHandler;
1284
+ async function deleteNotification() {
1285
+ tryRun = false;
1286
+ await notificationDocumentInTransaction.accessor.delete();
1287
+ deletedNotification = true;
1288
+ }
1289
+ // create/init the notification box if necessary/configured.
1290
+ if (notification && tryRun) {
1291
+ // if we're still trying to run, check the template is ok. If not, cancel the run.
1292
+ const { t // notification task/template type
1293
+ } = notification.n;
1294
+ if (isNotificationTask) {
1295
+ notificationTaskHandler = notificationTaskService.taskHandlerForNotificationTaskType(t);
1296
+ if (notificationTaskHandler) {
1297
+ if (notification.a >= NOTIFICATION_TASK_TYPE_MAX_SEND_ATTEMPTS) {
1298
+ tryRun = false;
1299
+ console.warn(`Configured notification task of type "${t}" has reached the delete threshhold after being attempted ${notification.a} times. Deleting notification task.`);
1300
+ await deleteNotification();
1301
+ }
1302
+ }
1303
+ else {
1304
+ tryRun = false;
1305
+ const delay = UNKNOWN_NOTIFICATION_TASK_TYPE_HOURS_DELAY;
1306
+ if (notification.a < UNKNOWN_NOTIFICATION_TASK_TYPE_DELETE_AFTER_RETRY_ATTEMPTS) {
1307
+ console.warn(`Notification task type of "${t}" was found in a Notification but has no handler. Action is being delayed by ${delay} hours.`);
1308
+ nextSat = dateFns.addHours(now, delay);
1309
+ }
1310
+ else {
1311
+ console.warn(`Notification task type of "${t}" was found in a Notification but has no handler. Action is being deleted.`);
1312
+ // delete the notification
1313
+ await deleteNotification();
1314
+ }
1315
+ }
1316
+ }
1317
+ else {
1318
+ templateInstance = notificationTemplateService.templateInstanceForType(t);
1319
+ isConfiguredTemplateType = templateInstance.isConfiguredType;
1320
+ const templateTypeInfo = appNotificationTemplateTypeInfoRecordService.appNotificationTemplateTypeInfoRecord[t];
1321
+ isKnownTemplateType = templateTypeInfo != null;
1322
+ onlySendToExplicitlyEnabledRecipients = notification.ois ?? templateTypeInfo?.onlySendToExplicitlyEnabledRecipients;
1323
+ onlyTextExplicitlyEnabledRecipients = notification.ots ?? templateTypeInfo?.onlyTextExplicitlyEnabledRecipients;
1324
+ if (!isConfiguredTemplateType) {
1325
+ // log the issue that an notification with an unconfigured type was queued
1326
+ const retryAttempts = isKnownTemplateType ? KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS : UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS;
1327
+ const delay = isKnownTemplateType ? KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY : UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY;
1328
+ if (notification.a < retryAttempts) {
1329
+ if (isKnownTemplateType) {
1330
+ console.warn(`Unconfigured but known template type of "${t}" (${templateTypeInfo.name}) was found in a Notification. Send is being delayed by ${delay} hours.`);
1331
+ }
1332
+ else {
1333
+ console.warn(`Unknown template type of "${t}" was found in a Notification. Send is being delayed by ${delay} hours.`);
1334
+ }
1335
+ // delay send for 12 hours, for a max of 24 hours incase it is an issue.
1336
+ nextSat = dateFns.addHours(now, delay);
1337
+ tryRun = false;
1338
+ }
1339
+ else {
1340
+ console.warn(`Unconfigured template type of "${t}" was found in a Notification. The Notification has reached the delete threshhold after failing to send due to misconfiguration multiple times and is being deleted.`);
1341
+ // after attempting to send 3 times, delete it.
1342
+ await deleteNotification();
1343
+ }
1344
+ }
1345
+ // handle the notification box's absence
1346
+ if (!notificationBox && tryRun) {
1347
+ switch (notification.st) {
1348
+ case firebase.NotificationSendType.INIT_BOX_AND_SEND:
1349
+ const { notificationBoxTemplate } = await createNotificationBoxInTransaction({ notificationBoxDocument }, transaction);
1350
+ notificationBox = firebase.setIdAndKeyFromKeyIdRefOnDocumentData(notificationBoxTemplate, notificationBoxDocument);
1351
+ createdBox = true;
1352
+ break;
1353
+ case firebase.NotificationSendType.SEND_IF_BOX_EXISTS:
1354
+ // delete the notification since it won't get sent.
1355
+ await deleteNotification();
1356
+ break;
1357
+ case firebase.NotificationSendType.SEND_WITHOUT_CREATING_BOX:
1358
+ // continue with current tryRun
1359
+ break;
1360
+ }
1361
+ }
1362
+ // if the notification box is not initialized/synchronized yet, do not run.
1363
+ if (tryRun && notificationBox && notificationBox.s) {
1364
+ notificationBoxNeedsInitialization = true;
1365
+ tryRun = false;
1366
+ nextSat = dateFns.addMinutes(now, NOTIFICATION_BOX_NOT_INITIALIZED_DELAY_MINUTES);
1367
+ }
1368
+ }
1369
+ }
1370
+ // update the notification send at time and attempt count
1371
+ if (notification != null && nextSat != null && !deletedNotification) {
1372
+ const isAtMaxAttempts = notification.a >= NOTIFICATION_MAX_SEND_ATTEMPTS;
1373
+ if (isAtMaxAttempts && notificationBoxNeedsInitialization) {
1374
+ await deleteNotification(); // just delete the notification if the box still hasn't been initialized successfully at this point.
1375
+ }
1376
+ // check if it was just deleted
1377
+ if (!deletedNotification) {
1378
+ const a = isNotificationTask && tryRun ? notification.a : notification.a + 1; // do not update a notification task's attempt count here, unless tryRun fails
1379
+ // NOTE: It is important to update sat so the notification task queue running doesn't get stuck in a query loop by notifications/tasks that have a sat value that is in the past, but was just run.
1380
+ await notificationDocumentInTransaction.update({ sat: nextSat, a });
1381
+ }
1382
+ }
1383
+ return {
1384
+ nextSat,
1385
+ throttled,
1386
+ isNotificationTask,
1387
+ deletedNotification,
1388
+ createdBox,
1389
+ notificationBoxModelKey: model,
1390
+ notificationBoxNeedsInitialization,
1391
+ notificationBox,
1392
+ notification,
1393
+ templateInstance,
1394
+ isKnownTemplateType,
1395
+ notificationTaskHandler,
1396
+ isConfiguredTemplateType,
1397
+ tryRun,
1398
+ onlySendToExplicitlyEnabledRecipients,
1399
+ onlyTextExplicitlyEnabledRecipients
1400
+ };
1401
+ });
1402
+ let success = false;
1403
+ let isUniqueNotificationTask = false;
1404
+ let uniqueNotificationTaskConflict = false;
1405
+ let sendEmailsResult;
1406
+ let sendTextsResult;
1407
+ let sendNotificationSummaryResult;
1408
+ let loadMessageFunctionFailure = false;
1409
+ let buildMessageFailure = false;
1410
+ let notificationMarkedDone = false;
1411
+ let notificationTaskCompletionType;
1412
+ let notificationTaskPartsRunCount = 0;
1413
+ let notificationTaskLoopingProtectionTriggered;
1414
+ let onSendAttemptedResult;
1415
+ let onSendSuccessResult;
1416
+ const notificationTemplateType = templateInstance?.type;
1417
+ if (isNotificationTask) {
1418
+ await handleNotificationTask();
1419
+ }
1420
+ else {
1421
+ await handleNormalNotification();
1422
+ }
1423
+ async function _runNotificationTaskNextPart(input) {
1424
+ const { notification, notificationTaskHandler, previouslyCompleteSubTasks } = input;
1425
+ const { n: item, cat, ut } = notification;
1426
+ let tryRunNextPart = false;
1427
+ let partNotificationTaskCompletionType;
1428
+ let partNotificationMarkedDone = false;
1429
+ let partTprReversal = false;
1430
+ let partSuccess = false;
1431
+ let nextCompleteSubTasks;
1432
+ const unique = ut ?? false;
1433
+ const notificationTask = {
1434
+ notificationDocument,
1435
+ totalSendAttempts: notification.a,
1436
+ currentCheckpointSendAttempts: notification.at ?? 0,
1437
+ taskType: item.t,
1438
+ item,
1439
+ data: item.d,
1440
+ checkpoints: notification.tpr,
1441
+ createdAt: cat,
1442
+ unique
1443
+ };
1444
+ // calculate results
1445
+ const notificationTemplate = {};
1446
+ // perform the task
1447
+ try {
1448
+ const handleTaskResult = await notificationTaskHandler.handleNotificationTask(notificationTask);
1449
+ const { completion, updateMetadata, delayUntil, canRunNextCheckpoint, allCompletedSubTasks } = handleTaskResult;
1450
+ partNotificationTaskCompletionType = completion;
1451
+ partSuccess = true;
1452
+ switch (completion) {
1453
+ case true:
1454
+ notificationTemplate.d = true; // mark as done
1455
+ break;
1456
+ case false:
1457
+ // failed
1458
+ notificationTemplate.a = notification.a + 1; // increase attempts count
1459
+ notificationTemplate.at = (notification.at ?? 0) + 1; // increase checkpoint attempts count
1460
+ // remove any completions, if applicable
1461
+ notificationTemplate.tpr = removeFromCompletionsArrayWithTaskResult(notification.tpr, handleTaskResult);
1462
+ partSuccess = false;
1463
+ break;
1464
+ default:
1465
+ // default case called if not true or false, which implies either a delay or partial completion
1466
+ // update the checkpoint attempts count
1467
+ if (Array.isArray(completion) && completion.length === 0) {
1468
+ notificationTemplate.at = (notification.at ?? 0) + 1; // increase checkpoint attempt/delays count
1469
+ tryRunNextPart = canRunNextCheckpoint === true && allCompletedSubTasks != null && delayUntil == null; // can only run the next part if subtasks were returned and there is no delay
1470
+ }
1471
+ else {
1472
+ tryRunNextPart = canRunNextCheckpoint === true && delayUntil == null; // can try the next part if there is no delayUntil and canRunNextCheckpoint is true
1473
+ notificationTemplate.at = 0; // reset checkpoint attempt/delay count
1474
+ }
1475
+ // add the checkpoint to the notification
1476
+ notificationTemplate.tpr = [
1477
+ ...removeFromCompletionsArrayWithTaskResult(notification.tpr, handleTaskResult), // remove any completions, if applicable
1478
+ ...util.asArray(completion)
1479
+ ];
1480
+ // calculate the updated notification item
1481
+ notificationTemplate.n = {
1482
+ ...notification.n,
1483
+ d: {
1484
+ ...notification.n.d,
1485
+ ...(updateMetadata ? util.filterOnlyUndefinedValues(updateMetadata) : undefined) // ignore any undefined values
1486
+ }
1487
+ };
1488
+ // can only run the next part if the tpr has changed, and the number of checkpoints completed has increased
1489
+ // if the tpr has not changed, then it is also considered a reversal
1490
+ if (tryRunNextPart) {
1491
+ const tprChanged = !util.iterablesAreSetEquivalent(notification.tpr, notificationTemplate.tpr);
1492
+ partTprReversal = !tprChanged || (tprChanged && notificationTemplate.tpr.length <= notification.tpr.length);
1493
+ if (allCompletedSubTasks != null) {
1494
+ switch (allCompletedSubTasks) {
1495
+ case true:
1496
+ case false:
1497
+ // only run if there is no tpr reversal flagged
1498
+ tryRunNextPart = !partTprReversal;
1499
+ break;
1500
+ default:
1501
+ // check subtask tpr changes
1502
+ nextCompleteSubTasks = util.asArray(allCompletedSubTasks);
1503
+ const subtaskTprChanged = !util.iterablesAreSetEquivalent(previouslyCompleteSubTasks, nextCompleteSubTasks);
1504
+ partTprReversal = !subtaskTprChanged || (subtaskTprChanged && nextCompleteSubTasks.length <= previouslyCompleteSubTasks.length);
1505
+ break;
1506
+ }
1507
+ }
1508
+ }
1509
+ break;
1510
+ }
1511
+ // do not update sat if the task is complete
1512
+ if (completion !== true && delayUntil != null) {
1513
+ // must be at least 20 seconds into the future from now, and/or the nextSat time to avoid parallel runs
1514
+ const minimumNextSatTime = dateFns.addSeconds(new Date(), 20);
1515
+ notificationTemplate.sat = date.findMaxDate([util.dateOrMillisecondsToDate(delayUntil, now), nextSat, minimumNextSatTime]) ?? minimumNextSatTime;
1516
+ }
1517
+ partNotificationMarkedDone = notificationTemplate.d === true;
1518
+ }
1519
+ catch (e) {
1520
+ console.error(`Failed handling task for notification "${notification.key}" with type "${notificationTask.taskType}": `, e);
1521
+ notificationTemplate.a = notification.a + 1; // increase attempts count
1522
+ notificationTemplate.sat = util.dateOrMillisecondsToDate(NOTIFICATION_TASK_TYPE_FAILURE_DELAY_MS, now);
1523
+ partSuccess = false;
1524
+ }
1525
+ // notification tasks are read
1526
+ let saveTaskResult = true;
1527
+ if (unique) {
1528
+ isUniqueNotificationTask = true;
1529
+ const latestNotification = await notificationDocument.snapshotData();
1530
+ if (!latestNotification || !date.isSameDate(latestNotification.cat, notification.cat)) {
1531
+ saveTaskResult = false;
1532
+ uniqueNotificationTaskConflict = true;
1533
+ }
1534
+ }
1535
+ if (saveTaskResult) {
1536
+ await notificationDocument.update(notificationTemplate);
1537
+ }
1538
+ return {
1539
+ tryRunNextPart,
1540
+ partNotificationMarkedDone,
1541
+ partNotificationTaskCompletionType,
1542
+ partTprReversal,
1543
+ nextCompleteSubTasks,
1544
+ partSuccess
1545
+ };
1546
+ }
1547
+ /**
1548
+ * Notification task handling.
1549
+ *
1550
+ * Notification takss can have multiple async but sequential parts.
1551
+ *
1552
+ * Some of these parts may be able to be run immediately one after the other, instead of waiting for
1553
+ * another sendNotification() to complete on it.
1554
+ */
1555
+ async function handleNotificationTask() {
1556
+ const MAX_NOTIFICATION_TASK_PARTS_RUN_ALLOWED = 5;
1557
+ if (tryRun && notification != null && notificationTaskHandler) {
1558
+ let currentNotification = notification;
1559
+ let previouslyCompleteSubTasks = [];
1560
+ notificationTaskLoopingProtectionTriggered = false;
1561
+ notificationTaskPartsRunCount = 0;
1562
+ while (notificationTaskPartsRunCount < MAX_NOTIFICATION_TASK_PARTS_RUN_ALLOWED) {
1563
+ notificationTaskPartsRunCount += 1;
1564
+ const result = await _runNotificationTaskNextPart({
1565
+ notification: currentNotification,
1566
+ notificationTaskHandler,
1567
+ previouslyCompleteSubTasks
1568
+ });
1569
+ notificationTaskCompletionType = result.partNotificationTaskCompletionType;
1570
+ previouslyCompleteSubTasks = result.nextCompleteSubTasks ?? [];
1571
+ success = result.partSuccess;
1572
+ const tryRunNextPart = result.partSuccess && result.tryRunNextPart && !notificationTaskLoopingProtectionTriggered;
1573
+ notificationTaskLoopingProtectionTriggered = notificationTaskLoopingProtectionTriggered || result.partTprReversal; // update the flag if the TPR has been reversed
1574
+ if (tryRunNextPart) {
1575
+ const updatedNotificationData = await notificationDocument.snapshotData();
1576
+ if (updatedNotificationData) {
1577
+ currentNotification = firebase.setIdAndKeyFromKeyIdRefOnDocumentData(updatedNotificationData, notificationDocument);
1578
+ }
1579
+ else {
1580
+ break; // notification is unavailable now
1581
+ }
1582
+ }
1583
+ else {
1584
+ break; // escape the loop
1585
+ }
1586
+ }
1587
+ }
1588
+ }
1589
+ /**
1590
+ * Handles a normal (non-task) notification.
1591
+ */
1592
+ async function handleNormalNotification() {
1593
+ // notification is only null/undefined if it didn't exist.
1594
+ if (notification != null) {
1595
+ if (tryRun && templateInstance != null) {
1596
+ // first load the message function
1597
+ const messageFunction = await templateInstance
1598
+ .loadMessageFunction({
1599
+ item: notification.n,
1600
+ notification,
1601
+ notificationBox: {
1602
+ m: notificationBoxModelKey
1603
+ }
1604
+ })
1605
+ .catch((e) => {
1606
+ loadMessageFunctionFailure = true;
1607
+ success = false;
1608
+ console.error(`Failed loading message function for type ${notificationTemplateType}: `, e);
1609
+ return undefined;
1610
+ });
1611
+ if (messageFunction) {
1612
+ function filterOutNoContentNotificationMessages(messages) {
1613
+ return messages.filter((x) => !x.flag);
1614
+ }
1615
+ // expand recipients
1616
+ const { emails: emailRecipients, texts: textRecipients, notificationSummaries: notificationSummaryRecipients } = await expandNotificationRecipients({
1617
+ notification,
1618
+ notificationBox,
1619
+ authService,
1620
+ notificationUserAccessor,
1621
+ globalRecipients: messageFunction.globalRecipients,
1622
+ onlySendToExplicitlyEnabledRecipients,
1623
+ onlyTextExplicitlyEnabledRecipients,
1624
+ notificationSummaryIdForUid: notificationSendService.notificationSummaryIdForUidFunction
1625
+ });
1626
+ let { es, ts, ps, ns, esr: currentEsr, tsr: currentTsr } = notification;
1627
+ // do emails
1628
+ let esr;
1629
+ if (es === firebase.NotificationSendState.QUEUED || es === firebase.NotificationSendState.SENT_PARTIAL) {
1630
+ const emailRecipientsAlreadySentTo = new Set(currentEsr.map((x) => x.toLowerCase()));
1631
+ const emailInputContexts = emailRecipients
1632
+ .filter((x) => !emailRecipientsAlreadySentTo.has(x.emailAddress.toLowerCase()))
1633
+ .map((x) => {
1634
+ const context = {
1635
+ recipient: {
1636
+ n: x.name,
1637
+ e: x.emailAddress,
1638
+ t: x.phoneNumber
1639
+ }
1640
+ };
1641
+ return context;
1642
+ });
1643
+ const emailMessages = await Promise.all(emailInputContexts.map(messageFunction))
1644
+ .then(filterOutNoContentNotificationMessages)
1645
+ .catch((e) => {
1646
+ console.error(`Failed building message function for type ${notificationTemplateType}: `, e);
1647
+ buildMessageFailure = true;
1648
+ return undefined;
1649
+ });
1650
+ if (emailMessages?.length) {
1651
+ if (notificationSendService.emailSendService != null) {
1652
+ let sendInstance;
1653
+ try {
1654
+ sendInstance = await notificationSendService.emailSendService.buildSendInstanceForEmailNotificationMessages(emailMessages);
1655
+ }
1656
+ catch (e) {
1657
+ console.error(`Failed building email send instance for notification "${notification.id}" with type "${notificationTemplateType}": `, e);
1658
+ es = firebase.NotificationSendState.CONFIG_ERROR;
1659
+ }
1660
+ if (sendInstance) {
1661
+ try {
1662
+ sendEmailsResult = await sendInstance();
1663
+ }
1664
+ catch (e) {
1665
+ console.error(`Failed sending email notification "${notification.id}" with type "${notificationTemplateType}": `, e);
1666
+ es = firebase.NotificationSendState.SEND_ERROR;
1667
+ }
1668
+ }
1669
+ }
1670
+ else {
1671
+ console.error(`Failed sending email notification "${notification.id}" with type "${notificationTemplateType}" due to no email service being configured.`);
1672
+ es = firebase.NotificationSendState.CONFIG_ERROR;
1673
+ }
1674
+ if (sendEmailsResult != null) {
1675
+ const { success, failed } = sendEmailsResult;
1676
+ esr = success.length ? currentEsr.concat(success.map((x) => x.toLowerCase())) : undefined;
1677
+ if (failed.length > 0) {
1678
+ es = firebase.NotificationSendState.SENT_PARTIAL;
1679
+ }
1680
+ else {
1681
+ es = firebase.NotificationSendState.SENT;
1682
+ }
1683
+ }
1684
+ }
1685
+ else {
1686
+ es = firebase.NotificationSendState.SENT;
1687
+ }
1688
+ }
1689
+ // do phone numbers
1690
+ let tsr;
1691
+ if (ts === firebase.NotificationSendState.QUEUED || ts === firebase.NotificationSendState.SENT_PARTIAL) {
1692
+ const textRecipientsAlreadySentTo = new Set(currentTsr);
1693
+ const textInputContexts = textRecipients
1694
+ .filter((x) => !textRecipientsAlreadySentTo.has(x.phoneNumber))
1695
+ .map((x) => {
1696
+ const context = {
1697
+ recipient: {
1698
+ n: x.name,
1699
+ e: x.emailAddress,
1700
+ t: x.phoneNumber
1701
+ }
1702
+ };
1703
+ return context;
1704
+ });
1705
+ const textMessages = await Promise.all(textInputContexts.map(messageFunction))
1706
+ .then(filterOutNoContentNotificationMessages)
1707
+ .catch((e) => {
1708
+ console.error(`Failed building message function for type ${notificationTemplateType}: `, e);
1709
+ buildMessageFailure = true;
1710
+ return undefined;
1711
+ });
1712
+ if (textMessages?.length) {
1713
+ if (notificationSendService.textSendService != null) {
1714
+ let sendInstance;
1715
+ try {
1716
+ sendInstance = await notificationSendService.textSendService.buildSendInstanceForTextNotificationMessages(textMessages);
1717
+ }
1718
+ catch (e) {
1719
+ console.error(`Failed building text send instance for notification "${notification.id}" with type "${notificationTemplateType}": `, e);
1720
+ ts = firebase.NotificationSendState.CONFIG_ERROR;
1721
+ }
1722
+ if (sendInstance) {
1723
+ try {
1724
+ sendTextsResult = await sendInstance();
1725
+ }
1726
+ catch (e) {
1727
+ console.error(`Failed sending text notification "${notification.id}" with type "${notificationTemplateType}": `, e);
1728
+ ts = firebase.NotificationSendState.SEND_ERROR;
1729
+ }
1730
+ }
1731
+ }
1732
+ else {
1733
+ console.error(`Failed sending text notification "${notification.id}" with type "${notificationTemplateType}" due to no text service being configured.`);
1734
+ ts = firebase.NotificationSendState.CONFIG_ERROR;
1735
+ }
1736
+ if (sendTextsResult != null) {
1737
+ const { success, failed } = sendTextsResult;
1738
+ tsr = success.length ? currentTsr.concat(success) : undefined;
1739
+ if (failed.length > 0) {
1740
+ ts = firebase.NotificationSendState.SENT_PARTIAL;
1741
+ }
1742
+ else {
1743
+ ts = firebase.NotificationSendState.SENT;
1744
+ }
1745
+ }
1746
+ }
1747
+ else {
1748
+ ts = firebase.NotificationSendState.SENT;
1749
+ }
1750
+ }
1751
+ ps = firebase.NotificationSendState.NO_TRY;
1752
+ // NOTE: FCM token management will probably done with a separate system within Notification that stores FCMs for specific users in the app. May also use UIDs to determine who got the push notificdation or not...
1753
+ // do notification summaries
1754
+ if (ns === firebase.NotificationSendState.QUEUED || ns === firebase.NotificationSendState.SENT_PARTIAL) {
1755
+ const notificationSummaryInputContexts = notificationSummaryRecipients.map((x) => {
1756
+ const context = {
1757
+ recipient: {
1758
+ n: x.name,
1759
+ s: x.notificationSummaryId
1760
+ }
1761
+ };
1762
+ return context;
1763
+ });
1764
+ const notificationSummaryMessages = await Promise.all(notificationSummaryInputContexts.map(messageFunction))
1765
+ .then(filterOutNoContentNotificationMessages)
1766
+ .catch((e) => {
1767
+ console.error(`Failed building message function for type ${notificationTemplateType}: `, e);
1768
+ buildMessageFailure = true;
1769
+ return undefined;
1770
+ });
1771
+ if (notificationSummaryMessages?.length) {
1772
+ if (notificationSendService.notificationSummarySendService != null) {
1773
+ let sendInstance;
1774
+ try {
1775
+ sendInstance = await notificationSendService.notificationSummarySendService.buildSendInstanceForNotificationSummaryMessages(notificationSummaryMessages);
1776
+ }
1777
+ catch (e) {
1778
+ console.error(`Failed building notification summary send instance for notification "${notification.id}" with type "${notificationTemplateType}": `, e);
1779
+ ns = firebase.NotificationSendState.CONFIG_ERROR;
1780
+ }
1781
+ if (sendInstance) {
1782
+ try {
1783
+ sendNotificationSummaryResult = await sendInstance();
1784
+ ns = firebase.NotificationSendState.SENT;
1785
+ }
1786
+ catch (e) {
1787
+ console.error(`Failed sending notification summary notification "${notification.id}" with type "${notificationTemplateType}": `, e);
1788
+ ns = firebase.NotificationSendState.SEND_ERROR;
1789
+ }
1790
+ }
1791
+ }
1792
+ else {
1793
+ console.error(`Failed sending notification summary notification "${notification.id}" with type "${notificationTemplateType}" due to no notification summary service being configured.`);
1794
+ ns = firebase.NotificationSendState.CONFIG_ERROR;
1795
+ }
1796
+ }
1797
+ else {
1798
+ ns = firebase.NotificationSendState.SENT;
1799
+ }
1800
+ }
1801
+ // calculate results
1802
+ const notificationTemplate = { es, ts, ps, ns, esr, tsr };
1803
+ success = firebase.notificationSendFlagsImplyIsComplete(notificationTemplate);
1804
+ if (success) {
1805
+ notificationTemplate.d = true;
1806
+ }
1807
+ else {
1808
+ notificationTemplate.a = notification.a + 1;
1809
+ if (notificationTemplate.a >= NOTIFICATION_MAX_SEND_ATTEMPTS) {
1810
+ notificationTemplate.d = true;
1811
+ }
1812
+ }
1813
+ await notificationDocument.update(notificationTemplate);
1814
+ notificationMarkedDone = notificationTemplate.d === true;
1815
+ const callbackDetails = {
1816
+ success,
1817
+ updatedSendFlags: notificationTemplate,
1818
+ sendEmailsResult,
1819
+ sendTextsResult,
1820
+ sendNotificationSummaryResult
1821
+ };
1822
+ const { onSendAttempted, onSendSuccess } = messageFunction;
1823
+ // call onSendAttempted, if one is configured
1824
+ if (onSendAttempted) {
1825
+ onSendAttemptedResult = await util.asPromise(onSendAttempted(callbackDetails))
1826
+ .then((value) => {
1827
+ return { value };
1828
+ })
1829
+ .catch((e) => {
1830
+ console.warn(`Caught exception while calling onSendAttempted for notification "${notification.id}" with type "${notificationTemplateType}": `, e);
1831
+ return { error: e };
1832
+ });
1833
+ }
1834
+ // call onSendSuccess, if one is configured
1835
+ if (notificationMarkedDone && onSendSuccess) {
1836
+ onSendSuccessResult = await util.asPromise(onSendSuccess(callbackDetails))
1837
+ .then((value) => {
1838
+ return { value };
1839
+ })
1840
+ .catch((e) => {
1841
+ console.warn(`Caught exception while calling onSendSuccess for notification "${notification.id}" with type "${notificationTemplateType}": `, e);
1842
+ return { error: e };
1843
+ });
1844
+ }
1845
+ }
1846
+ }
1847
+ else {
1848
+ switch (notification.st) {
1849
+ case firebase.NotificationSendType.SEND_IF_BOX_EXISTS:
1850
+ // deleted successfully
1851
+ success = deletedNotification;
1852
+ break;
1853
+ }
1854
+ }
1855
+ }
1856
+ }
1857
+ const result = {
1858
+ notificationTemplateType,
1859
+ isKnownTemplateType,
1860
+ isNotificationTask,
1861
+ isUniqueNotificationTask,
1862
+ uniqueNotificationTaskConflict,
1863
+ isConfiguredTemplateType,
1864
+ throttled,
1865
+ exists: notification != null,
1866
+ boxExists: notificationBox != null,
1867
+ notificationTaskPartsRunCount,
1868
+ notificationTaskLoopingProtectionTriggered,
1869
+ notificationBoxNeedsInitialization,
1870
+ notificationTaskCompletionType,
1871
+ createdBox,
1872
+ deletedNotification,
1873
+ notificationMarkedDone,
1874
+ tryRun,
1875
+ success,
1876
+ sendEmailsResult,
1877
+ sendTextsResult,
1878
+ sendNotificationSummaryResult,
1879
+ loadMessageFunctionFailure,
1880
+ buildMessageFailure,
1881
+ onSendAttemptedResult,
1882
+ onSendSuccessResult
1883
+ };
1884
+ return result;
1885
+ };
1886
+ });
1887
+ }
1888
+ const SEND_QUEUE_NOTIFICATIONS_TASK_EXCESS_THRESHOLD = 5000;
1889
+ function sendQueuedNotificationsFactory(context) {
1890
+ const { firebaseServerActionTransformFunctionFactory, notificationCollectionGroup } = context;
1891
+ const sendNotification = sendNotificationFactory(context);
1892
+ return firebaseServerActionTransformFunctionFactory(firebase.SendQueuedNotificationsParams, async (params) => {
1893
+ const { maxSendNotificationLoops } = params;
1894
+ const maxLoops = maxSendNotificationLoops ?? Number.MAX_SAFE_INTEGER;
1895
+ const sendNotificationLoopsTaskExcessThreshold = params.sendNotificationLoopsTaskExcessThreshold ?? SEND_QUEUE_NOTIFICATIONS_TASK_EXCESS_THRESHOLD;
1896
+ return async (input) => {
1897
+ const maxParallelTasks = input?.maxParellelSendTasks ?? params.maxParellelSendTasks ?? 5;
1898
+ const onSendNotificationResult = input?.onSendNotificationResult ?? util.mapIdentityFunction();
1899
+ let notificationLoopCount = 0;
1900
+ let notificationBoxesCreated = 0;
1901
+ let notificationsDeleted = 0;
1902
+ let notificationTasksVisited = 0;
1903
+ let notificationsVisited = 0;
1904
+ let notificationsSucceeded = 0;
1905
+ let notificationsDelayed = 0;
1906
+ let notificationsFailed = 0;
1907
+ let sendEmailsResult;
1908
+ let sendTextsResult;
1909
+ let sendNotificationSummaryResult;
1910
+ const sendNotificationParams = { key: firebase.firestoreDummyKey(), throwErrorIfSent: false };
1911
+ const sendNotificationInstance = await sendNotification(sendNotificationParams);
1912
+ let excessLoopsDetected = false;
1913
+ const sendQueuedNotifications = async () => {
1914
+ const query = notificationCollectionGroup.queryDocument(firebase.notificationsPastSendAtTimeQuery());
1915
+ const notificationDocuments = await query.getDocs();
1916
+ const result = await util.performAsyncTasks(notificationDocuments, async (notificationDocument) => {
1917
+ const result = await sendNotificationInstance(notificationDocument);
1918
+ onSendNotificationResult(result, notificationDocument);
1919
+ return result;
1920
+ }, {
1921
+ maxParallelTasks
1922
+ });
1923
+ return result;
1924
+ };
1925
+ // iterate through all notification items that need to be synced
1926
+ while (notificationLoopCount < maxLoops) {
1927
+ const sendQueuedNotificationsResults = await sendQueuedNotifications();
1928
+ sendQueuedNotificationsResults.results.forEach((x) => {
1929
+ const result = x[1];
1930
+ if (result.success) {
1931
+ notificationsSucceeded += 1;
1932
+ }
1933
+ else if (result.createdBox || result.notificationBoxNeedsInitialization) {
1934
+ notificationsDelayed += 1;
1935
+ }
1936
+ else {
1937
+ notificationsFailed += 1;
1938
+ }
1939
+ if (result.isNotificationTask) {
1940
+ notificationTasksVisited += 1;
1941
+ }
1942
+ if (result.deletedNotification) {
1943
+ notificationsDeleted += 1;
1944
+ }
1945
+ if (result.createdBox) {
1946
+ notificationBoxesCreated += 1;
1947
+ }
1948
+ sendEmailsResult = firebase.mergeNotificationSendMessagesResult(sendEmailsResult, result.sendEmailsResult);
1949
+ sendTextsResult = firebase.mergeNotificationSendMessagesResult(sendTextsResult, result.sendTextsResult);
1950
+ sendNotificationSummaryResult = firebase.mergeNotificationSendMessagesResult(sendNotificationSummaryResult, result.sendNotificationSummaryResult);
1951
+ });
1952
+ const found = sendQueuedNotificationsResults.results.length;
1953
+ notificationsVisited += found;
1954
+ notificationLoopCount += 1;
1955
+ if (!found) {
1956
+ break;
1957
+ }
1958
+ else if (!excessLoopsDetected && notificationLoopCount > sendNotificationLoopsTaskExcessThreshold) {
1959
+ excessLoopsDetected = true;
1960
+ console.error(`sendQueuedNotifications(EXCESS_LOOPS_DETECTED): Exceeded send notification loops task excess threshold of ${sendNotificationLoopsTaskExcessThreshold}.`);
1961
+ // continue the loops
1962
+ }
1963
+ }
1964
+ const result = {
1965
+ excessLoopsDetected,
1966
+ notificationLoopCount,
1967
+ notificationBoxesCreated,
1968
+ notificationsDeleted,
1969
+ notificationTasksVisited,
1970
+ notificationsVisited,
1971
+ notificationsSucceeded,
1972
+ notificationsDelayed,
1973
+ notificationsFailed,
1974
+ sendEmailsResult,
1975
+ sendTextsResult,
1976
+ sendNotificationSummaryResult
1977
+ };
1978
+ return result;
1979
+ };
1980
+ });
1981
+ }
1982
+ function cleanupSentNotificationsFactory(context) {
1983
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, notificationCollectionGroup, notificationBoxCollection, notificationWeekCollectionFactory } = context;
1984
+ return firebaseServerActionTransformFunctionFactory(firebase.CleanupSentNotificationsParams, async () => {
1985
+ return async () => {
1986
+ let notificationBoxesUpdatesCount = 0;
1987
+ let notificationsDeleted = 0;
1988
+ const notificationTasksDeletedCount = 0;
1989
+ let notificationWeeksCreated = 0;
1990
+ let notificationWeeksUpdated = 0;
1991
+ // iterate through all Notification items that need to be cleaned up
1992
+ while (true) {
1993
+ const cleanupSentNotificationsResults = await cleanupSentNotifications();
1994
+ cleanupSentNotificationsResults.results.forEach((x) => {
1995
+ const { itemsDeleted, weeksCreated, weeksUpdated } = x[1];
1996
+ notificationsDeleted += itemsDeleted;
1997
+ notificationWeeksCreated += weeksCreated;
1998
+ notificationWeeksUpdated += weeksUpdated;
1999
+ });
2000
+ const notificationBoxesUpdated = cleanupSentNotificationsResults.results.length;
2001
+ notificationBoxesUpdatesCount += notificationBoxesUpdated;
2002
+ if (!notificationBoxesUpdated) {
2003
+ break;
2004
+ }
2005
+ }
2006
+ async function cleanupSentNotifications() {
2007
+ const query = notificationCollectionGroup.queryDocument(firebase.notificationsReadyForCleanupQuery());
2008
+ const notificationDocuments = await query.getDocs();
2009
+ const notificationDocumentsGroupedByNotificationBox = Array.from(util.makeValuesGroupMap(notificationDocuments, (x) => x.parent.id).values());
2010
+ const result = await util.performAsyncTasks(notificationDocumentsGroupedByNotificationBox, async (notificationDocumentsInSameBox) => {
2011
+ const allPairs = await firebase.getDocumentSnapshotDataPairs(notificationDocumentsInSameBox);
2012
+ const allPairsWithDataAndMarkedDeleted = allPairs.filter((x) => x.data?.d);
2013
+ const { included: taskPairsWithDataAndMarkedDeleted, excluded: normalPairsWithDataAndMarkedDeleted } = util.separateValues(allPairsWithDataAndMarkedDeleted, (x) => x.data?.st === firebase.NotificationSendType.TASK_NOTIFICATION);
2014
+ const pairsGroupedByWeek = Array.from(util.makeValuesGroupMap(normalPairsWithDataAndMarkedDeleted, (x) => date.yearWeekCode(x.data.sat)).entries());
2015
+ // batch incase there are a lot of new notifications to move to week
2016
+ const pairsGroupedByWeekInBatches = pairsGroupedByWeek
2017
+ .map((x) => {
2018
+ const batches = util.batch(x[1], 40);
2019
+ return batches.map((batch) => [x[0], batch]);
2020
+ })
2021
+ .flat();
2022
+ const notificationBoxDocument = await notificationBoxCollection.documentAccessor().loadDocument(notificationDocumentsInSameBox[0].parent);
2023
+ // create/update the NotificationWeek
2024
+ const notificationWeekResults = await util.performAsyncTasks(pairsGroupedByWeekInBatches, async ([yearWeekCode, notificationDocumentsInSameWeek]) => {
2025
+ return firestoreContext.runTransaction(async (transaction) => {
2026
+ const notificationWeekDocument = notificationWeekCollectionFactory(notificationBoxDocument).documentAccessorForTransaction(transaction).loadDocumentForId(`${yearWeekCode}`);
2027
+ const notificationDocumentsInTransaction = firebase.loadDocumentsForDocumentReferencesFromValues(notificationCollectionGroup.documentAccessorForTransaction(transaction), notificationDocumentsInSameWeek, (x) => x.snapshot.ref);
2028
+ const notificationWeek = await notificationWeekDocument.snapshotData();
2029
+ const newItems = util.filterMaybeArrayValues(notificationDocumentsInSameWeek.map((x) => {
2030
+ const data = x.data;
2031
+ const shouldSaveToNotificationWeek = firebase.shouldSaveNotificationToNotificationWeek(data);
2032
+ return shouldSaveToNotificationWeek ? data.n : undefined;
2033
+ }));
2034
+ const n = [...(notificationWeek?.n ?? []), ...newItems];
2035
+ if (!notificationWeek) {
2036
+ // create
2037
+ await notificationWeekDocument.create({
2038
+ w: yearWeekCode,
2039
+ n
2040
+ });
2041
+ }
2042
+ else {
2043
+ // update
2044
+ await notificationWeekDocument.update({
2045
+ n
2046
+ });
2047
+ }
2048
+ // delete the notification items
2049
+ await Promise.all(notificationDocumentsInTransaction.map((x) => x.accessor.delete()));
2050
+ return {
2051
+ created: !notificationWeek
2052
+ };
2053
+ });
2054
+ });
2055
+ // delete all the task notifications
2056
+ const writeBatch = firestoreContext.batch();
2057
+ const writeBatchAccessor = notificationCollectionGroup.documentAccessorForTransaction(writeBatch);
2058
+ await Promise.all(taskPairsWithDataAndMarkedDeleted.map((x) => writeBatchAccessor.loadDocumentFrom(x.document).accessor.delete()));
2059
+ await writeBatch.commit();
2060
+ let weeksCreated = 0;
2061
+ let weeksUpdated = 0;
2062
+ const tasksDeleted = taskPairsWithDataAndMarkedDeleted.length;
2063
+ notificationWeekResults.results.forEach((x) => {
2064
+ if (x[1].created) {
2065
+ weeksCreated += 1;
2066
+ }
2067
+ else {
2068
+ weeksUpdated += 1;
2069
+ }
2070
+ });
2071
+ const result = {
2072
+ weeksCreated,
2073
+ weeksUpdated,
2074
+ itemsDeleted: allPairsWithDataAndMarkedDeleted.length,
2075
+ tasksDeleted
2076
+ };
2077
+ return result;
2078
+ }, {
2079
+ maxParallelTasks: 10
2080
+ });
2081
+ return result;
2082
+ }
2083
+ const result = {
2084
+ notificationBoxesUpdatesCount,
2085
+ notificationTasksDeletedCount,
2086
+ notificationsDeleted,
2087
+ notificationWeeksCreated,
2088
+ notificationWeeksUpdated
2089
+ };
2090
+ return result;
2091
+ };
2092
+ });
2093
+ }
2094
+
2095
+ // MARK: NotificationInitServerActionsContextConfig
2096
+ /**
2097
+ * Token to access/override the NotificationTemplateService's defaults records.
2098
+ */
2099
+ const NOTIFICATION_INIT_SERVER_ACTIONS_CONTEXT_CONFIG_TOKEN = 'NOTIFICATION_INIT_SERVER_ACTIONS_CONTEXT_CONFIG';
2100
+ const MAKE_TEMPLATE_FOR_NOTIFICATION_RELATED_MODEL_INITIALIZATION_FUNCTION_DELETE_RESPONSE = false;
2101
+ class NotificationInitServerActions {
2102
+ }
2103
+ function notificationInitServerActions(context) {
2104
+ return {
2105
+ initializeNotificationBox: initializeNotificationBoxFactory(context),
2106
+ initializeAllApplicableNotificationBoxes: initializeAllApplicableNotificationBoxesFactory(context),
2107
+ initializeNotificationSummary: initializeNotificationSummaryFactory(context),
2108
+ initializeAllApplicableNotificationSummaries: initializeAllApplicableNotificationSummariesFactory(context)
2109
+ };
2110
+ }
2111
+ async function initializeNotificationModelInTransaction(input) {
2112
+ const { makeTemplateFunction, throwErrorIfAlreadyInitialized, transaction, document: documentInTransaction, data: notificationBox } = input;
2113
+ let initialized = false;
2114
+ const alreadyInitialized = !notificationBox.s;
2115
+ if (!alreadyInitialized) {
2116
+ const flatModelKey = documentInTransaction.id;
2117
+ const modelKey = firebase.inferKeyFromTwoWayFlatFirestoreModelKey(flatModelKey);
2118
+ const modelCollectionName = firebase.firestoreModelKeyCollectionName(modelKey);
2119
+ const input = {
2120
+ transaction,
2121
+ flatModelKey,
2122
+ modelKey,
2123
+ collectionName: modelCollectionName
2124
+ };
2125
+ const template = await makeTemplateFunction(input);
2126
+ if (template === false) {
2127
+ await documentInTransaction.accessor.delete();
2128
+ }
2129
+ else if (template == null) {
2130
+ await documentInTransaction.update({
2131
+ s: false, // set false when "f" is set true
2132
+ fi: true
2133
+ });
2134
+ }
2135
+ else {
2136
+ initialized = true;
2137
+ await documentInTransaction.update({
2138
+ //
2139
+ ...template,
2140
+ m: undefined, // should not be changed
2141
+ s: null, // is now initialized.
2142
+ fi: false // set false
2143
+ });
2144
+ }
2145
+ }
2146
+ else if (throwErrorIfAlreadyInitialized) {
2147
+ throw notificationModelAlreadyInitializedError();
2148
+ }
2149
+ return {
2150
+ initialized,
2151
+ alreadyInitialized
2152
+ };
2153
+ }
2154
+ function initializeNotificationBoxInTransactionFactory(context) {
2155
+ const { notificationBoxCollection, makeTemplateForNotificationBoxInitialization } = context;
2156
+ return async (params, notificationBoxDocument, transaction) => {
2157
+ const { throwErrorIfAlreadyInitialized } = params;
2158
+ const notificationBoxDocumentInTransaction = notificationBoxCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(notificationBoxDocument);
2159
+ const notificationBox = await firebaseServer.assertSnapshotData(notificationBoxDocumentInTransaction);
2160
+ return initializeNotificationModelInTransaction({
2161
+ makeTemplateFunction: makeTemplateForNotificationBoxInitialization,
2162
+ throwErrorIfAlreadyInitialized,
2163
+ transaction,
2164
+ document: notificationBoxDocumentInTransaction,
2165
+ data: notificationBox
2166
+ });
2167
+ };
2168
+ }
2169
+ function initializeNotificationBoxFactory(context) {
2170
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory } = context;
2171
+ const initializeNotificationBoxInTransaction = initializeNotificationBoxInTransactionFactory(context);
2172
+ return firebaseServerActionTransformFunctionFactory(firebase.InitializeNotificationModelParams, async (params) => {
2173
+ return async (notificationBoxDocument) => {
2174
+ await firestoreContext.runTransaction((transaction) => initializeNotificationBoxInTransaction(params, notificationBoxDocument, transaction));
2175
+ return notificationBoxDocument;
2176
+ };
2177
+ });
2178
+ }
2179
+ function initializeAllApplicableNotificationBoxesFactory(context) {
2180
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, notificationBoxCollection, notificationCollectionGroup } = context;
2181
+ const initializeNotificationBoxInTransaction = initializeNotificationBoxInTransactionFactory(context);
2182
+ return firebaseServerActionTransformFunctionFactory(firebase.InitializeAllApplicableNotificationBoxesParams, async () => {
2183
+ return async () => {
2184
+ let notificationBoxesVisited = 0;
2185
+ let notificationBoxesSucceeded = 0;
2186
+ let notificationBoxesFailed = 0;
2187
+ let notificationBoxesAlreadyInitialized = 0;
2188
+ const initializeNotificationBoxParams = { key: firebase.firestoreDummyKey(), throwErrorIfAlreadyInitialized: false };
2189
+ async function initializeNotificationBoxes() {
2190
+ const query = notificationBoxCollection.queryDocument(firebase.notificationBoxesFlaggedForNeedsInitializationQuery());
2191
+ const notificationBoxDocuments = await query.getDocs();
2192
+ const result = await util.performAsyncTasks(notificationBoxDocuments, async (notificationBoxDocument) => {
2193
+ return firestoreContext.runTransaction((transaction) => initializeNotificationBoxInTransaction(initializeNotificationBoxParams, notificationBoxDocument, transaction));
2194
+ }, {
2195
+ maxParallelTasks: 5
2196
+ });
2197
+ return result;
2198
+ }
2199
+ // iterate through all NotificationBox items that need to be synced
2200
+ while (true) {
2201
+ const initializeNotificationBoxesResults = await initializeNotificationBoxes();
2202
+ initializeNotificationBoxesResults.results.forEach((x) => {
2203
+ const result = x[1];
2204
+ if (result.alreadyInitialized) {
2205
+ notificationBoxesAlreadyInitialized += 1;
2206
+ }
2207
+ else if (result.initialized) {
2208
+ notificationBoxesSucceeded += 1;
2209
+ }
2210
+ else {
2211
+ notificationBoxesFailed += 1;
2212
+ }
2213
+ });
2214
+ const found = initializeNotificationBoxesResults.results.length;
2215
+ notificationBoxesVisited += found;
2216
+ if (!found) {
2217
+ break;
2218
+ }
2219
+ }
2220
+ const result = {
2221
+ notificationBoxesVisited,
2222
+ notificationBoxesSucceeded,
2223
+ notificationBoxesFailed,
2224
+ notificationBoxesAlreadyInitialized
2225
+ };
2226
+ return result;
2227
+ };
2228
+ });
2229
+ }
2230
+ function initializeNotificationSummaryInTransactionFactory(context) {
2231
+ const { notificationSummaryCollection, makeTemplateForNotificationSummaryInitialization } = context;
2232
+ return async (params, notificationSummaryDocument, transaction) => {
2233
+ const { throwErrorIfAlreadyInitialized } = params;
2234
+ const notificationSummaryDocumentInTransaction = notificationSummaryCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(notificationSummaryDocument);
2235
+ const notificationSummary = await firebaseServer.assertSnapshotData(notificationSummaryDocumentInTransaction);
2236
+ return initializeNotificationModelInTransaction({
2237
+ makeTemplateFunction: makeTemplateForNotificationSummaryInitialization,
2238
+ throwErrorIfAlreadyInitialized,
2239
+ transaction,
2240
+ document: notificationSummaryDocumentInTransaction,
2241
+ data: notificationSummary
2242
+ });
2243
+ };
2244
+ }
2245
+ function initializeNotificationSummaryFactory(context) {
2246
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory } = context;
2247
+ const initializeNotificationSummaryInTransaction = initializeNotificationSummaryInTransactionFactory(context);
2248
+ return firebaseServerActionTransformFunctionFactory(firebase.InitializeNotificationModelParams, async (params) => {
2249
+ return async (notificationSummaryDocument) => {
2250
+ await firestoreContext.runTransaction((transaction) => initializeNotificationSummaryInTransaction(params, notificationSummaryDocument, transaction));
2251
+ return notificationSummaryDocument;
2252
+ };
2253
+ });
2254
+ }
2255
+ function initializeAllApplicableNotificationSummariesFactory(context) {
2256
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, notificationSummaryCollection, notificationCollectionGroup } = context;
2257
+ const initializeNotificationSummaryInTransaction = initializeNotificationSummaryInTransactionFactory(context);
2258
+ return firebaseServerActionTransformFunctionFactory(firebase.InitializeAllApplicableNotificationSummariesParams, async () => {
2259
+ return async () => {
2260
+ let notificationSummariesVisited = 0;
2261
+ let notificationSummariesSucceeded = 0;
2262
+ let notificationSummariesFailed = 0;
2263
+ let notificationSummariesAlreadyInitialized = 0;
2264
+ const initializeNotificationSummaryParams = { key: firebase.firestoreDummyKey(), throwErrorIfAlreadyInitialized: false };
2265
+ async function initializeNotificationSummaries() {
2266
+ const query = notificationSummaryCollection.queryDocument(firebase.notificationSummariesFlaggedForNeedsInitializationQuery());
2267
+ const notificationSummaryDocuments = await query.getDocs();
2268
+ const result = await util.performAsyncTasks(notificationSummaryDocuments, async (notificationSummaryDocument) => {
2269
+ return firestoreContext.runTransaction((transaction) => initializeNotificationSummaryInTransaction(initializeNotificationSummaryParams, notificationSummaryDocument, transaction));
2270
+ }, {
2271
+ maxParallelTasks: 5
2272
+ });
2273
+ return result;
2274
+ }
2275
+ // iterate through all NotificationSummary items that need to be synced
2276
+ while (true) {
2277
+ const initializeNotificationSummariesResults = await initializeNotificationSummaries();
2278
+ initializeNotificationSummariesResults.results.forEach((x) => {
2279
+ const result = x[1];
2280
+ if (result.alreadyInitialized) {
2281
+ notificationSummariesAlreadyInitialized += 1;
2282
+ }
2283
+ else if (result.initialized) {
2284
+ notificationSummariesSucceeded += 1;
2285
+ }
2286
+ else {
2287
+ notificationSummariesFailed += 1;
2288
+ }
2289
+ });
2290
+ const found = initializeNotificationSummariesResults.results.length;
2291
+ notificationSummariesVisited += found;
2292
+ if (!found) {
2293
+ break;
2294
+ }
2295
+ }
2296
+ const result = {
2297
+ notificationSummariesVisited,
2298
+ notificationSummariesSucceeded,
2299
+ notificationSummariesFailed,
2300
+ notificationSummariesAlreadyInitialized
2301
+ };
2302
+ return result;
2303
+ };
2304
+ });
2305
+ }
2306
+
2307
+ /******************************************************************************
2308
+ Copyright (c) Microsoft Corporation.
2309
+
2310
+ Permission to use, copy, modify, and/or distribute this software for any
2311
+ purpose with or without fee is hereby granted.
2312
+
2313
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
2314
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
2315
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
2316
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
2317
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
2318
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
2319
+ PERFORMANCE OF THIS SOFTWARE.
2320
+ ***************************************************************************** */
2321
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
2322
+
2323
+
2324
+ function __decorate(decorators, target, key, desc) {
2325
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2326
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2327
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2328
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2329
+ }
2330
+
2331
+ function __param(paramIndex, decorator) {
2332
+ return function (target, key) { decorator(target, key, paramIndex); }
2333
+ }
2334
+
2335
+ function __metadata(metadataKey, metadataValue) {
2336
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
2337
+ }
2338
+
2339
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
2340
+ var e = new Error(message);
2341
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
2342
+ };
2343
+
2344
+ // MARK: Tokens
2345
+ /**
2346
+ * Token to access/override the NotificationTemplateService's defaults records.
2347
+ */
2348
+ const NOTIFICATION_TEMPLATE_SERVICE_DEFAULTS_OVERRIDE_TOKEN = 'NOTIFICATION_TEMPLATE_SERVICE_DEFAULTS_OVERRIDE';
2349
+ /**
2350
+ * Token to access the NotificationTemplateService's type configs array.
2351
+ */
2352
+ const NOTIFICATION_TEMPLATE_SERVICE_CONFIGS_ARRAY_TOKEN = 'NOTIFICATION_TEMPLATE_SERVICE_CONFIGS_ARRAY';
2353
+
2354
+ /**
2355
+ * Service dedicated to providing access to NotificationMessageFunctionFactory values for specific NotificationTemplateTypes.
2356
+ */
2357
+ exports.NotificationTemplateService = class NotificationTemplateService {
2358
+ _defaults;
2359
+ _config;
2360
+ constructor(_inputDefaults, _inputConfigs) {
2361
+ this._defaults = _inputDefaults ?? {};
2362
+ this._config = new Map();
2363
+ if (_inputConfigs != null) {
2364
+ _inputConfigs.forEach((x) => {
2365
+ this._config.set(x.type, x);
2366
+ });
2367
+ }
2368
+ }
2369
+ configPairForType(type) {
2370
+ return [this._defaults[type], this._config.get(type)];
2371
+ }
2372
+ templateInstanceForType(type) {
2373
+ return notificationTemplateServiceInstance(this, type);
2374
+ }
2375
+ };
2376
+ exports.NotificationTemplateService = __decorate([
2377
+ __param(0, common.Optional()),
2378
+ __param(0, common.Inject(NOTIFICATION_TEMPLATE_SERVICE_DEFAULTS_OVERRIDE_TOKEN)),
2379
+ __param(1, common.Inject(NOTIFICATION_TEMPLATE_SERVICE_CONFIGS_ARRAY_TOKEN)),
2380
+ __metadata("design:paramtypes", [Object, Object])
2381
+ ], exports.NotificationTemplateService);
2382
+ /**
2383
+ * Creates a NotificationTemplateServiceInstance.
2384
+ *
2385
+ * @param service
2386
+ * @param type
2387
+ * @returns
2388
+ */
2389
+ function notificationTemplateServiceInstance(service, type) {
2390
+ const pair = service.configPairForType(type);
2391
+ const isKnownType = pair[0] != null || pair[1] != null;
2392
+ const defaultFactory = firebase.noContentNotificationMessageFunctionFactory();
2393
+ const instanceConfig = pair[1];
2394
+ return {
2395
+ service,
2396
+ type,
2397
+ isConfiguredType: isKnownType,
2398
+ loadMessageFunction: async (config) => {
2399
+ const factory = instanceConfig?.factory ?? defaultFactory;
2400
+ return factory(config);
2401
+ }
2402
+ };
2403
+ }
2404
+
2405
+ /**
2406
+ * Alternative version of createNotificationDocument() that checks if the document exists, and can run it if it does instead of recreated it.
2407
+ *
2408
+ * Does not support the use of a Transaction, as running should occur outside of a transaction.
2409
+ *
2410
+ * @param input
2411
+ * @returns
2412
+ */
2413
+ async function createOrRunUniqueNotificationDocument(input) {
2414
+ const { expediteService, expediteInstance, updateNextRunAtTime, now: inputNow } = input;
2415
+ let sat = input.template.sat;
2416
+ if (updateNextRunAtTime != null) {
2417
+ sat = updateNextRunAtTime === true ? (inputNow ?? new Date()) : updateNextRunAtTime;
2418
+ }
2419
+ const pair = firebase.createNotificationDocumentPair({
2420
+ ...input,
2421
+ template: {
2422
+ ...input.template,
2423
+ sat
2424
+ }
2425
+ });
2426
+ if (!pair.notification.ut) {
2427
+ throw new Error('createOrRunUniqueNotificationDocument(): Notification is not flagged as unique.');
2428
+ }
2429
+ const pairExists = await pair.notificationDocument.exists();
2430
+ let result = {
2431
+ ...pair,
2432
+ notificationCreated: false
2433
+ };
2434
+ async function runNotificationTask() {
2435
+ if (expediteService != null) {
2436
+ result.runResult = await expediteService.sendNotification(pair.notificationDocument, input.sendNotificationOptions);
2437
+ }
2438
+ else if (expediteInstance != null) {
2439
+ expediteInstance.enqueue(pair.notificationDocument);
2440
+ result.runEnqueued = true;
2441
+ }
2442
+ else if (!result.notificationCreated && updateNextRunAtTime != null) {
2443
+ await pair.notificationDocument.update({
2444
+ sat: sat
2445
+ });
2446
+ }
2447
+ }
2448
+ if (pairExists) {
2449
+ await runNotificationTask();
2450
+ }
2451
+ else {
2452
+ result = await firebase._createNotificationDocumentFromPair(input, pair);
2453
+ if (result.notificationCreated && input.runImmediatelyIfCreated) {
2454
+ await runNotificationTask();
2455
+ }
2456
+ }
2457
+ return result;
2458
+ }
2459
+
2460
+ /**
2461
+ * Service dedicated to providing access to NotificationMessageFunctionFactory values for specific NotificationTemplateTypes.
2462
+ */
2463
+ class NotificationSendService {
2464
+ }
2465
+
2466
+ /**
2467
+ * Service dedicated to providing access to NotificationMessageFunctionFactory values for specific NotificationTemplateTypes.
2468
+ */
2469
+ class NotificationTaskService {
2470
+ }
2471
+
2472
+ /**
2473
+ * Interface for a service that allows access to a NotificationServerActions instance and "expediting" the sending of notification(s) that should be emitted immediately for timeliness.
2474
+ *
2475
+ * @see MutableNotificationExpediteService is the default implementation.
2476
+ */
2477
+ class NotificationExpediteService {
2478
+ }
2479
+ /**
2480
+ * Creates a new NotificationExpediteServiceInstance with the input NotificationExpediteService.
2481
+
2482
+ * @param notificationExpediteService
2483
+ * @returns
2484
+ */
2485
+ function notificationExpediteServiceInstance(notificationExpediteService) {
2486
+ let _documentsToSend = [];
2487
+ const initialize = () => {
2488
+ _documentsToSend = []; // resets the documents to send
2489
+ };
2490
+ const enqueue = (notificationDocument) => {
2491
+ _documentsToSend.push(notificationDocument);
2492
+ };
2493
+ const enqueueCreateResult = (createResult) => {
2494
+ let enqueued = false;
2495
+ if (createResult.notificationDocument) {
2496
+ enqueue(createResult.notificationDocument);
2497
+ enqueued = true;
2498
+ }
2499
+ return enqueued;
2500
+ };
2501
+ const send = async (options) => {
2502
+ const results = await util.runAsyncTasksForValues(_documentsToSend, (x) => notificationExpediteService.sendNotification(x, options), {
2503
+ nonConcurrentTaskKeyFactory: (x) => x.parent.id // only send one notification at a time for a notification box
2504
+ });
2505
+ return results;
2506
+ };
2507
+ return {
2508
+ initialize,
2509
+ enqueue,
2510
+ enqueueCreateResult,
2511
+ send
2512
+ };
2513
+ }
2514
+ // MARK: Implementation
2515
+ /**
2516
+ * Service used to "expedite" the sending of a specific notification.
2517
+ *
2518
+ * Because the NotificationActionService is typically created after other action services are due to the dependency injection graph, this service is
2519
+ * created before the NotificationActionService is created, and then later updated by the NotificationActionService.
2520
+ *
2521
+ * It is best provided by provideMutableNotificationExpediteService() as a global provider.
2522
+ */
2523
+ exports.MutableNotificationExpediteService = class MutableNotificationExpediteService {
2524
+ _notificationServerActions;
2525
+ /**
2526
+ * Returns the configured NotificationServerActions instance.
2527
+ */
2528
+ getNotificationServerActions() {
2529
+ return this._notificationServerActions;
2530
+ }
2531
+ /**
2532
+ * Sets the NotificationServerActions instance to use.
2533
+ */
2534
+ setNotificationServerActions(notificationServerActions) {
2535
+ this._notificationServerActions = notificationServerActions;
2536
+ }
2537
+ async sendNotification(notificationDocument, options) {
2538
+ const sendNotification = await this._notificationServerActions.sendNotification({ key: firebase.firestoreDummyKey(), ...options });
2539
+ return sendNotification(notificationDocument);
2540
+ }
2541
+ expediteInstance() {
2542
+ return notificationExpediteServiceInstance(this);
2543
+ }
2544
+ };
2545
+ exports.MutableNotificationExpediteService = __decorate([
2546
+ common.Injectable()
2547
+ ], exports.MutableNotificationExpediteService);
2548
+ // MARK: Providers
2549
+ /**
2550
+ * Provides an instance of MutableNotificationExpediteService and NotificationExpediteService.
2551
+ *
2552
+ * This should generally be used in the global module of an app.
2553
+ */
2554
+ function provideMutableNotificationExpediteService() {
2555
+ return [
2556
+ exports.MutableNotificationExpediteService,
2557
+ {
2558
+ provide: NotificationExpediteService,
2559
+ useExisting: exports.MutableNotificationExpediteService
2560
+ }
2561
+ ];
2562
+ }
2563
+ /**
2564
+ * Convenience function that exports NotificationExpediteService and MutableNotificationExpediteService.
2565
+ *
2566
+ * This should generally be used in the global module of an app.
2567
+ */
2568
+ function exportMutableNotificationExpediteService() {
2569
+ return [NotificationExpediteService, exports.MutableNotificationExpediteService];
2570
+ }
2571
+
2572
+ // MARK: Provider Factories
2573
+ function notificationServerActionsContextFactory(context, notificationTemplateService, notificationSendService, notificationTaskService, notificationsExpediteService) {
2574
+ return { ...context, notificationTemplateService, notificationSendService, notificationTaskService, notificationsExpediteService };
2575
+ }
2576
+ function notificationServerActionsFactory(context, mutableNotificationExpediteService) {
2577
+ return notificationServerActions(context);
2578
+ }
2579
+ function notificationInitServerActionsFactory(context, notificationInitServerActionsContextConfig) {
2580
+ return notificationInitServerActions({
2581
+ ...context,
2582
+ ...notificationInitServerActionsContextConfig
2583
+ });
2584
+ }
2585
+ /**
2586
+ * Convenience function used to generate ModuleMetadata for an app's NotificationModule.
2587
+ *
2588
+ * By default this module exports:
2589
+ * - NotificationServerActionContext (NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN)
2590
+ * - NotificationTemplateService
2591
+ * - NotificationServerActions
2592
+ * - NotificationInitServerActions
2593
+ * - NotificationExpediteService (MutableNotificationExpediteService is used as the existing, but it is not re-exported)
2594
+ *
2595
+ * Be sure the class that delares the module using this function also extends AbstractAppNotificationModule.
2596
+ *
2597
+ * @param provide
2598
+ * @param useFactory
2599
+ * @returns
2600
+ */
2601
+ function appNotificationModuleMetadata(config$1) {
2602
+ const { dependencyModule, imports, exports: exports$1, providers } = config$1;
2603
+ const dependencyModuleImport = dependencyModule ? [dependencyModule] : [];
2604
+ return {
2605
+ imports: [config.ConfigModule, ...dependencyModuleImport, ...(imports ?? [])],
2606
+ exports: [NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN, NotificationExpediteService, exports.NotificationTemplateService, NotificationServerActions, NotificationInitServerActions, ...(exports$1 ?? [])],
2607
+ providers: [
2608
+ {
2609
+ provide: NotificationExpediteService,
2610
+ useExisting: exports.MutableNotificationExpediteService
2611
+ },
2612
+ {
2613
+ provide: NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN,
2614
+ useFactory: notificationServerActionsContextFactory,
2615
+ inject: [BASE_NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN, exports.NotificationTemplateService, NotificationSendService, NotificationTaskService, NotificationExpediteService]
2616
+ },
2617
+ {
2618
+ provide: exports.NotificationTemplateService,
2619
+ useClass: exports.NotificationTemplateService
2620
+ },
2621
+ {
2622
+ provide: NotificationServerActions,
2623
+ useFactory: notificationServerActionsFactory,
2624
+ inject: [NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN, NotificationExpediteService]
2625
+ },
2626
+ {
2627
+ provide: NotificationInitServerActions,
2628
+ useFactory: notificationInitServerActionsFactory,
2629
+ inject: [NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN, NOTIFICATION_INIT_SERVER_ACTIONS_CONTEXT_CONFIG_TOKEN]
2630
+ },
2631
+ ...(providers ?? [])
2632
+ ]
2633
+ };
2634
+ }
2635
+ /**
2636
+ * Abstract module that should be extended when using appNotificationModuleMetadata.
2637
+ */
2638
+ exports.AbstractAppNotificationModule = class AbstractAppNotificationModule {
2639
+ constructor(mutableNotificationExpediteService, actions) {
2640
+ mutableNotificationExpediteService.setNotificationServerActions(actions);
2641
+ }
2642
+ };
2643
+ exports.AbstractAppNotificationModule = __decorate([
2644
+ common.Module({}),
2645
+ __param(0, common.Inject(exports.MutableNotificationExpediteService)),
2646
+ __param(1, common.Inject(NotificationServerActions)),
2647
+ __metadata("design:paramtypes", [exports.MutableNotificationExpediteService, NotificationServerActions])
2648
+ ], exports.AbstractAppNotificationModule);
2649
+ /**
2650
+ * Pre-configured global provider for MutableNotificationExpediteService/NotificationExpediteService.
2651
+ */
2652
+ exports.GlobalNotificationModule = class GlobalNotificationModule {
2653
+ };
2654
+ exports.GlobalNotificationModule = __decorate([
2655
+ common.Global(),
2656
+ common.Module({
2657
+ providers: provideMutableNotificationExpediteService(),
2658
+ exports: exportMutableNotificationExpediteService()
2659
+ })
2660
+ ], exports.GlobalNotificationModule);
2661
+
2662
+ function firestoreNotificationSummarySendService(config) {
2663
+ const { context, allowCreateNotificationSummaries: inputAllowCreateNotificationSummaries } = config;
2664
+ const { firestoreContext, notificationSummaryCollection } = context;
2665
+ const allowCreateNotificationSummaries = inputAllowCreateNotificationSummaries ?? true;
2666
+ const sendService = {
2667
+ async buildSendInstanceForNotificationSummaryMessages(notificationMessages) {
2668
+ const messagesGroupedByNotificationSummaryMapBuilder = util.multiValueMapBuilder();
2669
+ notificationMessages.forEach((x) => {
2670
+ if (x.inputContext.recipient.s != null && x.item != null) {
2671
+ // only add to map builder if recipient id is defined
2672
+ messagesGroupedByNotificationSummaryMapBuilder.add(x.inputContext.recipient.s, x);
2673
+ }
2674
+ });
2675
+ const cutSubject = util.cutStringFunction({ maxLength: firebase.NOTIFICATION_SUMMARY_EMBEDDED_NOTIFICATION_ITEM_SUBJECT_MAX_LENGTH });
2676
+ const cutMessage = util.cutStringFunction({ maxLength: firebase.NOTIFICATION_SUMMARY_EMBEDDED_NOTIFICATION_ITEM_MESSAGE_MAX_LENGTH });
2677
+ const messagesGroups = messagesGroupedByNotificationSummaryMapBuilder.entries();
2678
+ return async () => {
2679
+ const success = [];
2680
+ const failed = [];
2681
+ const ignored = [];
2682
+ await util.runAsyncTasksForValues(messagesGroups, async ([notificationSummaryId, messages]) => {
2683
+ await firestoreContext
2684
+ .runTransaction(async (transaction) => {
2685
+ const notificationSummaryDocument = notificationSummaryCollection.documentAccessorForTransaction(transaction).loadDocumentForId(notificationSummaryId);
2686
+ const notificationSummary = await notificationSummaryDocument.snapshotData();
2687
+ let updated = false;
2688
+ let updateTemplate;
2689
+ const existingMessages = notificationSummary?.n ?? [];
2690
+ const existingMessageIds = new Set(existingMessages.map((x) => x.id));
2691
+ // ignore any repeat messages
2692
+ const messagesToSend = messages.filter((x) => !existingMessageIds.has(x.item.id));
2693
+ if (messagesToSend.length > 0) {
2694
+ // add the new items to existing n, then keep the last 1000
2695
+ const sortedN = existingMessages
2696
+ .concat(messagesToSend.map((x) => {
2697
+ let message = '';
2698
+ if (x.content.openingMessage) {
2699
+ message = x.content.openingMessage;
2700
+ }
2701
+ if (x.content.closingMessage) {
2702
+ message = (message ? message + '\n\n' : message) + x.content.closingMessage;
2703
+ }
2704
+ const item = {
2705
+ ...x.item,
2706
+ s: cutSubject(x.content.title),
2707
+ g: cutMessage(message)
2708
+ };
2709
+ return item;
2710
+ }))
2711
+ .sort(firebase.sortNotificationItemsFunction);
2712
+ const n = util.takeLast(sortedN, firebase.NOTIFICATION_SUMMARY_ITEM_LIMIT);
2713
+ updateTemplate = {
2714
+ n,
2715
+ lat: new Date()
2716
+ };
2717
+ }
2718
+ if (updateTemplate != null) {
2719
+ if (notificationSummary != null) {
2720
+ await notificationSummaryDocument.update(updateTemplate);
2721
+ updated = true;
2722
+ }
2723
+ else if (allowCreateNotificationSummaries) {
2724
+ // if it does not exist, and we are allowed to create new summaries, create it and add the new notifications
2725
+ const createTemplate = {
2726
+ ...makeNewNotificationSummaryTemplate(firebase.inferKeyFromTwoWayFlatFirestoreModelKey(notificationSummaryId)),
2727
+ ...updateTemplate
2728
+ };
2729
+ await notificationSummaryDocument.create(createTemplate);
2730
+ updated = true;
2731
+ }
2732
+ }
2733
+ return updated;
2734
+ })
2735
+ .then((updated) => {
2736
+ if (updated) {
2737
+ success.push(notificationSummaryId);
2738
+ }
2739
+ else {
2740
+ ignored.push(notificationSummaryId);
2741
+ }
2742
+ })
2743
+ .catch((e) => {
2744
+ console.error('firestoreNotificationSummarySendService(): failed updating notification summary', e);
2745
+ failed.push(notificationSummaryId);
2746
+ });
2747
+ });
2748
+ const sendResult = {
2749
+ success,
2750
+ failed,
2751
+ ignored
2752
+ };
2753
+ return sendResult;
2754
+ };
2755
+ }
2756
+ };
2757
+ return sendService;
2758
+ }
2759
+
2760
+ /**
2761
+ * NotificationTextSendService that ignores sending all messages.
2762
+ *
2763
+ * This is useful for cases where your app may eventually want to send text notifications and want the rest of your app configured like it currently does.
2764
+ *
2765
+ * @returns
2766
+ */
2767
+ function ignoreSendNotificationTextSendService() {
2768
+ const sendService = {
2769
+ async buildSendInstanceForTextNotificationMessages(notificationMessages) {
2770
+ return async () => {
2771
+ const success = [];
2772
+ const failed = [];
2773
+ const ignored = notificationMessages.map((x) => x.inputContext.recipient.t);
2774
+ const sendResult = {
2775
+ success,
2776
+ failed,
2777
+ ignored
2778
+ };
2779
+ return sendResult;
2780
+ };
2781
+ }
2782
+ };
2783
+ return sendService;
2784
+ }
2785
+
2786
+ /**
2787
+ * A basic NotificationTaskService implementation.
2788
+ */
2789
+ function notificationTaskService(config) {
2790
+ const { handlers: inputHandlers } = config;
2791
+ const handlers = {};
2792
+ inputHandlers.forEach((handlerConfig) => {
2793
+ const { type } = handlerConfig;
2794
+ handlers[type] = handlerForConfig(handlerConfig);
2795
+ });
2796
+ function handlerForConfig(handlerConfig) {
2797
+ const { flow: inputFlows, allowRunMultipleParts } = handlerConfig;
2798
+ const { included: checkpointFlows, excluded: nonCheckpointFlows } = util.separateValues(inputFlows, (x) => x.checkpoint != null);
2799
+ if (inputFlows.length === 0) {
2800
+ throw new Error('notificationTaskService(): NotificationTaskServiceTaskHandlerConfig must have at least one flow entry.');
2801
+ }
2802
+ else if (nonCheckpointFlows.length > 1) {
2803
+ throw new Error('notificationTaskService(): NotificationTaskServiceTaskHandlerConfig must not have more than one non-checkpoint flow.');
2804
+ }
2805
+ return {
2806
+ handleNotificationTask: async (notificationTask) => {
2807
+ const { checkpoints: completedCheckpoints } = notificationTask;
2808
+ let fn;
2809
+ switch (completedCheckpoints.length) {
2810
+ case 0:
2811
+ fn = (nonCheckpointFlows[0] ?? checkpointFlows[0])?.fn;
2812
+ break;
2813
+ default:
2814
+ const completedCheckpointsSet = new Set(completedCheckpoints);
2815
+ /**
2816
+ * Find the next flow function that hasn't had its checkpoint completed yet.
2817
+ */
2818
+ const nextCheckpoint = checkpointFlows.find((x) => !completedCheckpointsSet.has(x.checkpoint));
2819
+ fn = nextCheckpoint?.fn;
2820
+ break;
2821
+ }
2822
+ let result;
2823
+ if (fn) {
2824
+ result = await fn(notificationTask);
2825
+ // if allowRunMultipleParts is true, and the result doesn't have a canRunNextCheckpoint value, then set it to true.
2826
+ if (allowRunMultipleParts && result.canRunNextCheckpoint == null) {
2827
+ result.canRunNextCheckpoint = true;
2828
+ }
2829
+ }
2830
+ else {
2831
+ result = {
2832
+ completion: true // if there are no functions remaining, then the task is complete
2833
+ };
2834
+ }
2835
+ return result;
2836
+ }
2837
+ };
2838
+ }
2839
+ return {
2840
+ isKnownNotificationTaskType: (notificationTaskType) => {
2841
+ return handlers[notificationTaskType] !== undefined;
2842
+ },
2843
+ taskHandlerForNotificationTaskType: (notificationTaskType) => handlers[notificationTaskType]
2844
+ };
2845
+ }
2846
+
2847
+ /**
2848
+ * Creates a NotificationTaskSubtaskNotificationTaskHandlerFactory.
2849
+ */
2850
+ function notificationTaskSubtaskNotificationTaskHandlerFactory(factoryConfig) {
2851
+ const { taskType, subtaskHandlerFunctionName: subtaskHandlerName, inputFunction, defaultCleanup, cleanupFunction, buildUpdateMetadata: inputBuildUpdateMetadata } = factoryConfig;
2852
+ return (subtaskHandlerConfig) => {
2853
+ const { processors: inputProcessors, maxCleanupRetryAttempts: inputMaxCleanupRetryAttempts, cleanupRetryDelay: inputCleanupRetryDelay, defaultAllowRunMultipleParts } = subtaskHandlerConfig;
2854
+ const maxCleanupRetryAttempts = inputMaxCleanupRetryAttempts ?? firebase.DEFAULT_NOTIFICATION_TASK_SUBTASK_CLEANUP_RETRY_ATTEMPTS;
2855
+ const cleanupRetryDelay = inputCleanupRetryDelay ?? firebase.DEFAULT_NOTIFICATION_TASK_SUBTASK_CLEANUP_RETRY_DELAY;
2856
+ const buildUpdateMetadata = inputBuildUpdateMetadata ?? (() => undefined);
2857
+ const processors = {};
2858
+ inputProcessors.forEach((processorConfig) => {
2859
+ const { target } = processorConfig;
2860
+ processors[target] = processorFunctionForConfig(processorConfig);
2861
+ });
2862
+ /**
2863
+ * Structure is similar to notificationTaskService(), but contained to handle the subtasks.
2864
+ */
2865
+ function processorFunctionForConfig(processorConfig) {
2866
+ const { flow: inputFlows, cleanup, allowRunMultipleParts: processorAllowRunMultipleParts } = processorConfig;
2867
+ const { included: subtaskFlows, excluded: nonSubtaskFlows } = util.separateValues(inputFlows, (x) => x.subtask != null);
2868
+ const allowRunMultipleParts = processorAllowRunMultipleParts ?? defaultAllowRunMultipleParts;
2869
+ if (inputFlows.length === 0) {
2870
+ throw new Error(`${subtaskHandlerName}(): NotificationTaskSubtaskProcessorConfig must have at least one flow entry.`);
2871
+ }
2872
+ else if (nonSubtaskFlows.length > 1) {
2873
+ throw new Error(`${subtaskHandlerName}(): NotificationTaskSubtaskProcessorConfig must not have more than one non-subtask flow.`);
2874
+ }
2875
+ const allKnownSubtasks = util.unique(inputFlows.map((x) => x.subtask));
2876
+ return {
2877
+ process: async (input) => {
2878
+ const { notificationTask, completedSubtasks, subtaskData } = input;
2879
+ let fn;
2880
+ switch (completedSubtasks.length) {
2881
+ case 0:
2882
+ fn = (nonSubtaskFlows[0] ?? subtaskFlows[0])?.fn;
2883
+ break;
2884
+ default:
2885
+ const completedSubtasksSet = new Set(completedSubtasks);
2886
+ /**
2887
+ * Find the next flow function that hasn't had its checkpoint completed yet.
2888
+ */
2889
+ const nextSubtask = subtaskFlows.find((x) => !completedSubtasksSet.has(x.subtask));
2890
+ fn = nextSubtask?.fn;
2891
+ break;
2892
+ }
2893
+ let result;
2894
+ if (fn) {
2895
+ /*
2896
+ * This section is similar to handleNotificationTask() in notification.action.server.ts,
2897
+ * but is modified to handle the subtasks. The main difference is the attempt count is maintained,
2898
+ * and instead is available via the normal NotificationTask attempts details.
2899
+ */
2900
+ const subtaskResult = await fn(input);
2901
+ const { completion: subtaskCompletion, updateMetadata: subtaskUpdateMetadata, delayUntil, canRunNextCheckpoint } = subtaskResult;
2902
+ let allSubtasksDone = false;
2903
+ let sfps = completedSubtasks;
2904
+ // update the task metadata to reflect the changes
2905
+ switch (subtaskCompletion) {
2906
+ case true:
2907
+ allSubtasksDone = true;
2908
+ break;
2909
+ case false:
2910
+ // remove any completions, if applicable
2911
+ sfps = removeFromCompletionsArrayWithTaskResult(sfps, subtaskResult);
2912
+ break;
2913
+ default:
2914
+ sfps = util.unique([
2915
+ ...removeFromCompletionsArrayWithTaskResult(sfps, subtaskResult), // remove any completions, if applicable
2916
+ ...util.asArray(subtaskCompletion)
2917
+ ]);
2918
+ const completedSubtasksSet = new Set(sfps);
2919
+ const incompleteSubtasks = allKnownSubtasks.filter((x) => !completedSubtasksSet.has(x));
2920
+ allSubtasksDone = incompleteSubtasks.length === 0;
2921
+ break;
2922
+ }
2923
+ // configure the update metadata result
2924
+ const sd = {
2925
+ ...subtaskData,
2926
+ ...subtaskUpdateMetadata
2927
+ };
2928
+ /**
2929
+ * This is updating the metadata for the NotificationTask, which has a nested data
2930
+ */
2931
+ const baseUpdateMetadata = {
2932
+ ...notificationTask.data,
2933
+ sfps,
2934
+ sd
2935
+ };
2936
+ let updateMetadata = (await buildUpdateMetadata(baseUpdateMetadata, input));
2937
+ if (updateMetadata) {
2938
+ // inject sfps and sd back in
2939
+ updateMetadata = {
2940
+ ...updateMetadata,
2941
+ sfps,
2942
+ sd
2943
+ };
2944
+ }
2945
+ else {
2946
+ updateMetadata = baseUpdateMetadata;
2947
+ }
2948
+ const nextCanRunNextCheckpoint = canRunNextCheckpoint ?? allowRunMultipleParts;
2949
+ result = {
2950
+ completion: allSubtasksDone ? ['processing'] : firebase.delayCompletion(), // return processing until all subtasks are complete.
2951
+ updateMetadata,
2952
+ canRunNextCheckpoint: nextCanRunNextCheckpoint,
2953
+ allCompletedSubTasks: sfps,
2954
+ delayUntil // delay is passed through
2955
+ };
2956
+ }
2957
+ else {
2958
+ // no more subtasks to process, and no metadata changes. Mark as processing complete and continue.
2959
+ result = firebase.completeSubtaskProcessingAndScheduleCleanupTaskResult();
2960
+ }
2961
+ return result;
2962
+ },
2963
+ cleanup
2964
+ };
2965
+ }
2966
+ function useInputDataFactory(fn) {
2967
+ return async (notificationTask) => {
2968
+ const { data } = notificationTask;
2969
+ let result;
2970
+ if (data) {
2971
+ try {
2972
+ const inputFunctionResult = await inputFunction(data, notificationTask);
2973
+ result = await fn(notificationTask, inputFunctionResult, data);
2974
+ }
2975
+ catch (e) {
2976
+ if (e instanceof NotificationTaskSubTaskMissingRequiredDataTermination) {
2977
+ // Task is complete if the required data no longer exists. Nothing to cleanup.
2978
+ result = firebase.notificationTaskComplete();
2979
+ }
2980
+ else {
2981
+ // rethrow the error
2982
+ throw e;
2983
+ }
2984
+ }
2985
+ }
2986
+ else {
2987
+ // Improperly configured task. Complete immediately.
2988
+ result = firebase.notificationTaskComplete();
2989
+ }
2990
+ return result;
2991
+ };
2992
+ }
2993
+ const result = {
2994
+ type: taskType,
2995
+ flow: [
2996
+ {
2997
+ checkpoint: firebase.NOTIFICATION_TASK_SUBTASK_CHECKPOINT_PROCESSING,
2998
+ fn: useInputDataFactory(async (notificationTask, inputFunctionResult, data) => {
2999
+ let result;
3000
+ const baseInput = {
3001
+ ...inputFunctionResult,
3002
+ notificationTask
3003
+ };
3004
+ const { target } = baseInput;
3005
+ if (target) {
3006
+ const processor = processors[target];
3007
+ if (processor) {
3008
+ const { sd: subtaskData, sfps: completedSubtasks } = data;
3009
+ const input = {
3010
+ ...baseInput,
3011
+ target,
3012
+ completedSubtasks: completedSubtasks ?? [],
3013
+ subtaskData
3014
+ };
3015
+ result = await processor.process(input);
3016
+ }
3017
+ else {
3018
+ // processor is unknown. Complete the task.
3019
+ result = firebase.completeSubtaskProcessingAndScheduleCleanupTaskResult();
3020
+ }
3021
+ }
3022
+ else {
3023
+ // target is unknown. Complete the task.
3024
+ result = firebase.completeSubtaskProcessingAndScheduleCleanupTaskResult();
3025
+ }
3026
+ return result;
3027
+ })
3028
+ },
3029
+ {
3030
+ checkpoint: firebase.NOTIFICATION_TASK_SUBTASK_CHECKPOINT_CLEANUP,
3031
+ fn: useInputDataFactory(async (notificationTask, inputFunctionResult, data) => {
3032
+ let result;
3033
+ let cleanupFunctionInput = {
3034
+ ...inputFunctionResult,
3035
+ notificationTask
3036
+ };
3037
+ const { target } = cleanupFunctionInput;
3038
+ let cleanupInstructions;
3039
+ if (target) {
3040
+ const processor = processors[target];
3041
+ if (processor && processor.cleanup) {
3042
+ const { sd: subtaskData, sfps: completedSubtasks } = data;
3043
+ const input = {
3044
+ ...cleanupFunctionInput,
3045
+ notificationTask,
3046
+ completedSubtasks: completedSubtasks ?? [],
3047
+ target,
3048
+ subtaskData
3049
+ };
3050
+ cleanupInstructions = (await processor.cleanup(input, defaultCleanup)) ?? (await defaultCleanup(cleanupFunctionInput));
3051
+ cleanupFunctionInput = input;
3052
+ }
3053
+ else {
3054
+ // processor is unknown. Complete the task.
3055
+ cleanupInstructions = await defaultCleanup(cleanupFunctionInput);
3056
+ }
3057
+ }
3058
+ else {
3059
+ // target is unknown. Complete the task.
3060
+ cleanupInstructions = await defaultCleanup(cleanupFunctionInput);
3061
+ }
3062
+ if (cleanupInstructions.cleanupSuccess === false && notificationTask.currentCheckpointSendAttempts <= maxCleanupRetryAttempts) {
3063
+ result = firebase.notificationTaskDelayRetry(cleanupInstructions.delayRetryUntil ?? cleanupRetryDelay);
3064
+ }
3065
+ else {
3066
+ result = await cleanupFunction(cleanupFunctionInput, cleanupInstructions);
3067
+ }
3068
+ return result;
3069
+ })
3070
+ }
3071
+ ]
3072
+ };
3073
+ return result;
3074
+ };
3075
+ }
3076
+ // MARK: Internally Handled Errors
3077
+ /**
3078
+ * Thrown when a subtask no longer has data available to continue processing.
3079
+ *
3080
+ * The subtask will be marked as immediately complete, and no cleanup will occur.
3081
+ *
3082
+ * This is useful in cases where the underlying models or data that the subtask rely on are deleted (and those models were also required for cleanup) so the task can be marked as complete without attempting cleanup.
3083
+ */
3084
+ class NotificationTaskSubTaskMissingRequiredDataTermination extends makeError.BaseError {
3085
+ }
3086
+ /**
3087
+ * Creates a NotificationTaskSubTaskMissingRequiredDataTermination.
3088
+ */
3089
+ function notificationTaskSubTaskMissingRequiredDataTermination() {
3090
+ return new NotificationTaskSubTaskMissingRequiredDataTermination();
3091
+ }
3092
+
3093
+ function storageFileModelAlreadyInitializedError() {
3094
+ return firebaseServer.preconditionConflictError({
3095
+ message: `This model has already been initialized.`,
3096
+ code: firebase.STORAGE_FILE_MODEL_ALREADY_INITIALIZED_ERROR_CODE
3097
+ });
3098
+ }
3099
+ function storageFileNotFlaggedForGroupsSyncError() {
3100
+ return firebaseServer.preconditionConflictError({
3101
+ message: `This StorageFile has not been flagged for sync with its groups.`,
3102
+ code: firebase.STORAGE_FILE_NOT_FLAGGED_FOR_GROUPS_SYNC_ERROR_CODE
3103
+ });
3104
+ }
3105
+ function uploadedFileDoesNotExistError() {
3106
+ return firebaseServer.preconditionConflictError({
3107
+ message: `The target uploaded file does not exist.`,
3108
+ code: firebase.UPLOADED_FILE_DOES_NOT_EXIST_ERROR_CODE
3109
+ });
3110
+ }
3111
+ function uploadedFileIsNotAllowedToBeInitializedError() {
3112
+ return firebaseServer.preconditionConflictError({
3113
+ message: `The target uploaded file is not allowed to be initialized.`,
3114
+ code: firebase.UPLOADED_FILE_NOT_ALLOWED_TO_BE_INITIALIZED_ERROR_CODE
3115
+ });
3116
+ }
3117
+ function uploadedFileInitializationFailedError(data) {
3118
+ return firebaseServer.internalServerError({
3119
+ message: `The target uploaded file initialization failed with result type "${data.resultType}".`,
3120
+ code: firebase.UPLOADED_FILE_INITIALIZATION_FAILED_ERROR_CODE,
3121
+ data: {
3122
+ resultType: data.resultType
3123
+ }
3124
+ });
3125
+ }
3126
+ function uploadedFileInitializationDiscardedError() {
3127
+ return firebaseServer.internalServerError({
3128
+ message: `The target uploaded file initialization was discarded.`,
3129
+ code: firebase.UPLOADED_FILE_INITIALIZATION_DISCARDED_ERROR_CODE
3130
+ });
3131
+ }
3132
+ function storageFileProcessingNotAllowedForInvalidStateError() {
3133
+ return firebaseServer.preconditionConflictError({
3134
+ message: `The target StorageFileDocument must be in an OK state to be processed and processing not flagged as SHOULD_NOT_PROCESS.`,
3135
+ code: firebase.STORAGE_FILE_PROCESSING_NOT_ALLOWED_FOR_INVALID_STATE_ERROR_CODE
3136
+ });
3137
+ }
3138
+ function storageFileProcessingNotQueuedForProcessingError() {
3139
+ return firebaseServer.preconditionConflictError({
3140
+ message: `The target StorageFileDocument is not queued for processing.`,
3141
+ code: firebase.STORAGE_FILE_PROCESSING_NOT_QUEUED_FOR_PROCESSING_ERROR_CODE
3142
+ });
3143
+ }
3144
+ function storageFileProcessingNotAvailableForTypeError() {
3145
+ return firebaseServer.preconditionConflictError({
3146
+ message: `The target StorageFileDocument is not available for processing.`,
3147
+ code: firebase.STORAGE_FILE_PROCESSING_NOT_AVAILABLE_FOR_TYPE_ERROR_CODE
3148
+ });
3149
+ }
3150
+ function storageFileAlreadyProcessedError() {
3151
+ return firebaseServer.preconditionConflictError({
3152
+ message: `The target StorageFileDocument has already finished processing.`,
3153
+ code: firebase.STORAGE_FILE_ALREADY_PROCESSED_ERROR_CODE
3154
+ });
3155
+ }
3156
+ function storageFileNotFlaggedForDeletionError() {
3157
+ return firebaseServer.preconditionConflictError({
3158
+ message: `The target StorageFileDocument is not flagged for deletion.`,
3159
+ code: firebase.STORAGE_FILE_NOT_FLAGGED_FOR_DELETION_ERROR_CODE
3160
+ });
3161
+ }
3162
+ function storageFileCannotBeDeletedYetError() {
3163
+ return firebaseServer.preconditionConflictError({
3164
+ message: `The target StorageFileDocument cannot be deleted yet.`,
3165
+ code: firebase.STORAGE_FILE_CANNOT_BE_DELETED_YET_ERROR_CODE
3166
+ });
3167
+ }
3168
+ function storageFileGroupQueuedForInitializationError() {
3169
+ return firebaseServer.preconditionConflictError({
3170
+ message: `The target StorageFileGroupDocument is queued for initialization.`,
3171
+ code: firebase.STORAGE_FILE_GROUP_QUEUED_FOR_INITIALIZATION_ERROR_CODE
3172
+ });
3173
+ }
3174
+ function createStorageFileGroupInputError() {
3175
+ return firebaseServer.preconditionConflictError({
3176
+ message: `The model or storageFileId is required for creating a StorageFileGroup.`,
3177
+ code: firebase.STORAGE_FILE_GROUP_CREATE_INPUT_ERROR_CODE
3178
+ });
3179
+ }
3180
+
3181
+ /**
3182
+ * Injection token for the BaseStorageFileServerActionsContext
3183
+ */
3184
+ const BASE_STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN = 'BASE_STORAGE_FILE_SERVER_ACTION_CONTEXT';
3185
+ /**
3186
+ * Injection token for the StorageFileServerActionsContext
3187
+ */
3188
+ const STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN = 'STORAGE_FILE_SERVER_ACTION_CONTEXT';
3189
+ class StorageFileServerActions {
3190
+ }
3191
+ function storageFileServerActions(context) {
3192
+ return {
3193
+ createStorageFile: createStorageFileFactory(context),
3194
+ initializeAllStorageFilesFromUploads: initializeAllStorageFilesFromUploadsFactory(context),
3195
+ initializeStorageFileFromUpload: initializeStorageFileFromUploadFactory(context),
3196
+ updateStorageFile: updateStorageFileFactory(context),
3197
+ processAllQueuedStorageFiles: processAllQueuedStorageFilesFactory(context),
3198
+ processStorageFile: processStorageFileFactory(context),
3199
+ deleteAllQueuedStorageFiles: deleteAllQueuedStorageFilesFactory(context),
3200
+ deleteStorageFile: deleteStorageFileFactory(context),
3201
+ downloadStorageFile: downloadStorageFileFactory(context),
3202
+ createStorageFileGroup: createStorageFileGroupFactory(context),
3203
+ updateStorageFileGroup: updateStorageFileGroupFactory(context),
3204
+ syncStorageFileWithGroups: syncStorageFileWithGroupsFactory(context),
3205
+ syncAllFlaggedStorageFilesWithGroups: syncAllFlaggedStorageFilesWithGroupsFactory(context),
3206
+ regenerateStorageFileGroupContent: regenerateStorageFileGroupContentFactory(context),
3207
+ regenerateAllFlaggedStorageFileGroupsContent: regenerateAllFlaggedStorageFileGroupsContentFactory(context)
3208
+ };
3209
+ }
3210
+ // MARK: Actions
3211
+ function createStorageFileFactory(context) {
3212
+ const { storageFileCollection, firestoreContext, firebaseServerActionTransformFunctionFactory } = context;
3213
+ return firebaseServerActionTransformFunctionFactory(firebase.CreateStorageFileParams, async (params) => {
3214
+ return async () => {
3215
+ const storageFileDocument = null;
3216
+ // TODO: check the file exists, and pull the metadata, and create the document
3217
+ return storageFileDocument;
3218
+ };
3219
+ });
3220
+ }
3221
+ function initializeAllStorageFilesFromUploadsFactory(context) {
3222
+ const { storageService, firebaseServerActionTransformFunctionFactory } = context;
3223
+ const _initializeStorageFileFromUploadFile = _initializeStorageFileFromUploadFileFactory(context);
3224
+ return firebaseServerActionTransformFunctionFactory(firebase.InitializeAllStorageFilesFromUploadsParams, async (params) => {
3225
+ const { folderPath, maxFilesToInitialize, overrideUploadsFolderPath } = params;
3226
+ const fullPath = util.mergeSlashPaths([overrideUploadsFolderPath ?? firebase.UPLOADS_FOLDER_PATH, folderPath]); // only targets the uploads folder
3227
+ return async () => {
3228
+ const folder = storageService.folder(fullPath);
3229
+ const modelKeys = [];
3230
+ let filesVisited = 0;
3231
+ let initializationsSuccessCount = 0;
3232
+ let initializationsFailureCount = 0;
3233
+ await firebase.iterateStorageListFilesByEachFile({
3234
+ folder,
3235
+ includeNestedResults: true,
3236
+ readItemsFromPageResult: (results) => results.result.files(),
3237
+ iterateEachPageItem: async (file) => {
3238
+ const fileInstance = file.file();
3239
+ const initializeResult = await _initializeStorageFileFromUploadFile({ file: fileInstance }).catch(() => null);
3240
+ filesVisited++;
3241
+ if (initializeResult) {
3242
+ initializationsSuccessCount++;
3243
+ modelKeys.push(initializeResult.key);
3244
+ }
3245
+ else {
3246
+ initializationsFailureCount++;
3247
+ }
3248
+ },
3249
+ /**
3250
+ * The maximum number of files to initialize at once.
3251
+ */
3252
+ iterateItemsLimit: maxFilesToInitialize ?? 1000,
3253
+ /**
3254
+ * Iterate four separate pages at a time
3255
+ */
3256
+ maxParallelPages: 4
3257
+ });
3258
+ const result = {
3259
+ modelKeys,
3260
+ filesVisited,
3261
+ initializationsSuccessCount,
3262
+ initializationsFailureCount
3263
+ };
3264
+ return result;
3265
+ };
3266
+ });
3267
+ }
3268
+ function _initializeStorageFileFromUploadFileFactory(context) {
3269
+ const { firestoreContext, storageFileInitializeFromUploadService, notificationExpediteService } = context;
3270
+ const processStorageFileInTransaction = _processStorageFileInTransactionFactory(context);
3271
+ return async (input) => {
3272
+ const { file, expediteProcessing } = input;
3273
+ const { bucketId, pathString } = file.storagePath;
3274
+ // file must exist
3275
+ const exists = await file.exists();
3276
+ if (!exists) {
3277
+ throw uploadedFileDoesNotExistError();
3278
+ }
3279
+ // file must be allowed to be initialized
3280
+ const isAllowedToBeInitialized = await storageFileInitializeFromUploadService.checkFileIsAllowedToBeInitialized(file);
3281
+ if (!isAllowedToBeInitialized) {
3282
+ throw uploadedFileIsNotAllowedToBeInitializedError();
3283
+ }
3284
+ let storageFileDocument;
3285
+ let initializationResult;
3286
+ let httpsError;
3287
+ try {
3288
+ initializationResult = await storageFileInitializeFromUploadService.initializeFromUpload({
3289
+ file
3290
+ });
3291
+ async function deleteFile() {
3292
+ try {
3293
+ // can now delete the uploaded file
3294
+ await file.delete();
3295
+ }
3296
+ catch (e) {
3297
+ // log errors here, but do nothing.
3298
+ console.error(`initializeStorageFileFromUpload(): Error deleting uploaded file (${bucketId}/${pathString})`, e);
3299
+ }
3300
+ }
3301
+ switch (initializationResult.resultType) {
3302
+ case 'success':
3303
+ await deleteFile();
3304
+ if (initializationResult.storageFileDocument) {
3305
+ storageFileDocument = initializationResult.storageFileDocument;
3306
+ // expedite processing if requested
3307
+ if (storageFileDocument != null && expediteProcessing) {
3308
+ const storageFile = await firebaseServer.assertSnapshotData(storageFileDocument);
3309
+ if (storageFile.ps === firebase.StorageFileProcessingState.QUEUED_FOR_PROCESSING) {
3310
+ const expediteInstance = notificationExpediteService.expediteInstance();
3311
+ await firestoreContext.runTransaction(async (transaction) => {
3312
+ expediteInstance.initialize();
3313
+ await processStorageFileInTransaction({
3314
+ storageFileDocument: storageFileDocument,
3315
+ expediteInstance
3316
+ }, transaction);
3317
+ });
3318
+ await expediteInstance.send().catch(() => null);
3319
+ }
3320
+ }
3321
+ }
3322
+ else {
3323
+ httpsError = uploadedFileInitializationDiscardedError();
3324
+ }
3325
+ break;
3326
+ case 'initializer_error':
3327
+ if (initializationResult.initializationError) {
3328
+ throw initializationResult.initializationError; // re-throw the encountered error
3329
+ }
3330
+ break;
3331
+ case 'permanent_initializer_failure':
3332
+ // log the error
3333
+ if (initializationResult.initializationError) {
3334
+ console.warn(`initializeStorageFileFromUpload(): Permanent initializer failure for file (${bucketId}/${pathString})`, initializationResult.initializationError);
3335
+ }
3336
+ // delete the file
3337
+ await deleteFile();
3338
+ // return the error
3339
+ httpsError = uploadedFileInitializationFailedError({
3340
+ resultType: initializationResult.resultType,
3341
+ fileDeleted: true
3342
+ });
3343
+ break;
3344
+ case 'no_determiner_match':
3345
+ case 'no_initializer_configured':
3346
+ default:
3347
+ httpsError = uploadedFileInitializationFailedError({
3348
+ resultType: initializationResult.resultType
3349
+ });
3350
+ console.error(`initializeStorageFileFromUpload(): Unknown file type (${initializationResult.resultType}) encountered for storage file "${bucketId}/${pathString}".`);
3351
+ break;
3352
+ }
3353
+ }
3354
+ catch (e) {
3355
+ console.error(`initializeStorageFileFromUpload(): Error while initializing storage file (${bucketId}/${pathString}) from upload`, e);
3356
+ httpsError = uploadedFileInitializationFailedError({ resultType: 'initializer_error' });
3357
+ }
3358
+ if (httpsError) {
3359
+ throw httpsError;
3360
+ }
3361
+ else if (!storageFileDocument) {
3362
+ throw uploadedFileInitializationDiscardedError(); // throw again for redundancy
3363
+ }
3364
+ return storageFileDocument;
3365
+ };
3366
+ }
3367
+ function initializeStorageFileFromUploadFactory(context) {
3368
+ const { storageService, firebaseServerActionTransformFunctionFactory } = context;
3369
+ const _initializeStorageFileFromUploadFile = _initializeStorageFileFromUploadFileFactory(context);
3370
+ return firebaseServerActionTransformFunctionFactory(firebase.InitializeStorageFileFromUploadParams, async (params) => {
3371
+ const { bucketId, pathString, expediteProcessing } = params;
3372
+ return async () => {
3373
+ const file = storageService.file(bucketId == null ? pathString : { bucketId, pathString });
3374
+ return _initializeStorageFileFromUploadFile({ file, expediteProcessing });
3375
+ };
3376
+ });
3377
+ }
3378
+ function updateStorageFileFactory(context) {
3379
+ const { storageFileCollection, firestoreContext, firebaseServerActionTransformFunctionFactory } = context;
3380
+ return firebaseServerActionTransformFunctionFactory(firebase.UpdateStorageFileParams, async (params) => {
3381
+ const { sdat } = params;
3382
+ return async (storageFileDocument) => {
3383
+ const updateTemplate = {
3384
+ sdat
3385
+ };
3386
+ await storageFileDocument.update(updateTemplate);
3387
+ return storageFileDocument;
3388
+ };
3389
+ });
3390
+ }
3391
+ function updateStorageFileGroupFactory(context) {
3392
+ const { firestoreContext, storageFileGroupCollection, firebaseServerActionTransformFunctionFactory } = context;
3393
+ return firebaseServerActionTransformFunctionFactory(firebase.UpdateStorageFileGroupParams, async (params) => {
3394
+ const { entries } = params;
3395
+ return async (storageFileGroupDocument) => {
3396
+ await firestoreContext.runTransaction(async (transaction) => {
3397
+ const storageFileGroupDocumentInTransaction = storageFileGroupCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(storageFileGroupDocument);
3398
+ const storageFileGroup = await firebaseServer.assertSnapshotData(storageFileGroupDocumentInTransaction);
3399
+ let f = undefined;
3400
+ // update entries
3401
+ if (entries?.length) {
3402
+ f = util.ModelRelationUtility.updateCollection(storageFileGroup.f, entries, {
3403
+ readKey: (x) => x.s,
3404
+ merge: (existing, update) => {
3405
+ const n = update.n === undefined ? existing.n : update.n;
3406
+ return {
3407
+ ...existing,
3408
+ n
3409
+ };
3410
+ }
3411
+ });
3412
+ }
3413
+ const updateTemplate = {
3414
+ f
3415
+ };
3416
+ await storageFileGroupDocumentInTransaction.update(updateTemplate);
3417
+ });
3418
+ return storageFileGroupDocument;
3419
+ };
3420
+ });
3421
+ }
3422
+ function processAllQueuedStorageFilesFactory(context) {
3423
+ const { storageFileCollection, firebaseServerActionTransformFunctionFactory } = context;
3424
+ const processStorageFile = processStorageFileFactory(context);
3425
+ return firebaseServerActionTransformFunctionFactory(firebase.ProcessAllQueuedStorageFilesParams, async (params) => {
3426
+ return async () => {
3427
+ let storageFilesVisited = 0;
3428
+ let storageFilesProcessStarted = 0;
3429
+ let storageFilesFailedStarting = 0;
3430
+ const proceessStorageFileParams = {
3431
+ key: firebase.firestoreDummyKey()
3432
+ };
3433
+ const processStorageFileInstance = await processStorageFile(proceessStorageFileParams);
3434
+ await firebase.iterateFirestoreDocumentSnapshotPairs({
3435
+ documentAccessor: storageFileCollection.documentAccessor(),
3436
+ iterateSnapshotPair: async (snapshotPair) => {
3437
+ storageFilesVisited++;
3438
+ const processStorageFileResult = await processStorageFileInstance(snapshotPair.document).catch(() => null);
3439
+ if (processStorageFileResult) {
3440
+ storageFilesProcessStarted++;
3441
+ }
3442
+ else {
3443
+ storageFilesFailedStarting++;
3444
+ }
3445
+ },
3446
+ constraintsFactory: () => firebase.storageFilesQueuedForProcessingQuery(),
3447
+ queryFactory: storageFileCollection,
3448
+ batchSize: undefined,
3449
+ performTasksConfig: {
3450
+ maxParallelTasks: 10
3451
+ }
3452
+ });
3453
+ const result = {
3454
+ storageFilesVisited,
3455
+ storageFilesProcessStarted,
3456
+ storageFilesFailedStarting
3457
+ };
3458
+ return result;
3459
+ };
3460
+ });
3461
+ }
3462
+ function _processStorageFileInTransactionFactory(context) {
3463
+ const { storageFileCollection, notificationCollectionGroup } = context;
3464
+ return async (input, transaction) => {
3465
+ const { storageFileDocument, storageFile: inputStorageFile, params, expediteInstance } = input;
3466
+ const { checkRetryProcessing, forceRestartProcessing, processAgainIfSuccessful } = params ?? {};
3467
+ const storageFileDocumentInTransaction = storageFileCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(storageFileDocument);
3468
+ const storageFile = inputStorageFile ?? (await firebaseServer.assertSnapshotData(storageFileDocumentInTransaction));
3469
+ async function beginProcessing(overrideExistingTask) {
3470
+ const state = storageFile.fs;
3471
+ // check the storageFile is in the OK state
3472
+ if (state !== firebase.StorageFileState.OK) {
3473
+ throw storageFileProcessingNotAllowedForInvalidStateError();
3474
+ }
3475
+ const createNotificationTaskResult = await firebase.createNotificationDocument({
3476
+ context,
3477
+ transaction,
3478
+ template: firebase.storageFileProcessingNotificationTaskTemplate({
3479
+ storageFileDocument,
3480
+ overrideExistingTask
3481
+ })
3482
+ });
3483
+ await storageFileDocumentInTransaction.update({
3484
+ ps: firebase.StorageFileProcessingState.PROCESSING,
3485
+ pat: new Date(), // set new processing start date
3486
+ pcat: null, // clear processing completion date
3487
+ pn: createNotificationTaskResult.notificationDocument.key
3488
+ });
3489
+ expediteInstance?.enqueueCreateResult(createNotificationTaskResult);
3490
+ }
3491
+ switch (storageFile.ps) {
3492
+ case firebase.StorageFileProcessingState.INIT_OR_NONE:
3493
+ // queue up for processing, unless it has no purpose
3494
+ if (!storageFile.p) {
3495
+ throw storageFileProcessingNotAvailableForTypeError();
3496
+ }
3497
+ else {
3498
+ await beginProcessing(false);
3499
+ }
3500
+ break;
3501
+ case firebase.StorageFileProcessingState.QUEUED_FOR_PROCESSING:
3502
+ // begin processing
3503
+ await beginProcessing(false);
3504
+ break;
3505
+ case firebase.StorageFileProcessingState.PROCESSING:
3506
+ // check if the processing task is still running
3507
+ const shouldCheckProcessing = !util.isThrottled(firebase.STORAGE_FILE_PROCESSING_STUCK_THROTTLE_CHECK_MS, storageFile.pat);
3508
+ if (!storageFile.pn) {
3509
+ await beginProcessing(true); // if no processing task is set, restart processing to recover from the broken state
3510
+ }
3511
+ else {
3512
+ const { pn } = storageFile;
3513
+ const notificationDocument = notificationCollectionGroup.documentAccessorForTransaction(transaction).loadDocumentForKey(pn);
3514
+ if (checkRetryProcessing || shouldCheckProcessing) {
3515
+ const notification = await notificationDocument.snapshotData();
3516
+ if (!notification) {
3517
+ // the notification document is missing. Re-begin processing
3518
+ await beginProcessing(true);
3519
+ }
3520
+ else if (notification.d || forceRestartProcessing) {
3521
+ // if the notification is somehow in the done state but the StorageFile never got notified in the same transaction, requeue.
3522
+ await beginProcessing(true);
3523
+ }
3524
+ // NOTE: We could look at the state of the notification task more, but at this point the task is probably still valid and still running,
3525
+ // so we can only wait on it. In general if the task still exists and is not yet done, then we should wait on it as the
3526
+ // task running system should complete eventually by design.
3527
+ }
3528
+ else if (expediteInstance) {
3529
+ // enqueue the existing notification to be run in the expedite instance
3530
+ expediteInstance.enqueue(notificationDocument);
3531
+ }
3532
+ }
3533
+ break;
3534
+ case firebase.StorageFileProcessingState.DO_NOT_PROCESS:
3535
+ throw storageFileProcessingNotQueuedForProcessingError();
3536
+ case firebase.StorageFileProcessingState.SUCCESS:
3537
+ if (forceRestartProcessing || processAgainIfSuccessful) {
3538
+ await beginProcessing(true);
3539
+ }
3540
+ else {
3541
+ throw storageFileAlreadyProcessedError();
3542
+ }
3543
+ break;
3544
+ }
3545
+ };
3546
+ }
3547
+ function processStorageFileFactory(context) {
3548
+ const { firestoreContext, notificationExpediteService, firebaseServerActionTransformFunctionFactory } = context;
3549
+ const processStorageFileInTransaction = _processStorageFileInTransactionFactory(context);
3550
+ return firebaseServerActionTransformFunctionFactory(firebase.ProcessStorageFileParams, async (params) => {
3551
+ const { runImmediately } = params;
3552
+ return async (storageFileDocument) => {
3553
+ const expediteInstance = notificationExpediteService.expediteInstance();
3554
+ await firestoreContext.runTransaction(async (transaction) => {
3555
+ expediteInstance.initialize();
3556
+ await processStorageFileInTransaction({
3557
+ storageFileDocument,
3558
+ params,
3559
+ expediteInstance
3560
+ }, transaction);
3561
+ });
3562
+ let expediteResult = null;
3563
+ // expedite the task if requested
3564
+ if (runImmediately) {
3565
+ expediteResult = await expediteInstance.send().then((x) => x[0]);
3566
+ }
3567
+ const result = {
3568
+ runImmediately: runImmediately ?? false,
3569
+ expediteResult
3570
+ };
3571
+ return result;
3572
+ };
3573
+ });
3574
+ }
3575
+ function deleteAllQueuedStorageFilesFactory(context) {
3576
+ const { storageFileCollection, firebaseServerActionTransformFunctionFactory } = context;
3577
+ const deleteStorageFile = deleteStorageFileFactory(context);
3578
+ return firebaseServerActionTransformFunctionFactory(firebase.DeleteAllQueuedStorageFilesParams, async (params) => {
3579
+ return async () => {
3580
+ let storageFilesVisited = 0;
3581
+ let storageFilesDeleted = 0;
3582
+ let storageFilesFailedDeleting = 0;
3583
+ const deleteStorageFileInstance = await deleteStorageFile({
3584
+ key: firebase.firestoreDummyKey()
3585
+ });
3586
+ await firebase.iterateFirestoreDocumentSnapshotPairs({
3587
+ documentAccessor: storageFileCollection.documentAccessor(),
3588
+ iterateSnapshotPair: async (snapshotPair) => {
3589
+ const { document: storageFileDocument } = snapshotPair;
3590
+ storageFilesVisited++;
3591
+ const deleteStorageFileResult = await deleteStorageFileInstance(storageFileDocument)
3592
+ .then(() => true)
3593
+ .catch(() => false);
3594
+ if (deleteStorageFileResult) {
3595
+ storageFilesDeleted++;
3596
+ }
3597
+ else {
3598
+ storageFilesFailedDeleting++;
3599
+ }
3600
+ },
3601
+ constraintsFactory: () => firebase.storageFilesQueuedForDeleteQuery(),
3602
+ queryFactory: storageFileCollection,
3603
+ batchSize: undefined,
3604
+ performTasksConfig: {
3605
+ maxParallelTasks: 10
3606
+ }
3607
+ });
3608
+ const result = {
3609
+ storageFilesDeleted,
3610
+ storageFilesFailedDeleting,
3611
+ storageFilesVisited
3612
+ };
3613
+ return result;
3614
+ };
3615
+ });
3616
+ }
3617
+ function deleteStorageFileFactory(context) {
3618
+ const { firestoreContext, storageService, storageFileCollection, firebaseServerActionTransformFunctionFactory } = context;
3619
+ const syncStorageFileWithGroupsInTransaction = _syncStorageFileWithGroupsInTransactionFactory(context);
3620
+ return firebaseServerActionTransformFunctionFactory(firebase.DeleteStorageFileParams, async (params) => {
3621
+ const { force } = params;
3622
+ return async (inputStorageFileDocument) => {
3623
+ await firestoreContext.runTransaction(async (transaction) => {
3624
+ const storageFileDocument = await storageFileCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(inputStorageFileDocument);
3625
+ const storageFile = await firebaseServer.assertSnapshotData(storageFileDocument);
3626
+ const fileAccessor = storageService.file(storageFile);
3627
+ if (!force) {
3628
+ if (!storageFile.sdat) {
3629
+ throw storageFileNotFlaggedForDeletionError();
3630
+ }
3631
+ else if (!util.isPast(storageFile.sdat)) {
3632
+ throw storageFileCannotBeDeletedYetError();
3633
+ }
3634
+ }
3635
+ // remove the storage file from any groups
3636
+ await syncStorageFileWithGroupsInTransaction({ storageFileDocument, storageFile, force: true, removeAllStorageFileGroups: true }, transaction);
3637
+ // delete the file
3638
+ await fileAccessor.delete().catch(() => null);
3639
+ // delete the document
3640
+ await storageFileDocument.accessor.delete();
3641
+ });
3642
+ };
3643
+ });
3644
+ }
3645
+ function downloadStorageFileFactory(context) {
3646
+ const { storageService, firebaseServerActionTransformFunctionFactory, storageFileCollection } = context;
3647
+ return firebaseServerActionTransformFunctionFactory(firebase.DownloadStorageFileParams, async (params) => {
3648
+ const { key: targetStorageFileDocumentKey, asAdmin, expiresAt, expiresIn: inputExpiresIn, responseDisposition, responseContentType } = params;
3649
+ return async (storageFileDocument) => {
3650
+ // if the StorageFileDocument was not provided, set it from the target key
3651
+ if (!storageFileDocument) {
3652
+ storageFileDocument = storageFileCollection.documentAccessor().loadDocumentForKey(targetStorageFileDocumentKey);
3653
+ }
3654
+ const storageFile = await firebaseServer.assertSnapshotData(storageFileDocument);
3655
+ const fileAccessor = storageService.file(storageFile);
3656
+ let result;
3657
+ if (fileAccessor.getSignedUrl) {
3658
+ const expiresIn = inputExpiresIn ?? util.MS_IN_MINUTE * 30;
3659
+ const expires = util.expirationDetails({ defaultExpiresFromDateToNow: true, expiresAt, expiresIn });
3660
+ let downloadUrlExpiresAt = expires.getExpirationDate();
3661
+ // if they're not an admin, limit the expiration to a max of 30 days.
3662
+ if (downloadUrlExpiresAt && !asAdmin) {
3663
+ const maxExpirationDate = dateFns.addDays(new Date(), 30);
3664
+ downloadUrlExpiresAt = date.findMinDate([downloadUrlExpiresAt, maxExpirationDate]);
3665
+ }
3666
+ const [downloadUrl, metadata] = await Promise.all([
3667
+ fileAccessor.getSignedUrl({
3668
+ action: 'read',
3669
+ expiresAt: downloadUrlExpiresAt ?? undefined,
3670
+ responseDisposition: responseDisposition ?? undefined, // can be set by anyone
3671
+ responseType: asAdmin ? (responseContentType ?? undefined) : undefined // can only be set by admins
3672
+ }),
3673
+ fileAccessor.getMetadata()
3674
+ ]);
3675
+ result = {
3676
+ url: downloadUrl,
3677
+ fileName: metadata.name ? util.slashPathDetails(metadata.name).end : undefined,
3678
+ mimeType: responseContentType ?? metadata.contentType,
3679
+ expiresAt: util.unixDateTimeSecondsNumberFromDate(downloadUrlExpiresAt)
3680
+ };
3681
+ }
3682
+ else {
3683
+ throw firebaseServer.internalServerError('Signed url function appears to not be avalable.');
3684
+ }
3685
+ return result;
3686
+ };
3687
+ });
3688
+ }
3689
+ function createStorageFileGroupInTransactionFactory(context) {
3690
+ const { storageFileGroupCollection } = context;
3691
+ return async (params, transaction) => {
3692
+ const { now: inputNow, skipCreate, template } = params;
3693
+ const now = inputNow ?? new Date();
3694
+ const storageFileGroupDocument = firebase.loadStorageFileGroupDocumentForReferencePair(params, storageFileGroupCollection.documentAccessorForTransaction(transaction));
3695
+ const storageFileGroupTemplate = {
3696
+ o: firebase.firestoreDummyKey(), // set during initialization
3697
+ cat: now,
3698
+ s: true, // requires initialization
3699
+ f: [],
3700
+ ...template
3701
+ };
3702
+ if (!skipCreate) {
3703
+ await storageFileGroupDocument.create(storageFileGroupTemplate);
3704
+ }
3705
+ return {
3706
+ storageFileGroupTemplate,
3707
+ storageFileGroupDocument
3708
+ };
3709
+ };
3710
+ }
3711
+ function createStorageFileGroupFactory(context) {
3712
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory } = context;
3713
+ const createStorageFileGroupInTransaction = createStorageFileGroupInTransactionFactory(context);
3714
+ return firebaseServerActionTransformFunctionFactory(firebase.CreateStorageFileGroupParams, async (params) => {
3715
+ const { model, storageFileId } = params;
3716
+ const storageFileGroupRelatedModelKey = model ? model : storageFileId ? firebase.inferKeyFromTwoWayFlatFirestoreModelKey(storageFileId) : undefined;
3717
+ if (!storageFileGroupRelatedModelKey) {
3718
+ throw createStorageFileGroupInputError();
3719
+ }
3720
+ return async () => {
3721
+ const result = await firestoreContext.runTransaction(async (transaction) => {
3722
+ const { storageFileGroupDocument } = await createStorageFileGroupInTransaction({ storageFileGroupRelatedModelKey }, transaction);
3723
+ return storageFileGroupDocument;
3724
+ });
3725
+ return result;
3726
+ };
3727
+ });
3728
+ }
3729
+ function _syncStorageFileWithGroupsInTransactionFactory(context) {
3730
+ const { storageFileCollection, storageFileGroupCollection } = context;
3731
+ const createStorageFileGroupInTransaction = createStorageFileGroupInTransactionFactory(context);
3732
+ return async (input, transaction) => {
3733
+ const { storageFileDocument, storageFile: inputStorageFile, force, removeAllStorageFileGroups, skipStorageFileUpdate } = input;
3734
+ const storageFileDocumentInTransaction = storageFileCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(storageFileDocument);
3735
+ const storageFileGroupDocumentAccessor = storageFileGroupCollection.documentAccessorForTransaction(transaction);
3736
+ const storageFile = inputStorageFile ?? (await firebaseServer.assertSnapshotData(storageFileDocumentInTransaction));
3737
+ if (!storageFile.gs && !force) {
3738
+ throw storageFileNotFlaggedForGroupsSyncError();
3739
+ }
3740
+ const g = storageFile.g ?? [];
3741
+ const storageFileGroupDocuments = firebase.loadDocumentsForIds(storageFileGroupDocumentAccessor, g);
3742
+ const storageFileGroupPairs = await firebase.getDocumentSnapshotDataPairs(storageFileGroupDocuments);
3743
+ let storageFilesGroupsCreated = 0;
3744
+ let storageFilesGroupsUpdated = 0;
3745
+ await util.performAsyncTasks(storageFileGroupPairs, async (storageFileGroupPair) => {
3746
+ const { data: storageFileGroup, document: storageFileGroupDocument } = storageFileGroupPair;
3747
+ const existsInStorageFileGroup = storageFileGroup?.f.some((x) => x.s === storageFileDocument.id);
3748
+ const change = removeAllStorageFileGroups ? (existsInStorageFileGroup ? 'remove' : undefined) : !existsInStorageFileGroup ? 'add' : undefined;
3749
+ switch (change) {
3750
+ case 'add':
3751
+ // add it if it doesn't exist
3752
+ const createTemplate = firebase.calculateStorageFileGroupEmbeddedFileUpdate({
3753
+ storageFileGroup: storageFileGroup ?? { f: [] },
3754
+ insert: [
3755
+ {
3756
+ s: storageFileDocument.id
3757
+ }
3758
+ ],
3759
+ allowRecalculateRegenerateFlag: false
3760
+ });
3761
+ if (!storageFileGroup) {
3762
+ // if the group does not exist, then create it
3763
+ await createStorageFileGroupInTransaction({ storageFileGroupDocument, template: createTemplate }, transaction);
3764
+ storageFilesGroupsCreated += 1;
3765
+ }
3766
+ else {
3767
+ // if the group exists, then update it
3768
+ await storageFileGroupDocument.update(createTemplate);
3769
+ storageFilesGroupsUpdated += 1;
3770
+ }
3771
+ break;
3772
+ case 'remove':
3773
+ // remove it
3774
+ const removeTemplate = firebase.calculateStorageFileGroupEmbeddedFileUpdate({
3775
+ storageFileGroup: storageFileGroup ?? { f: [] },
3776
+ remove: [storageFileDocument.id]
3777
+ });
3778
+ await storageFileGroupDocument.update(removeTemplate);
3779
+ storageFilesGroupsUpdated += 1;
3780
+ break;
3781
+ }
3782
+ });
3783
+ const result = {
3784
+ storageFilesGroupsCreated,
3785
+ storageFilesGroupsUpdated
3786
+ };
3787
+ // update the storage file to no longer be flagged for sync
3788
+ if (!skipStorageFileUpdate) {
3789
+ await storageFileDocumentInTransaction.update({
3790
+ gs: false
3791
+ });
3792
+ }
3793
+ return result;
3794
+ };
3795
+ }
3796
+ function syncStorageFileWithGroupsFactory(context) {
3797
+ const { firestoreContext, storageFileCollection, storageFileGroupCollection, firebaseServerActionTransformFunctionFactory } = context;
3798
+ const syncStorageFileWithGroupsInTransaction = _syncStorageFileWithGroupsInTransactionFactory(context);
3799
+ return firebaseServerActionTransformFunctionFactory(firebase.SyncStorageFileWithGroupsParams, async (params) => {
3800
+ const { force } = params;
3801
+ return async (storageFileDocument) => {
3802
+ return firestoreContext.runTransaction(async (transaction) => syncStorageFileWithGroupsInTransaction({ storageFileDocument, force }, transaction));
3803
+ };
3804
+ });
3805
+ }
3806
+ function syncAllFlaggedStorageFilesWithGroupsFactory(context) {
3807
+ const { firebaseServerActionTransformFunctionFactory, storageFileCollection } = context;
3808
+ const syncStorageFileWithGroups = syncStorageFileWithGroupsFactory(context);
3809
+ return firebaseServerActionTransformFunctionFactory(firebase.SyncAllFlaggedStorageFilesWithGroupsParams, async (params) => {
3810
+ return async () => {
3811
+ const syncStorageFileWithGroupsInstance = await syncStorageFileWithGroups({
3812
+ key: firebase.firestoreDummyKey(),
3813
+ force: true // force anyways; they should all be flagged for sync when the query hits
3814
+ });
3815
+ let storageFilesSynced = 0;
3816
+ let storageFilesGroupsCreated = 0;
3817
+ let storageFilesGroupsUpdated = 0;
3818
+ await firebase.iterateFirestoreDocumentSnapshotPairBatches({
3819
+ documentAccessor: storageFileCollection.documentAccessor(),
3820
+ iterateSnapshotPairsBatch: async (snapshotPairBatch) => {
3821
+ // only sync StorageFiles that are flagged for sync
3822
+ await util.runAsyncTasksForValues(snapshotPairBatch.filter((x) => x.data.gs), async (snapshotPair) => {
3823
+ const { document: storageFileDocument } = snapshotPair;
3824
+ const result = await syncStorageFileWithGroupsInstance(storageFileDocument);
3825
+ storageFilesSynced += 1;
3826
+ storageFilesGroupsCreated += result.storageFilesGroupsCreated;
3827
+ storageFilesGroupsUpdated += result.storageFilesGroupsUpdated;
3828
+ }, {
3829
+ maxParallelTasks: 10, // can update 10 storageFiles/Groups at the same time
3830
+ nonConcurrentTaskKeyFactory: (x) => x.data.g // do not update the same group at the same time
3831
+ });
3832
+ },
3833
+ queryFactory: storageFileCollection,
3834
+ constraintsFactory: () => firebase.storageFileFlaggedForSyncWithGroupsQuery(),
3835
+ performTasksConfig: {
3836
+ sequential: true // run batches sequentially to avoid contention in updating a StorageFileGroup
3837
+ },
3838
+ totalSnapshotsLimit: 1000,
3839
+ limitPerCheckpoint: 100
3840
+ });
3841
+ const result = {
3842
+ storageFilesSynced,
3843
+ storageFilesGroupsCreated,
3844
+ storageFilesGroupsUpdated
3845
+ };
3846
+ return result;
3847
+ };
3848
+ });
3849
+ }
3850
+ function regenerateStorageFileGroupContentFactory(context) {
3851
+ const { firestoreContext, storageService, storageFileCollection, storageFileGroupCollection, firebaseServerActionTransformFunctionFactory } = context;
3852
+ const processStorageFileInTransaction = _processStorageFileInTransactionFactory(context);
3853
+ return firebaseServerActionTransformFunctionFactory(firebase.RegenerateStorageFileGroupContentParams, async (params) => {
3854
+ const { force } = params;
3855
+ const createStorageFileDocumentPair = firebase.createStorageFileDocumentPairFactory({
3856
+ defaultCreationType: firebase.StorageFileCreationType.FOR_STORAGE_FILE_GROUP
3857
+ });
3858
+ return async (storageFileGroupDocument) => {
3859
+ return firestoreContext.runTransaction(async (transaction) => {
3860
+ const storageFileGroupDocumentInTransaction = storageFileGroupCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(storageFileGroupDocument);
3861
+ const storageFileGroup = await firebaseServer.assertSnapshotData(storageFileGroupDocumentInTransaction);
3862
+ const storageFileDocumentAccessor = storageFileCollection.documentAccessorForTransaction(transaction);
3863
+ const { o, zsf, s } = storageFileGroup;
3864
+ // must not be queued for initialization
3865
+ if (s) {
3866
+ throw storageFileGroupQueuedForInitializationError();
3867
+ }
3868
+ const existingZipStorageFileDocument = zsf ? storageFileDocumentAccessor.loadDocumentForId(zsf) : undefined;
3869
+ const [existingZipStorageFilePair] = await Promise.all([existingZipStorageFileDocument ? firebase.getDocumentSnapshotDataPair(existingZipStorageFileDocument) : undefined]);
3870
+ let contentStorageFilesFlaggedForProcessing = 0;
3871
+ const updateTemplate = {
3872
+ re: false // clear the regeneration flag
3873
+ };
3874
+ // For each content type, create/update/flag the StorageFile for processing that type
3875
+ const { regenerateZip } = firebase.calculateStorageFileGroupRegeneration({ storageFileGroup, force });
3876
+ if (regenerateZip) {
3877
+ // check that the storageFile exists, and if it doesn't, create a new one
3878
+ if (!existingZipStorageFilePair?.data) {
3879
+ const zipStorageFile = storageService.file(firebase.storageFileGroupZipFileStoragePath(storageFileGroupDocument.id));
3880
+ // create a new StorageFile
3881
+ const { storageFileDocument } = await createStorageFileDocumentPair({
3882
+ storagePathRef: zipStorageFile,
3883
+ accessor: storageFileDocumentAccessor,
3884
+ parentStorageFileGroup: storageFileGroupDocument,
3885
+ purpose: firebase.STORAGE_FILE_GROUP_ZIP_STORAGE_FILE_PURPOSE,
3886
+ shouldBeProcessed: true,
3887
+ ownershipKey: o,
3888
+ metadata: {
3889
+ sfg: storageFileGroupDocument.id
3890
+ }
3891
+ });
3892
+ updateTemplate.zsf = storageFileDocument.id;
3893
+ }
3894
+ else {
3895
+ // flag it for processing again
3896
+ await processStorageFileInTransaction({ params: { processAgainIfSuccessful: true }, storageFileDocument: existingZipStorageFilePair.document, storageFile: existingZipStorageFilePair.data }, transaction);
3897
+ }
3898
+ contentStorageFilesFlaggedForProcessing += 1;
3899
+ }
3900
+ // update the StorageFileGroup
3901
+ await storageFileGroupDocumentInTransaction.update(updateTemplate);
3902
+ const result = {
3903
+ contentStorageFilesFlaggedForProcessing
3904
+ };
3905
+ return result;
3906
+ });
3907
+ };
3908
+ });
3909
+ }
3910
+ function regenerateAllFlaggedStorageFileGroupsContentFactory(context) {
3911
+ const { firebaseServerActionTransformFunctionFactory, storageFileGroupCollection } = context;
3912
+ const regenerateStorageFileGroupContent = regenerateStorageFileGroupContentFactory(context);
3913
+ return firebaseServerActionTransformFunctionFactory(firebase.RegenerateAllFlaggedStorageFileGroupsContentParams, async (params) => {
3914
+ return async () => {
3915
+ const regenerateStorageFileGroupContentInstance = await regenerateStorageFileGroupContent({
3916
+ key: firebase.firestoreDummyKey()
3917
+ });
3918
+ let storageFileGroupsUpdated = 0;
3919
+ let contentStorageFilesFlaggedForProcessing = 0;
3920
+ await firebase.iterateFirestoreDocumentSnapshotPairs({
3921
+ documentAccessor: storageFileGroupCollection.documentAccessor(),
3922
+ iterateSnapshotPair: async (snapshotPair) => {
3923
+ const { data: storageFileGroup } = snapshotPair;
3924
+ if (!storageFileGroup.s) {
3925
+ const result = await regenerateStorageFileGroupContentInstance(snapshotPair.document);
3926
+ storageFileGroupsUpdated += 1;
3927
+ contentStorageFilesFlaggedForProcessing += result.contentStorageFilesFlaggedForProcessing;
3928
+ }
3929
+ },
3930
+ queryFactory: storageFileGroupCollection,
3931
+ constraintsFactory: () => firebase.storageFileGroupsFlaggedForContentRegenerationQuery(),
3932
+ performTasksConfig: {
3933
+ maxParallelTasks: 10
3934
+ },
3935
+ totalSnapshotsLimit: 1000,
3936
+ limitPerCheckpoint: 100
3937
+ });
3938
+ const result = {
3939
+ storageFileGroupsUpdated,
3940
+ contentStorageFilesFlaggedForProcessing
3941
+ };
3942
+ return result;
3943
+ };
3944
+ });
3945
+ }
3946
+
3947
+ // MARK: StorageFileInitServerActionsContextConfig
3948
+ /**
3949
+ * Token to access/override the StorageFileTemplateService's defaults records.
3950
+ */
3951
+ const STORAGE_FILE_INIT_SERVER_ACTIONS_CONTEXT_CONFIG_TOKEN = 'STORAGE_FILE_INIT_SERVER_ACTIONS_CONTEXT_CONFIG';
3952
+ const MAKE_TEMPLATE_FOR_STORAGEFILE_RELATED_MODEL_INITIALIZATION_FUNCTION_DELETE_RESPONSE = false;
3953
+ class StorageFileInitServerActions {
3954
+ }
3955
+ function storageFileInitServerActions(context) {
3956
+ return {
3957
+ initializeStorageFileGroup: initializeStorageFileGroupFactory(context),
3958
+ initializeAllApplicableStorageFileGroups: initializeAllApplicableStorageFileGroupsFactory(context)
3959
+ };
3960
+ }
3961
+ async function initializeStorageFileModelInTransaction(input) {
3962
+ const { makeTemplateFunction, throwErrorIfAlreadyInitialized, transaction, document: documentInTransaction, data: storageFileModel } = input;
3963
+ let initialized = false;
3964
+ const alreadyInitialized = !storageFileModel.s;
3965
+ if (!alreadyInitialized) {
3966
+ const flatModelKey = documentInTransaction.id;
3967
+ const modelKey = firebase.inferKeyFromTwoWayFlatFirestoreModelKey(flatModelKey);
3968
+ const modelCollectionName = firebase.firestoreModelKeyCollectionName(modelKey);
3969
+ const input = {
3970
+ transaction,
3971
+ flatModelKey,
3972
+ modelKey,
3973
+ collectionName: modelCollectionName
3974
+ };
3975
+ const template = await makeTemplateFunction(input);
3976
+ if (template === false) {
3977
+ await documentInTransaction.accessor.delete();
3978
+ }
3979
+ else if (template == null) {
3980
+ await documentInTransaction.update({
3981
+ s: false, // set false when "f" is set true
3982
+ fi: true
3983
+ });
3984
+ }
3985
+ else {
3986
+ initialized = true;
3987
+ await documentInTransaction.update({
3988
+ //
3989
+ ...template,
3990
+ m: undefined, // should not be changed
3991
+ s: null, // is now initialized.
3992
+ fi: false // set false
3993
+ });
3994
+ }
3995
+ }
3996
+ else if (throwErrorIfAlreadyInitialized) {
3997
+ throw storageFileModelAlreadyInitializedError();
3998
+ }
3999
+ return {
4000
+ initialized,
4001
+ alreadyInitialized
4002
+ };
4003
+ }
4004
+ function initializeStorageFileGroupInTransactionFactory(context) {
4005
+ const { storageFileGroupCollection, makeTemplateForStorageFileGroupInitialization } = context;
4006
+ return async (params, storageFileGroupDocument, transaction) => {
4007
+ const { throwErrorIfAlreadyInitialized } = params;
4008
+ const storageFileGroupDocumentInTransaction = storageFileGroupCollection.documentAccessorForTransaction(transaction).loadDocumentFrom(storageFileGroupDocument);
4009
+ const storageFileGroup = await firebaseServer.assertSnapshotData(storageFileGroupDocumentInTransaction);
4010
+ return initializeStorageFileModelInTransaction({
4011
+ makeTemplateFunction: async (input) => {
4012
+ const baseTemplate = (await makeTemplateForStorageFileGroupInitialization(input));
4013
+ // template can only define o and any StorageFileGroupContentFlagsData properties
4014
+ return {
4015
+ o: baseTemplate.o,
4016
+ z: baseTemplate.z,
4017
+ re: true // always flag for content regeneration
4018
+ };
4019
+ },
4020
+ throwErrorIfAlreadyInitialized,
4021
+ transaction,
4022
+ document: storageFileGroupDocumentInTransaction,
4023
+ data: storageFileGroup
4024
+ });
4025
+ };
4026
+ }
4027
+ function initializeStorageFileGroupFactory(context) {
4028
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory } = context;
4029
+ const initializeStorageFileGroupInTransaction = initializeStorageFileGroupInTransactionFactory(context);
4030
+ return firebaseServerActionTransformFunctionFactory(firebase.InitializeStorageFileModelParams, async (params) => {
4031
+ return async (storageFileGroupDocument) => {
4032
+ await firestoreContext.runTransaction((transaction) => initializeStorageFileGroupInTransaction(params, storageFileGroupDocument, transaction));
4033
+ return storageFileGroupDocument;
4034
+ };
4035
+ });
4036
+ }
4037
+ function initializeAllApplicableStorageFileGroupsFactory(context) {
4038
+ const { firestoreContext, firebaseServerActionTransformFunctionFactory, storageFileGroupCollection } = context;
4039
+ const initializeStorageFileGroupInTransaction = initializeStorageFileGroupInTransactionFactory(context);
4040
+ return firebaseServerActionTransformFunctionFactory(firebase.InitializeAllApplicableStorageFileGroupsParams, async () => {
4041
+ return async () => {
4042
+ let storageFileGroupsVisited = 0;
4043
+ let storageFileGroupsSucceeded = 0;
4044
+ let storageFileGroupsFailed = 0;
4045
+ let storageFileGroupsAlreadyInitialized = 0;
4046
+ const initializeStorageFileGroupParams = { key: firebase.firestoreDummyKey(), throwErrorIfAlreadyInitialized: false };
4047
+ async function initializeStorageFileGroups() {
4048
+ const query = storageFileGroupCollection.queryDocument(firebase.storageFileGroupsFlaggedForNeedsInitializationQuery());
4049
+ const storageFileGroupDocuments = await query.getDocs();
4050
+ const result = await util.performAsyncTasks(storageFileGroupDocuments, async (storageFileGroupDocument) => {
4051
+ return firestoreContext.runTransaction((transaction) => initializeStorageFileGroupInTransaction(initializeStorageFileGroupParams, storageFileGroupDocument, transaction));
4052
+ }, {
4053
+ maxParallelTasks: 5
4054
+ });
4055
+ return result;
4056
+ }
4057
+ while (true) {
4058
+ const initializeStorageFileGroupsResults = await initializeStorageFileGroups();
4059
+ initializeStorageFileGroupsResults.results.forEach((x) => {
4060
+ const result = x[1];
4061
+ if (result.alreadyInitialized) {
4062
+ storageFileGroupsAlreadyInitialized += 1;
4063
+ }
4064
+ else if (result.initialized) {
4065
+ storageFileGroupsSucceeded += 1;
4066
+ }
4067
+ else {
4068
+ storageFileGroupsFailed += 1;
4069
+ }
4070
+ });
4071
+ const found = initializeStorageFileGroupsResults.results.length;
4072
+ storageFileGroupsVisited += found;
4073
+ if (!found) {
4074
+ break;
4075
+ }
4076
+ }
4077
+ const result = {
4078
+ storageFileGroupsVisited,
4079
+ storageFileGroupsSucceeded,
4080
+ storageFileGroupsFailed,
4081
+ storageFileGroupsAlreadyInitialized
4082
+ };
4083
+ return result;
4084
+ };
4085
+ });
4086
+ }
4087
+
4088
+ /**
4089
+ * Service dedicated to initializing a StorageFileDocument value from an uploaded file.
4090
+ */
4091
+ class StorageFileInitializeFromUploadService {
4092
+ }
4093
+
4094
+ // MARK: Provider Factories
4095
+ function storageFileServerActionsContextFactory(context, storageFileInitializeFromUploadService) {
4096
+ return { ...context, storageFileInitializeFromUploadService };
4097
+ }
4098
+ function storageFileServerActionsFactory(context) {
4099
+ return storageFileServerActions(context);
4100
+ }
4101
+ function storageFileInitServerActionsFactory(context, storageFileInitServerActionsContextConfig) {
4102
+ return storageFileInitServerActions({
4103
+ ...context,
4104
+ ...storageFileInitServerActionsContextConfig
4105
+ });
4106
+ }
4107
+ /**
4108
+ * Convenience function used to generate ModuleMetadata for an app's StorageFileModule.
4109
+ *
4110
+ * By default this module exports:
4111
+ * - StorageFileServerActionContext (STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN)
4112
+ * - StorageFileServerActions
4113
+ * - StorageFileInitServerActions
4114
+ *
4115
+ * Be sure the class that delares the module using this function also extends AbstractAppStorageFileModule.
4116
+ *
4117
+ * @param provide
4118
+ * @param useFactory
4119
+ * @returns
4120
+ */
4121
+ function appStorageFileModuleMetadata(config$1) {
4122
+ const { dependencyModule, imports, exports: exports$1, providers } = config$1;
4123
+ const dependencyModuleImport = dependencyModule ? [dependencyModule] : [];
4124
+ return {
4125
+ imports: [config.ConfigModule, ...dependencyModuleImport, ...(imports ?? [])],
4126
+ exports: [STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN, StorageFileServerActions, StorageFileInitServerActions, ...(exports$1 ?? [])],
4127
+ providers: [
4128
+ {
4129
+ provide: STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN,
4130
+ useFactory: storageFileServerActionsContextFactory,
4131
+ inject: [BASE_STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN, StorageFileInitializeFromUploadService]
4132
+ },
4133
+ {
4134
+ provide: StorageFileServerActions,
4135
+ useFactory: storageFileServerActionsFactory,
4136
+ inject: [STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN]
4137
+ },
4138
+ {
4139
+ provide: StorageFileInitServerActions,
4140
+ useFactory: storageFileInitServerActionsFactory,
4141
+ inject: [STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN, STORAGE_FILE_INIT_SERVER_ACTIONS_CONTEXT_CONFIG_TOKEN]
4142
+ },
4143
+ ...(providers ?? [])
4144
+ ]
4145
+ };
4146
+ }
4147
+
4148
+ /**
4149
+ * Performs a query and flags the matching StorageFiles for deletion.
4150
+ *
4151
+ * @param input The input for the query and flagging.
4152
+ * @returns The result of the query and flagging.
4153
+ */
4154
+ async function queryAndFlagStorageFilesForDelete(input) {
4155
+ const { storageFileCollection, constraints, queuedForDeleteTime: inputQueueForDeleteTime, skipDeleteForKeys } = input;
4156
+ const queuedForDeleteTime = inputQueueForDeleteTime ?? true;
4157
+ const skipDeleteSet = new Set(util.asArray(skipDeleteForKeys));
4158
+ let visitedCount = 0;
4159
+ let queuedForDeleteCount = 0;
4160
+ await firebase.iterateFirestoreDocumentSnapshotPairs({
4161
+ documentAccessor: storageFileCollection.documentAccessor(),
4162
+ iterateSnapshotPair: async (snapshotPair) => {
4163
+ const { document, data: storageFile } = snapshotPair;
4164
+ if (!storageFile.sdat && !skipDeleteSet.has(storageFile.key)) {
4165
+ await document.update(markStorageFileForDeleteTemplate(queuedForDeleteTime));
4166
+ queuedForDeleteCount++;
4167
+ }
4168
+ visitedCount++;
4169
+ },
4170
+ queryFactory: storageFileCollection,
4171
+ constraintsFactory: () => constraints,
4172
+ batchSize: undefined,
4173
+ performTasksConfig: {
4174
+ maxParallelTasks: 20
4175
+ }
4176
+ });
4177
+ return {
4178
+ visitedCount,
4179
+ queuedForDeleteCount
4180
+ };
4181
+ }
4182
+ /**
4183
+ * Creates a template for updating a StorageFile to be queued for deletion at the input time.
4184
+ *
4185
+ * @param queueForDeleteTime When to delete the StorageFile. If true or unset, the StorageFile will be flagged to be deleted immediately.
4186
+ * @returns The update template for the StorageFile.
4187
+ */
4188
+ function markStorageFileForDeleteTemplate(queueForDeleteTime) {
4189
+ const updateTemplate = {
4190
+ sdat: queueForDeleteTime === true || queueForDeleteTime == null ? new Date() : util.dateFromDateOrTimeMillisecondsNumber(queueForDeleteTime),
4191
+ fs: firebase.StorageFileState.QUEUED_FOR_DELETE
4192
+ };
4193
+ return updateTemplate;
4194
+ }
4195
+
4196
+ const storageFileProcessingNotificationTaskHandlerDefaultCleanup = () => {
4197
+ return {
4198
+ cleanupSuccess: true,
4199
+ nextProcessingState: firebase.StorageFileProcessingState.SUCCESS,
4200
+ queueForDelete: false // do not queue for delete automatically
4201
+ };
4202
+ };
4203
+ /**
4204
+ * Creates a NotificationTaskServiceTaskHandlerConfig that handles the StorageFileProcessingNotificationTask.
4205
+ */
4206
+ function storageFileProcessingNotificationTaskHandler(config) {
4207
+ const { processors: inputProcessors, storageAccessor, storageFileFirestoreCollections, allStorageFileGroupProcessorConfig } = config;
4208
+ const storageFileDocumentAccessor = storageFileFirestoreCollections.storageFileCollection.documentAccessor();
4209
+ const makeFileDetailsAccessor = firebase.storedFileReaderFactory();
4210
+ const defaultCleanup = storageFileProcessingNotificationTaskHandlerDefaultCleanup;
4211
+ const processors = [...inputProcessors];
4212
+ if (allStorageFileGroupProcessorConfig !== false) {
4213
+ const storageFileGroupProcessors = allStorageFileGroupStorageFileProcessingPurposeSubtaskProcessors({
4214
+ ...allStorageFileGroupProcessorConfig,
4215
+ storageFileFirestoreCollections,
4216
+ storageAccessor
4217
+ });
4218
+ util.pushArrayItemsIntoArray(processors, storageFileGroupProcessors);
4219
+ }
4220
+ return notificationTaskSubtaskNotificationTaskHandlerFactory({
4221
+ taskType: firebase.STORAGE_FILE_PROCESSING_NOTIFICATION_TASK_TYPE,
4222
+ subtaskHandlerFunctionName: 'storageFileProcessingNotificationTaskHandler',
4223
+ inputFunction: async (data) => {
4224
+ const storageFileDocument = await storageFileDocumentAccessor.loadDocumentForId(data.storageFile);
4225
+ const loadStorageFile = util.cachedGetter(async () => {
4226
+ const storageFile = await firebase.getDocumentSnapshotData(storageFileDocument, true);
4227
+ if (!storageFile) {
4228
+ throw notificationTaskSubTaskMissingRequiredDataTermination();
4229
+ }
4230
+ return storageFile;
4231
+ });
4232
+ let purpose = data?.p;
4233
+ if (!purpose) {
4234
+ // attempt to load the purpose from the storage file, if it exists.
4235
+ purpose = (await loadStorageFile().then((x) => x.p));
4236
+ }
4237
+ let storagePath;
4238
+ if (data.storagePath) {
4239
+ storagePath = data.storagePath;
4240
+ }
4241
+ else {
4242
+ storagePath = await loadStorageFile().then((x) => ({ bucketId: x.bucketId, pathString: x.pathString }));
4243
+ }
4244
+ const file = storageAccessor.file(storagePath);
4245
+ const fileDetailsAccessor = makeFileDetailsAccessor(file);
4246
+ const input = {
4247
+ target: purpose,
4248
+ loadStorageFile,
4249
+ fileDetailsAccessor,
4250
+ storageFileDocument
4251
+ };
4252
+ return input;
4253
+ },
4254
+ buildUpdateMetadata: (baseUpdateMetadata, input) => {
4255
+ const { target } = input;
4256
+ return {
4257
+ ...baseUpdateMetadata,
4258
+ // always re-copy the target/storagePath for the next run so StorageFile does not have to be reloaded
4259
+ p: target,
4260
+ storagePath: firebase.copyStoragePath(input.fileDetailsAccessor.input)
4261
+ };
4262
+ },
4263
+ defaultCleanup,
4264
+ cleanupFunction: async function (input, cleanupInstructions) {
4265
+ const { storageFileDocument } = input;
4266
+ const { nextProcessingState, queueForDelete, flagResyncWithStorageFileGroups: syncWithStorageFileGroups } = cleanupInstructions;
4267
+ let updateTemplate = {
4268
+ ps: nextProcessingState ?? firebase.StorageFileProcessingState.SUCCESS,
4269
+ pcat: new Date(), // set new cleanup/completion date
4270
+ pn: null // clear reference
4271
+ };
4272
+ const shouldQueueForDelete = queueForDelete != null && queueForDelete !== false;
4273
+ if (shouldQueueForDelete) {
4274
+ updateTemplate = {
4275
+ ...updateTemplate,
4276
+ ...markStorageFileForDeleteTemplate(queueForDelete)
4277
+ };
4278
+ }
4279
+ else if (syncWithStorageFileGroups) {
4280
+ // resync with storage file groups
4281
+ updateTemplate.gs = true;
4282
+ }
4283
+ await storageFileDocument.update(updateTemplate);
4284
+ return firebase.notificationTaskComplete();
4285
+ }
4286
+ })({
4287
+ ...config,
4288
+ processors
4289
+ });
4290
+ }
4291
+ function allStorageFileGroupStorageFileProcessingPurposeSubtaskProcessors(config) {
4292
+ const { excludeZipProcessing } = config;
4293
+ const processors = [];
4294
+ if (!excludeZipProcessing) {
4295
+ processors.push(storageFileGroupZipStorageFileProcessingPurposeSubtaskProcessor(config));
4296
+ }
4297
+ return processors;
4298
+ }
4299
+ function storageFileGroupZipStorageFileProcessingPurposeSubtaskProcessor(config) {
4300
+ const { storageFileFirestoreCollections, storageAccessor, zip } = config;
4301
+ const { storageFileCollection, storageFileGroupCollection } = storageFileFirestoreCollections;
4302
+ const { maxNumberOfFilesToZipInParallel: inputMaxNumberOfFilesToZipInParallel, zipFileDisplayNameFunctionFactory: inputZipFileDisplayNameFunctionFactory, configureZipInfoJson: inputConfigureZipInfoJson, configureZipArchiverOptions: inputConfigureZipArchiverOptions, finalizeZipArchive } = zip ?? {};
4303
+ const maxNumberOfFilesToZipInParallel = inputMaxNumberOfFilesToZipInParallel ?? 3;
4304
+ const appendZipInfoJson = inputConfigureZipInfoJson !== false;
4305
+ const configureZipArchiverOptions = inputConfigureZipArchiverOptions ?? (() => ({ zlib: { level: 9 } }));
4306
+ const configureZipInfoJson = (appendZipInfoJson ? inputConfigureZipInfoJson : undefined) ?? util.MAP_IDENTITY;
4307
+ const zipFileDisplayNameFunctionFactory = inputZipFileDisplayNameFunctionFactory ?? (() => () => null);
4308
+ const storageFileGroupZipProcessorConfig = {
4309
+ target: firebase.STORAGE_FILE_GROUP_ZIP_STORAGE_FILE_PURPOSE,
4310
+ cleanup: zip?.cleanup,
4311
+ flow: [
4312
+ {
4313
+ subtask: firebase.STORAGE_FILE_GROUP_ZIP_STORAGE_FILE_PURPOSE_CREATE_ZIP_SUBTASK,
4314
+ fn: async (input) => {
4315
+ const { storageFileDocument, fileDetailsAccessor } = input;
4316
+ const storageFile = await input.loadStorageFile();
4317
+ const storageFileMetadata = storageFile.d;
4318
+ const storageFileGroupId = storageFileMetadata?.sfg;
4319
+ let result;
4320
+ async function flagStorageFileForDeletion() {
4321
+ await storageFileDocument.update(markStorageFileForDeleteTemplate());
4322
+ return firebase.notificationTaskComplete(); // skip cleanup step
4323
+ }
4324
+ if (storageFileGroupId) {
4325
+ const storageFileGroupDocument = storageFileGroupCollection.documentAccessor().loadDocumentForId(storageFileGroupId);
4326
+ const storageFileGroup = await storageFileGroupDocument.snapshotData();
4327
+ if (storageFileGroup) {
4328
+ const embeddedFilesMap = new Map(storageFileGroup.f.map((x) => [x.s, x]));
4329
+ const storageFileIdsToZip = storageFileGroup.f.map((x) => x.s);
4330
+ const storageFilesToZip = firebase.loadDocumentsForIds(storageFileCollection.documentAccessor(), storageFileIdsToZip);
4331
+ const storageFileDataPairsToZip = await firebase.getDocumentSnapshotDataPairs(storageFilesToZip);
4332
+ const zipFileDisplayNameFunction = await zipFileDisplayNameFunctionFactory({ storageFileGroup, storageFileGroupDocument });
4333
+ let flagCleanFileAssociations = undefined;
4334
+ // create a new file
4335
+ const zipFileAccessor = storageAccessor.file(fileDetailsAccessor.input);
4336
+ if (zipFileAccessor.uploadStream && zipFileAccessor.getStream) {
4337
+ const uploadStream = zipFileAccessor.uploadStream({
4338
+ contentType: util.ZIP_FILE_MIME_TYPE
4339
+ });
4340
+ const startedAt = new Date();
4341
+ const archiverOptions = await configureZipArchiverOptions({ input, storageFileGroup });
4342
+ const newArchive = archiver('zip', archiverOptions);
4343
+ // pipe the archive to the upload stream
4344
+ newArchive.pipe(uploadStream, { end: true });
4345
+ // upload each of the files to the archive
4346
+ await util.performAsyncTasks(storageFileDataPairsToZip, async (storageFileDataPair) => {
4347
+ const { data: storageFile } = storageFileDataPair;
4348
+ if (storageFile) {
4349
+ const { n: storageFileDisplayName } = storageFile;
4350
+ // make sure it references the storage file group
4351
+ const referencesStorageFileGroup = storageFile.g.some((x) => x === storageFileGroupId);
4352
+ if (referencesStorageFileGroup) {
4353
+ const fileAccessor = storageAccessor.file(storageFile);
4354
+ const metadata = await fileAccessor.getMetadata().catch(() => null);
4355
+ if (metadata) {
4356
+ const fileSlashPathDetails = util.slashPathDetails(metadata.name);
4357
+ const storageFileGroupEmbeddedFile = embeddedFilesMap.get(storageFile.id);
4358
+ const { n: embeddedFileNameOverride } = storageFileGroupEmbeddedFile;
4359
+ const nameFromFactory = await zipFileDisplayNameFunction({ metadata, fileAccessor, storageFile, storageFileDocument, storageFileGroupEmbeddedFile });
4360
+ let untypedName = nameFromFactory || storageFileDisplayName || embeddedFileNameOverride || fileSlashPathDetails.fileName;
4361
+ let extension;
4362
+ if (fileSlashPathDetails.typedFileExtension) {
4363
+ extension = fileSlashPathDetails.typedFileExtension;
4364
+ }
4365
+ else if (metadata.contentType) {
4366
+ extension = util.documentFileExtensionForMimeType(metadata.contentType);
4367
+ }
4368
+ // set the default name if still unset
4369
+ untypedName = untypedName || `sf_${storageFile.id}`;
4370
+ const name = extension ? `${untypedName}.${extension}` : untypedName;
4371
+ const fileStream = fileAccessor.getStream();
4372
+ await util.useCallback((x) => {
4373
+ // append the file to the archive
4374
+ newArchive.append(fileStream, {
4375
+ name
4376
+ });
4377
+ // if the stream errors, call back
4378
+ fileStream.on('error', (e) => x(e));
4379
+ // when the stream finishes, call back
4380
+ fileStream.on('finish', () => x());
4381
+ });
4382
+ }
4383
+ else {
4384
+ flagCleanFileAssociations = true;
4385
+ }
4386
+ }
4387
+ else {
4388
+ flagCleanFileAssociations = true;
4389
+ }
4390
+ }
4391
+ }, {
4392
+ maxParallelTasks: maxNumberOfFilesToZipInParallel
4393
+ });
4394
+ const finishedAt = new Date();
4395
+ // create the info.json file
4396
+ if (appendZipInfoJson) {
4397
+ const infoJson = await configureZipInfoJson({
4398
+ sfg: storageFileGroupId,
4399
+ sf: storageFileIdsToZip,
4400
+ s: startedAt.toISOString(),
4401
+ f: finishedAt.toISOString()
4402
+ });
4403
+ let infoJsonString;
4404
+ try {
4405
+ infoJsonString = JSON.stringify(infoJson);
4406
+ }
4407
+ catch (e) {
4408
+ console.error('storageFileGroupZipStorageFileProcessingPurposeSubtaskProcessor(): Failed to convert the info json to a string. Check your custom configureInfoJson() function.', e);
4409
+ }
4410
+ if (infoJsonString) {
4411
+ newArchive.append(infoJsonString, {
4412
+ name: firebase.STORAGE_FILE_GROUP_ZIP_INFO_JSON_FILE_NAME
4413
+ });
4414
+ }
4415
+ }
4416
+ // perform any other tasks using the zip archive
4417
+ if (finalizeZipArchive) {
4418
+ await finalizeZipArchive({
4419
+ input,
4420
+ storageFileGroup,
4421
+ archive: newArchive
4422
+ });
4423
+ }
4424
+ // finalize the archive
4425
+ await newArchive.finalize();
4426
+ // update the StorageFileGroup
4427
+ await storageFileGroupDocument.update({
4428
+ zat: finishedAt,
4429
+ c: flagCleanFileAssociations
4430
+ });
4431
+ // schedule/run the cleanup task
4432
+ result = firebase.notificationSubtaskComplete({
4433
+ canRunNextCheckpoint: true
4434
+ });
4435
+ }
4436
+ else {
4437
+ // uploadStream is not available for some reason? Should never occur.
4438
+ console.warn('storageFileGroupZipStorageFileProcessingPurposeSubtaskProcessor(): uploadStream is not available for some reason while creating a new zip.');
4439
+ result = firebase.notificationTaskDelayRetry(util.MS_IN_HOUR);
4440
+ }
4441
+ }
4442
+ else {
4443
+ // storage file group no longer exists. Flag the StorageFile for deletion.
4444
+ result = await flagStorageFileForDeletion();
4445
+ }
4446
+ }
4447
+ else {
4448
+ // improperly configured StorageFile for this type. Flag the StorageFile for deletion.
4449
+ result = await flagStorageFileForDeletion();
4450
+ }
4451
+ return result;
4452
+ }
4453
+ }
4454
+ ]
4455
+ };
4456
+ return storageFileGroupZipProcessorConfig;
4457
+ }
4458
+
4459
+ function storageFileInitializeFromUploadServiceInitializerResultPermanentFailure(error, createdFile) {
4460
+ return {
4461
+ error,
4462
+ permanentFailure: true,
4463
+ createdFile
4464
+ };
4465
+ }
4466
+ /**
4467
+ * A basic StorageFileInitializeFromUploadService implementation.
4468
+ */
4469
+ function storageFileInitializeFromUploadService(config) {
4470
+ const { storageService, storageFileCollection, initializer: inputInitializers, determiner: inputDeterminers, validate, checkFileIsAllowedToBeInitialized: inputCheckFileIsAllowedToBeInitialized, requireStorageFileRelatedFileMetadataBeSet } = config;
4471
+ const allDeterminers = [];
4472
+ const initializers = {};
4473
+ const detailsAccessorFactory = firebase.storedFileReaderFactory();
4474
+ if (inputDeterminers) {
4475
+ util.pushItemOrArrayItemsIntoArray(allDeterminers, inputDeterminers);
4476
+ }
4477
+ // iterate initializers
4478
+ inputInitializers.forEach((initializer) => {
4479
+ const { type: inputTypes, determiner: inputDeterminer } = initializer;
4480
+ const types = util.asArray(inputTypes);
4481
+ types.forEach((type) => {
4482
+ initializers[type] = initializer;
4483
+ });
4484
+ if (inputDeterminer) {
4485
+ const wrappedDeterminer = firebase.limitUploadFileTypeDeterminer(inputDeterminer, types);
4486
+ allDeterminers.push(wrappedDeterminer);
4487
+ }
4488
+ });
4489
+ const determiner = firebase.combineUploadFileTypeDeterminers({
4490
+ determiners: allDeterminers,
4491
+ ...{
4492
+ completeSearchOnFirstMatch: true,
4493
+ ...config.combineDeterminersConfig
4494
+ }
4495
+ });
4496
+ // validate initializers
4497
+ if (validate) {
4498
+ const allInitializerTypes = Object.keys(initializers);
4499
+ const allDeterminerTypes = new Set(determiner.getPossibleFileTypes());
4500
+ // all initializer types should have a corresponding determiner
4501
+ allInitializerTypes.forEach((type) => {
4502
+ if (!allDeterminerTypes.has(type)) {
4503
+ throw new Error(`Initializer type ${type} does not have a corresponding determiner.`);
4504
+ }
4505
+ });
4506
+ }
4507
+ async function determineUploadFileType(input) {
4508
+ const { file } = input;
4509
+ const fileDetailsAccessor = detailsAccessorFactory(file);
4510
+ return determiner.determine(fileDetailsAccessor);
4511
+ }
4512
+ return {
4513
+ checkFileIsAllowedToBeInitialized: inputCheckFileIsAllowedToBeInitialized ?? util.asDecisionFunction(true),
4514
+ determineUploadFileType,
4515
+ initializeFromUpload: async (input) => {
4516
+ const determinerResult = await determineUploadFileType(input);
4517
+ let resultType;
4518
+ let createdFilePath;
4519
+ let storageFileDocument;
4520
+ let processorError;
4521
+ let previousStorageFilesFlaggedForDeletion;
4522
+ if (determinerResult) {
4523
+ const { input: fileDetailsAccessor } = determinerResult;
4524
+ resultType = 'success';
4525
+ const initializer = initializers[determinerResult.type];
4526
+ if (initializer) {
4527
+ try {
4528
+ const initializerResult = await initializer.initialize({ determinerResult, fileDetailsAccessor });
4529
+ if (initializerResult.error) {
4530
+ const { error, permanentFailure, createdFile } = initializerResult;
4531
+ processorError = error;
4532
+ if (permanentFailure) {
4533
+ resultType = 'permanent_initializer_failure';
4534
+ }
4535
+ else {
4536
+ resultType = 'initializer_error';
4537
+ }
4538
+ // delete the created file
4539
+ if (createdFile != null) {
4540
+ await storageService
4541
+ .file(createdFile.storagePath)
4542
+ .delete()
4543
+ .catch(() => 0);
4544
+ }
4545
+ }
4546
+ else {
4547
+ let flagPreviousForDelete;
4548
+ if (initializerResult.createStorageFileResult) {
4549
+ const { createStorageFileResult, flagPreviousForDelete: flagPreviousForDeleteResult } = initializerResult;
4550
+ createdFilePath = firebase.copyStoragePath(createStorageFileResult.storageFile);
4551
+ storageFileDocument = createStorageFileResult.storageFileDocument;
4552
+ if (flagPreviousForDeleteResult) {
4553
+ if (typeof flagPreviousForDeleteResult === 'object') {
4554
+ flagPreviousForDelete = flagPreviousForDeleteResult;
4555
+ }
4556
+ else {
4557
+ const { p, pg, u } = createStorageFileResult.storageFile;
4558
+ if (!p || !u) {
4559
+ throw new Error('initializeFromUpload(): flagPreviousForDelete=true requires that the created StorageFile have a purpose (p) and user (u).');
4560
+ }
4561
+ flagPreviousForDelete = {
4562
+ purpose: p,
4563
+ purposeSubgroup: pg,
4564
+ user: u
4565
+ };
4566
+ }
4567
+ }
4568
+ }
4569
+ else {
4570
+ createdFilePath = firebase.copyStoragePath(initializerResult.createdFile.storagePath);
4571
+ storageFileDocument = initializerResult.storageFileDocument;
4572
+ flagPreviousForDelete = initializerResult.flagPreviousForDelete;
4573
+ }
4574
+ // sanitize the returned value, incase the result comes from a transaction
4575
+ if (storageFileDocument) {
4576
+ storageFileDocument = storageFileCollection.documentAccessor().loadDocumentFrom(storageFileDocument);
4577
+ }
4578
+ // set the metadata on the associated file
4579
+ try {
4580
+ const createdFile = storageService.file(createdFilePath);
4581
+ const fileMetadata = await createdFile.getMetadata();
4582
+ await createdFile.setMetadata({
4583
+ customMetadata: {
4584
+ ...fileMetadata.customMetadata,
4585
+ [firebase.STORAGEFILE_RELATED_FILE_METADATA_KEY]: storageFileDocument.id
4586
+ }
4587
+ });
4588
+ }
4589
+ catch (e) {
4590
+ // failed to set the metadata. It isn't strictly necessary, so don't throw an error unless configured to always throw an error
4591
+ if (requireStorageFileRelatedFileMetadataBeSet) {
4592
+ // mark the created item for delete
4593
+ await storageFileDocument.update(markStorageFileForDeleteTemplate());
4594
+ // throw the exception
4595
+ throw e;
4596
+ }
4597
+ }
4598
+ // if flagPreviousForDelete is set, flag the previous storage files for deletion
4599
+ if (flagPreviousForDelete) {
4600
+ const flagForDeleteResult = await queryAndFlagStorageFilesForDelete({
4601
+ storageFileCollection,
4602
+ constraints: firebase.storageFilePurposeAndUserQuery(flagPreviousForDelete),
4603
+ skipDeleteForKeys: [storageFileDocument.key]
4604
+ });
4605
+ previousStorageFilesFlaggedForDeletion = flagForDeleteResult.queuedForDeleteCount;
4606
+ }
4607
+ }
4608
+ }
4609
+ catch (e) {
4610
+ resultType = 'initializer_error';
4611
+ processorError = e;
4612
+ }
4613
+ }
4614
+ else {
4615
+ resultType = 'no_initializer_configured';
4616
+ }
4617
+ }
4618
+ else {
4619
+ resultType = 'no_determiner_match';
4620
+ }
4621
+ const result = {
4622
+ resultType,
4623
+ storageFileDocument,
4624
+ initializationError: processorError,
4625
+ previousStorageFilesFlaggedForDeletion
4626
+ };
4627
+ return result;
4628
+ }
4629
+ };
4630
+ }
4631
+
4632
+ exports.BASE_NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN = BASE_NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN;
4633
+ exports.BASE_STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN = BASE_STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN;
4634
+ exports.KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS;
4635
+ exports.KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY = KNOWN_BUT_UNCONFIGURED_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY;
4636
+ exports.MAILGUN_NOTIFICATION_EMAIL_SEND_SERVICE_DEFAULT_MAX_BATCH_SIZE_PER_REQUEST = MAILGUN_NOTIFICATION_EMAIL_SEND_SERVICE_DEFAULT_MAX_BATCH_SIZE_PER_REQUEST;
4637
+ exports.MAKE_TEMPLATE_FOR_NOTIFICATION_RELATED_MODEL_INITIALIZATION_FUNCTION_DELETE_RESPONSE = MAKE_TEMPLATE_FOR_NOTIFICATION_RELATED_MODEL_INITIALIZATION_FUNCTION_DELETE_RESPONSE;
4638
+ exports.MAKE_TEMPLATE_FOR_STORAGEFILE_RELATED_MODEL_INITIALIZATION_FUNCTION_DELETE_RESPONSE = MAKE_TEMPLATE_FOR_STORAGEFILE_RELATED_MODEL_INITIALIZATION_FUNCTION_DELETE_RESPONSE;
4639
+ exports.NOTIFICATION_BOX_NOT_INITIALIZED_DELAY_MINUTES = NOTIFICATION_BOX_NOT_INITIALIZED_DELAY_MINUTES;
4640
+ exports.NOTIFICATION_INIT_SERVER_ACTIONS_CONTEXT_CONFIG_TOKEN = NOTIFICATION_INIT_SERVER_ACTIONS_CONTEXT_CONFIG_TOKEN;
4641
+ exports.NOTIFICATION_MAX_SEND_ATTEMPTS = NOTIFICATION_MAX_SEND_ATTEMPTS;
4642
+ exports.NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN = NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN;
4643
+ exports.NOTIFICATION_TASK_MINIMUM_SET_AT_THROTTLE_TIME_MINUTES = NOTIFICATION_TASK_MINIMUM_SET_AT_THROTTLE_TIME_MINUTES;
4644
+ exports.NOTIFICATION_TASK_TYPE_FAILURE_DELAY_HOURS = NOTIFICATION_TASK_TYPE_FAILURE_DELAY_HOURS;
4645
+ exports.NOTIFICATION_TASK_TYPE_FAILURE_DELAY_MS = NOTIFICATION_TASK_TYPE_FAILURE_DELAY_MS;
4646
+ exports.NOTIFICATION_TASK_TYPE_MAX_SEND_ATTEMPTS = NOTIFICATION_TASK_TYPE_MAX_SEND_ATTEMPTS;
4647
+ exports.NOTIFICATION_TEMPLATE_SERVICE_CONFIGS_ARRAY_TOKEN = NOTIFICATION_TEMPLATE_SERVICE_CONFIGS_ARRAY_TOKEN;
4648
+ exports.NOTIFICATION_TEMPLATE_SERVICE_DEFAULTS_OVERRIDE_TOKEN = NOTIFICATION_TEMPLATE_SERVICE_DEFAULTS_OVERRIDE_TOKEN;
4649
+ exports.NotificationExpediteService = NotificationExpediteService;
4650
+ exports.NotificationInitServerActions = NotificationInitServerActions;
4651
+ exports.NotificationSendService = NotificationSendService;
4652
+ exports.NotificationServerActions = NotificationServerActions;
4653
+ exports.NotificationTaskService = NotificationTaskService;
4654
+ exports.NotificationTaskSubTaskMissingRequiredDataTermination = NotificationTaskSubTaskMissingRequiredDataTermination;
4655
+ exports.SEND_QUEUE_NOTIFICATIONS_TASK_EXCESS_THRESHOLD = SEND_QUEUE_NOTIFICATIONS_TASK_EXCESS_THRESHOLD;
4656
+ exports.STORAGE_FILE_INIT_SERVER_ACTIONS_CONTEXT_CONFIG_TOKEN = STORAGE_FILE_INIT_SERVER_ACTIONS_CONTEXT_CONFIG_TOKEN;
4657
+ exports.STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN = STORAGE_FILE_SERVER_ACTION_CONTEXT_TOKEN;
4658
+ exports.StorageFileInitServerActions = StorageFileInitServerActions;
4659
+ exports.StorageFileInitializeFromUploadService = StorageFileInitializeFromUploadService;
4660
+ exports.StorageFileServerActions = StorageFileServerActions;
4661
+ exports.UNKNOWN_NOTIFICATION_TASK_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = UNKNOWN_NOTIFICATION_TASK_TYPE_DELETE_AFTER_RETRY_ATTEMPTS;
4662
+ exports.UNKNOWN_NOTIFICATION_TASK_TYPE_HOURS_DELAY = UNKNOWN_NOTIFICATION_TASK_TYPE_HOURS_DELAY;
4663
+ exports.UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS = UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_DELETE_AFTER_RETRY_ATTEMPTS;
4664
+ exports.UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY = UNKNOWN_NOTIFICATION_TEMPLATE_TYPE_HOURS_DELAY;
4665
+ exports._initializeStorageFileFromUploadFileFactory = _initializeStorageFileFromUploadFileFactory;
4666
+ exports._processStorageFileInTransactionFactory = _processStorageFileInTransactionFactory;
4667
+ exports._syncStorageFileWithGroupsInTransactionFactory = _syncStorageFileWithGroupsInTransactionFactory;
4668
+ exports.allStorageFileGroupStorageFileProcessingPurposeSubtaskProcessors = allStorageFileGroupStorageFileProcessingPurposeSubtaskProcessors;
4669
+ exports.appNotificationModuleMetadata = appNotificationModuleMetadata;
4670
+ exports.appStorageFileModuleMetadata = appStorageFileModuleMetadata;
4671
+ exports.cleanupSentNotificationsFactory = cleanupSentNotificationsFactory;
4672
+ exports.createNotificationBoxFactory = createNotificationBoxFactory;
4673
+ exports.createNotificationBoxInTransactionFactory = createNotificationBoxInTransactionFactory;
4674
+ exports.createNotificationIdRequiredError = createNotificationIdRequiredError;
4675
+ exports.createNotificationSummaryFactory = createNotificationSummaryFactory;
4676
+ exports.createNotificationUserFactory = createNotificationUserFactory;
4677
+ exports.createOrRunUniqueNotificationDocument = createOrRunUniqueNotificationDocument;
4678
+ exports.createStorageFileFactory = createStorageFileFactory;
4679
+ exports.createStorageFileGroupFactory = createStorageFileGroupFactory;
4680
+ exports.createStorageFileGroupInTransactionFactory = createStorageFileGroupInTransactionFactory;
4681
+ exports.createStorageFileGroupInputError = createStorageFileGroupInputError;
4682
+ exports.deleteAllQueuedStorageFilesFactory = deleteAllQueuedStorageFilesFactory;
4683
+ exports.deleteStorageFileFactory = deleteStorageFileFactory;
4684
+ exports.downloadStorageFileFactory = downloadStorageFileFactory;
4685
+ exports.expandNotificationRecipients = expandNotificationRecipients;
4686
+ exports.exportMutableNotificationExpediteService = exportMutableNotificationExpediteService;
4687
+ exports.firestoreNotificationSummarySendService = firestoreNotificationSummarySendService;
4688
+ exports.ignoreSendNotificationTextSendService = ignoreSendNotificationTextSendService;
4689
+ exports.initializeAllApplicableNotificationBoxesFactory = initializeAllApplicableNotificationBoxesFactory;
4690
+ exports.initializeAllApplicableNotificationSummariesFactory = initializeAllApplicableNotificationSummariesFactory;
4691
+ exports.initializeAllApplicableStorageFileGroupsFactory = initializeAllApplicableStorageFileGroupsFactory;
4692
+ exports.initializeAllStorageFilesFromUploadsFactory = initializeAllStorageFilesFromUploadsFactory;
4693
+ exports.initializeNotificationBoxFactory = initializeNotificationBoxFactory;
4694
+ exports.initializeNotificationBoxInTransactionFactory = initializeNotificationBoxInTransactionFactory;
4695
+ exports.initializeNotificationModelInTransaction = initializeNotificationModelInTransaction;
4696
+ exports.initializeNotificationSummaryFactory = initializeNotificationSummaryFactory;
4697
+ exports.initializeNotificationSummaryInTransactionFactory = initializeNotificationSummaryInTransactionFactory;
4698
+ exports.initializeStorageFileFromUploadFactory = initializeStorageFileFromUploadFactory;
4699
+ exports.initializeStorageFileGroupFactory = initializeStorageFileGroupFactory;
4700
+ exports.initializeStorageFileGroupInTransactionFactory = initializeStorageFileGroupInTransactionFactory;
4701
+ exports.initializeStorageFileModelInTransaction = initializeStorageFileModelInTransaction;
4702
+ exports.mailgunNotificationEmailSendService = mailgunNotificationEmailSendService;
4703
+ exports.makeNewNotificationSummaryTemplate = makeNewNotificationSummaryTemplate;
4704
+ exports.markStorageFileForDeleteTemplate = markStorageFileForDeleteTemplate;
4705
+ exports.notificationBoxDoesNotExist = notificationBoxDoesNotExist;
4706
+ exports.notificationBoxExclusionTargetInvalidError = notificationBoxExclusionTargetInvalidError;
4707
+ exports.notificationBoxExistsForModelError = notificationBoxExistsForModelError;
4708
+ exports.notificationBoxRecipientDoesNotExistsError = notificationBoxRecipientDoesNotExistsError;
4709
+ exports.notificationBoxUnregistredModelTypeInitializationError = notificationBoxUnregistredModelTypeInitializationError;
4710
+ exports.notificationExpediteServiceInstance = notificationExpediteServiceInstance;
4711
+ exports.notificationInitServerActions = notificationInitServerActions;
4712
+ exports.notificationInitServerActionsFactory = notificationInitServerActionsFactory;
4713
+ exports.notificationModelAlreadyInitializedError = notificationModelAlreadyInitializedError;
4714
+ exports.notificationServerActions = notificationServerActions;
4715
+ exports.notificationServerActionsContextFactory = notificationServerActionsContextFactory;
4716
+ exports.notificationServerActionsFactory = notificationServerActionsFactory;
4717
+ exports.notificationTaskService = notificationTaskService;
4718
+ exports.notificationTaskSubTaskMissingRequiredDataTermination = notificationTaskSubTaskMissingRequiredDataTermination;
4719
+ exports.notificationTaskSubtaskNotificationTaskHandlerFactory = notificationTaskSubtaskNotificationTaskHandlerFactory;
4720
+ exports.notificationTemplateServiceInstance = notificationTemplateServiceInstance;
4721
+ exports.notificationUserBlockedFromBeingAddedToRecipientsError = notificationUserBlockedFromBeingAddedToRecipientsError;
4722
+ exports.notificationUserInvalidUidForCreateError = notificationUserInvalidUidForCreateError;
4723
+ exports.notificationUserLockedConfigFromBeingUpdatedError = notificationUserLockedConfigFromBeingUpdatedError;
4724
+ exports.processAllQueuedStorageFilesFactory = processAllQueuedStorageFilesFactory;
4725
+ exports.processStorageFileFactory = processStorageFileFactory;
4726
+ exports.provideMutableNotificationExpediteService = provideMutableNotificationExpediteService;
4727
+ exports.queryAndFlagStorageFilesForDelete = queryAndFlagStorageFilesForDelete;
4728
+ exports.regenerateAllFlaggedStorageFileGroupsContentFactory = regenerateAllFlaggedStorageFileGroupsContentFactory;
4729
+ exports.regenerateStorageFileGroupContentFactory = regenerateStorageFileGroupContentFactory;
4730
+ exports.resyncAllNotificationUsersFactory = resyncAllNotificationUsersFactory;
4731
+ exports.resyncNotificationUserFactory = resyncNotificationUserFactory;
4732
+ exports.sendNotificationFactory = sendNotificationFactory;
4733
+ exports.sendQueuedNotificationsFactory = sendQueuedNotificationsFactory;
4734
+ exports.storageFileAlreadyProcessedError = storageFileAlreadyProcessedError;
4735
+ exports.storageFileCannotBeDeletedYetError = storageFileCannotBeDeletedYetError;
4736
+ exports.storageFileGroupQueuedForInitializationError = storageFileGroupQueuedForInitializationError;
4737
+ exports.storageFileGroupZipStorageFileProcessingPurposeSubtaskProcessor = storageFileGroupZipStorageFileProcessingPurposeSubtaskProcessor;
4738
+ exports.storageFileInitServerActions = storageFileInitServerActions;
4739
+ exports.storageFileInitServerActionsFactory = storageFileInitServerActionsFactory;
4740
+ exports.storageFileInitializeFromUploadService = storageFileInitializeFromUploadService;
4741
+ exports.storageFileInitializeFromUploadServiceInitializerResultPermanentFailure = storageFileInitializeFromUploadServiceInitializerResultPermanentFailure;
4742
+ exports.storageFileModelAlreadyInitializedError = storageFileModelAlreadyInitializedError;
4743
+ exports.storageFileNotFlaggedForDeletionError = storageFileNotFlaggedForDeletionError;
4744
+ exports.storageFileNotFlaggedForGroupsSyncError = storageFileNotFlaggedForGroupsSyncError;
4745
+ exports.storageFileProcessingNotAllowedForInvalidStateError = storageFileProcessingNotAllowedForInvalidStateError;
4746
+ exports.storageFileProcessingNotAvailableForTypeError = storageFileProcessingNotAvailableForTypeError;
4747
+ exports.storageFileProcessingNotQueuedForProcessingError = storageFileProcessingNotQueuedForProcessingError;
4748
+ exports.storageFileProcessingNotificationTaskHandler = storageFileProcessingNotificationTaskHandler;
4749
+ exports.storageFileProcessingNotificationTaskHandlerDefaultCleanup = storageFileProcessingNotificationTaskHandlerDefaultCleanup;
4750
+ exports.storageFileServerActions = storageFileServerActions;
4751
+ exports.storageFileServerActionsContextFactory = storageFileServerActionsContextFactory;
4752
+ exports.storageFileServerActionsFactory = storageFileServerActionsFactory;
4753
+ exports.syncAllFlaggedStorageFilesWithGroupsFactory = syncAllFlaggedStorageFilesWithGroupsFactory;
4754
+ exports.syncStorageFileWithGroupsFactory = syncStorageFileWithGroupsFactory;
4755
+ exports.updateNotificationBoxFactory = updateNotificationBoxFactory;
4756
+ exports.updateNotificationBoxRecipientExclusionInTransactionFactory = updateNotificationBoxRecipientExclusionInTransactionFactory;
4757
+ exports.updateNotificationBoxRecipientFactory = updateNotificationBoxRecipientFactory;
4758
+ exports.updateNotificationBoxRecipientInTransactionFactory = updateNotificationBoxRecipientInTransactionFactory;
4759
+ exports.updateNotificationSummaryFactory = updateNotificationSummaryFactory;
4760
+ exports.updateNotificationUserFactory = updateNotificationUserFactory;
4761
+ exports.updateNotificationUserNotificationBoxRecipientConfig = updateNotificationUserNotificationBoxRecipientConfig;
4762
+ exports.updateStorageFileFactory = updateStorageFileFactory;
4763
+ exports.updateStorageFileGroupFactory = updateStorageFileGroupFactory;
4764
+ exports.uploadedFileDoesNotExistError = uploadedFileDoesNotExistError;
4765
+ exports.uploadedFileInitializationDiscardedError = uploadedFileInitializationDiscardedError;
4766
+ exports.uploadedFileInitializationFailedError = uploadedFileInitializationFailedError;
4767
+ exports.uploadedFileIsNotAllowedToBeInitializedError = uploadedFileIsNotAllowedToBeInitializedError;