@oneuptime/common 12.0.13 → 12.0.14

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 (376) hide show
  1. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +14 -0
  2. package/Models/DatabaseModels/UserCall.ts +94 -3
  3. package/Models/DatabaseModels/UserEmail.ts +94 -3
  4. package/Models/DatabaseModels/UserIncomingCallNumber.ts +94 -3
  5. package/Models/DatabaseModels/UserPush.ts +37 -0
  6. package/Models/DatabaseModels/UserSMS.ts +94 -3
  7. package/Models/DatabaseModels/UserWhatsApp.ts +93 -2
  8. package/Server/API/BaseAPI.ts +79 -14
  9. package/Server/API/UserCallAPI.ts +53 -45
  10. package/Server/API/UserEmailAPI.ts +53 -44
  11. package/Server/API/UserIncomingCallNumberAPI.ts +48 -38
  12. package/Server/API/UserNotificationMethodAdminAPI.ts +481 -0
  13. package/Server/API/UserPushAPI.ts +126 -2
  14. package/Server/API/UserSmsAPI.ts +53 -44
  15. package/Server/API/UserWhatsAppAPI.ts +53 -50
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.ts +154 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.ts +17 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.ts +45 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/VerificationCodeRateLimit.ts +586 -0
  21. package/Server/Services/AnalyticsDatabaseService.ts +169 -0
  22. package/Server/Services/DatabaseService.ts +101 -0
  23. package/Server/Services/LogAggregationService.ts +5 -0
  24. package/Server/Services/PushNotificationService.ts +127 -12
  25. package/Server/Services/TeamMemberService.ts +53 -13
  26. package/Server/Services/UserCallService.ts +55 -24
  27. package/Server/Services/UserEmailService.ts +83 -17
  28. package/Server/Services/UserIncomingCallNumberService.ts +49 -85
  29. package/Server/Services/UserNotificationMethodAdminService.ts +1527 -0
  30. package/Server/Services/UserNotificationRuleService.ts +26 -0
  31. package/Server/Services/UserPushService.ts +89 -0
  32. package/Server/Services/UserSmsService.ts +50 -17
  33. package/Server/Services/UserWhatsAppService.ts +48 -15
  34. package/Server/Types/Database/QueryHelper.ts +30 -0
  35. package/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.ts +59 -0
  36. package/Server/Utils/ChannelVerification.ts +510 -0
  37. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +13 -16
  38. package/Server/Utils/Monitor/MonitorAlert.ts +24 -21
  39. package/Server/Utils/Monitor/MonitorIncident.ts +21 -18
  40. package/Server/Utils/Monitor/MonitorResourceContext.ts +107 -0
  41. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +442 -0
  42. package/Server/Utils/Monitor/SeriesResourceLabels.ts +3 -3
  43. package/Server/Utils/Monitor/SeriesResourceLinker.ts +96 -28
  44. package/Server/Utils/TotpAuth.ts +137 -9
  45. package/Server/Utils/UserRegistrationToken.ts +182 -0
  46. package/Server/Utils/VerificationCode.ts +134 -0
  47. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +173 -94
  48. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +1151 -0
  49. package/Tests/App/Dashboard/AdminUserOnCallPages.test.tsx +905 -0
  50. package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +338 -0
  51. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1 -1
  52. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +28 -0
  53. package/Tests/Models/UserPushCriticalAlertColumn.test.ts +172 -0
  54. package/Tests/Server/API/BaseAPIGetListParallel.test.ts +519 -0
  55. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +543 -0
  56. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +108 -18
  57. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +963 -0
  58. package/Tests/Server/API/UserPushCriticalAlertsApi.test.ts +114 -0
  59. package/Tests/Server/API/UserSmsApi.test.ts +107 -14
  60. package/Tests/Server/API/UserTotpAuthAPI.test.ts +427 -0
  61. package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +315 -0
  62. package/Tests/Server/Middleware/VerificationCodeRateLimit.test.ts +775 -0
  63. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +10 -62
  64. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +33 -13
  65. package/Tests/Server/Services/AnalyticsDatabaseSortKeyBoundary.test.ts +658 -0
  66. package/Tests/Server/Services/CriticalOnCallAlertDelivery.test.ts +458 -0
  67. package/Tests/Server/Services/CriticalPushAlertPayload.test.ts +301 -0
  68. package/Tests/Server/Services/DatabaseServiceAtomicIncrement.test.ts +143 -0
  69. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1 -0
  70. package/Tests/Server/Services/LogAggregationScanMemory.test.ts +421 -0
  71. package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
  72. package/Tests/Server/Services/NotificationChannelCodeIssuance.test.ts +401 -0
  73. package/Tests/Server/Services/PushNotificationDeliveryOptions.test.ts +204 -0
  74. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +21 -1
  75. package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +290 -0
  76. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +948 -0
  77. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1 -0
  78. package/Tests/Server/Services/UserPushCriticalAlertToggle.test.ts +255 -0
  79. package/Tests/Server/Services/UserTotpAuthEnrolment.test.ts +218 -0
  80. package/Tests/Server/TestingUtils/AuthenticatorApp.ts +248 -0
  81. package/Tests/Server/Types/Database/Permissions/NotificationChannelVerificationColumns.test.ts +135 -0
  82. package/Tests/Server/Types/Database/Permissions/OnCallScheduleRelationSelect.test.ts +504 -0
  83. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAnyOf.test.ts +91 -0
  84. package/Tests/Server/Types/Workflow/Components/JsonToText.test.ts +384 -0
  85. package/Tests/Server/Types/Workflow/Components/MergeJson.test.ts +403 -0
  86. package/Tests/Server/Utils/Browser.test.ts +162 -0
  87. package/Tests/Server/Utils/ChannelVerification.test.ts +689 -0
  88. package/Tests/Server/Utils/Express.test.ts +371 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.test.ts +707 -0
  90. package/Tests/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.test.ts +698 -0
  91. package/Tests/Server/Utils/Monitor/Criteria/IncomingEmailBodyCriteria.test.ts +243 -0
  92. package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestBodyCriteria.test.ts +278 -0
  93. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +160 -20
  94. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +13 -8
  95. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +162 -18
  96. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +349 -0
  97. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +690 -0
  98. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +7 -2
  99. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +92 -18
  100. package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +267 -0
  101. package/Tests/Server/Utils/Telemetry/OneuptimeLabel.test.ts +439 -0
  102. package/Tests/Server/Utils/TotpAuth.test.ts +719 -0
  103. package/Tests/Server/Utils/UserRegistrationToken.test.ts +322 -0
  104. package/Tests/Server/Utils/VerificationCode.test.ts +296 -0
  105. package/Tests/Types/AutoRemediation/AutoRemediationSuggestionStatus.test.ts +192 -0
  106. package/Tests/Types/Call/CallRequest.test.ts +189 -0
  107. package/Tests/Types/Dashboard/DashboardSize.test.ts +226 -0
  108. package/Tests/Types/Incident/IncidentSlaStatus.test.ts +191 -0
  109. package/Tests/Types/Metrics/MetricDashboardMetricType.test.ts +278 -0
  110. package/Tests/Types/Metrics/MetricPipelineRuleType.test.ts +517 -0
  111. package/Tests/Types/Monitor/IncomingMonitorDefaultCriteria.test.ts +470 -0
  112. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +6 -4
  113. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +262 -5
  114. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
  115. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
  116. package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +905 -0
  117. package/Tests/Types/Service/ServiceLanguage.test.ts +255 -0
  118. package/Tests/Types/TextRandomGeneration.test.ts +211 -0
  119. package/Tests/UI/Components/Button.test.tsx +175 -1
  120. package/Tests/UI/Components/CodeBlockLanguages.test.tsx +236 -0
  121. package/Tests/UI/Components/CommandPalette/CommandPalette.test.tsx +568 -0
  122. package/Tests/UI/Components/CommandPalette/CommandPaletteKeyboard.test.tsx +239 -0
  123. package/Tests/UI/Components/CommandPalette/CommandPaletteProviders.test.tsx +400 -0
  124. package/Tests/UI/Components/CommandPalette/PaletteFilter.test.ts +234 -0
  125. package/Tests/UI/Components/CommandPalette/RecentCommands.test.ts +97 -0
  126. package/Tests/UI/Components/CountModelSideMenuItemGuard.test.tsx +198 -0
  127. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +1 -1
  128. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +267 -0
  129. package/Tests/UI/Components/List.test.tsx +9 -2
  130. package/Tests/UI/Components/ListLoadingStates.test.tsx +173 -0
  131. package/Tests/UI/Components/MarkdownLazy.test.tsx +113 -0
  132. package/Tests/UI/Components/MarkdownMermaidRetry.test.tsx +110 -0
  133. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +8 -0
  134. package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +692 -0
  135. package/Tests/UI/Components/MoreMenuMotion.test.tsx +118 -0
  136. package/Tests/UI/Components/NavBarCommandKGating.test.tsx +178 -0
  137. package/Tests/UI/Components/OrderedStatesList.test.tsx +19 -2
  138. package/Tests/UI/Components/SideOverMotion.test.tsx +109 -0
  139. package/Tests/UI/Components/Skeleton.test.tsx +71 -0
  140. package/Tests/UI/Components/TableLoadingStates.test.tsx +279 -0
  141. package/Tests/UI/Components/Tabs.test.tsx +24 -0
  142. package/Tests/UI/Components/Toast.test.tsx +130 -7
  143. package/Tests/UI/Components/ToastStacking.test.tsx +198 -0
  144. package/Tests/UI/Utils/Dropdown.test.ts +423 -0
  145. package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +122 -0
  146. package/Tests/UI/Utils/ModelListCache.test.ts +338 -0
  147. package/Tests/UI/Utils/ProjectListCacheInvalidation.test.ts +225 -0
  148. package/Tests/Utils/API.test.ts +16 -2
  149. package/Tests/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.test.ts +347 -0
  150. package/Tests/Utils/Dashboard/Components/DashboardListSharedArgs.test.ts +259 -0
  151. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +466 -0
  152. package/Tests/Utils/Rum/UrlScrubberSegments.test.ts +362 -0
  153. package/Types/Email/EmailTemplateType.ts +1 -0
  154. package/Types/Monitor/MonitorCriteriaInstance.ts +41 -20
  155. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +131 -8
  156. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +48 -8
  157. package/Types/Monitor/ServiceAlertTemplates.ts +1352 -0
  158. package/Types/Permission.ts +35 -0
  159. package/Types/PushNotification/AndroidNotificationChannel.ts +26 -0
  160. package/Types/PushNotification/PushNotificationMessage.ts +15 -0
  161. package/Types/PushNotification/PushNotificationRequest.ts +8 -16
  162. package/Types/Service/ServiceLanguage.ts +144 -0
  163. package/Types/Text.ts +111 -19
  164. package/UI/Components/Button/Button.tsx +27 -14
  165. package/UI/Components/CodeBlock/CodeBlock.tsx +54 -7
  166. package/UI/Components/CodeBlock/LanguageRegistry.ts +103 -0
  167. package/UI/Components/CommandPalette/CommandPalette.tsx +790 -0
  168. package/UI/Components/CommandPalette/PaletteFilter.ts +196 -0
  169. package/UI/Components/CommandPalette/PaletteRow.tsx +173 -0
  170. package/UI/Components/CommandPalette/RecentCommands.ts +57 -0
  171. package/UI/Components/CommandPalette/Types.ts +49 -0
  172. package/UI/Components/CommandPalette/UseProviderSearch.ts +129 -0
  173. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  174. package/UI/Components/Feed/FeedItem.tsx +1 -1
  175. package/UI/Components/List/List.tsx +37 -5
  176. package/UI/Components/List/ListSkeleton.tsx +54 -0
  177. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +6 -1
  178. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +71 -26
  179. package/UI/Components/Modal/Modal.tsx +2 -2
  180. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  181. package/UI/Components/ModelTable/useCustomFieldColumns.ts +11 -5
  182. package/UI/Components/MoreMenu/MoreMenu.tsx +39 -1
  183. package/UI/Components/Navbar/NavBar.tsx +14 -1
  184. package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +50 -5
  185. package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +40 -1
  186. package/UI/Components/SideOver/SideOver.tsx +37 -2
  187. package/UI/Components/Skeleton/Skeleton.tsx +53 -0
  188. package/UI/Components/Table/Table.tsx +74 -44
  189. package/UI/Components/Table/TableSkeletonRows.tsx +141 -0
  190. package/UI/Components/Tabs/Tab.tsx +1 -1
  191. package/UI/Components/Toast/Toast.tsx +118 -53
  192. package/UI/Components/Toast/ToastInit.tsx +82 -32
  193. package/UI/Styles/Theme.css +40 -0
  194. package/UI/Utils/ModelAPI/ModelAPI.ts +20 -10
  195. package/UI/Utils/ModelListCache.ts +188 -0
  196. package/UI/Utils/Project.ts +29 -0
  197. package/Utils/API.ts +6 -1
  198. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +14 -0
  199. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/UserCall.js +101 -3
  201. package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/UserEmail.js +101 -3
  203. package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js +101 -3
  205. package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/UserPush.js +38 -0
  207. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/UserSMS.js +101 -3
  209. package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/UserWhatsApp.js +100 -2
  211. package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
  212. package/build/dist/Server/API/BaseAPI.js +69 -13
  213. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  214. package/build/dist/Server/API/UserCallAPI.js +42 -38
  215. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  216. package/build/dist/Server/API/UserEmailAPI.js +42 -38
  217. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  218. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +37 -32
  219. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  220. package/build/dist/Server/API/UserNotificationMethodAdminAPI.js +341 -0
  221. package/build/dist/Server/API/UserNotificationMethodAdminAPI.js.map +1 -0
  222. package/build/dist/Server/API/UserPushAPI.js +95 -2
  223. package/build/dist/Server/API/UserPushAPI.js.map +1 -1
  224. package/build/dist/Server/API/UserSmsAPI.js +42 -38
  225. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  226. package/build/dist/Server/API/UserWhatsAppAPI.js +42 -40
  227. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  228. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js +89 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js.map +1 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js +12 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js.map +1 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js +40 -0
  233. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js.map +1 -0
  234. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  236. package/build/dist/Server/Middleware/VerificationCodeRateLimit.js +393 -0
  237. package/build/dist/Server/Middleware/VerificationCodeRateLimit.js.map +1 -0
  238. package/build/dist/Server/Services/AnalyticsDatabaseService.js +136 -0
  239. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  240. package/build/dist/Server/Services/DatabaseService.js +74 -0
  241. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  242. package/build/dist/Server/Services/LogAggregationService.js +5 -0
  243. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  244. package/build/dist/Server/Services/PushNotificationService.js +69 -30
  245. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  246. package/build/dist/Server/Services/TeamMemberService.js +43 -7
  247. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  248. package/build/dist/Server/Services/UserCallService.js +54 -22
  249. package/build/dist/Server/Services/UserCallService.js.map +1 -1
  250. package/build/dist/Server/Services/UserEmailService.js +78 -17
  251. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  252. package/build/dist/Server/Services/UserIncomingCallNumberService.js +45 -74
  253. package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
  254. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1117 -0
  255. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -0
  256. package/build/dist/Server/Services/UserNotificationRuleService.js +20 -0
  257. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  258. package/build/dist/Server/Services/UserPushService.js +77 -0
  259. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  260. package/build/dist/Server/Services/UserSmsService.js +50 -17
  261. package/build/dist/Server/Services/UserSmsService.js.map +1 -1
  262. package/build/dist/Server/Services/UserWhatsAppService.js +48 -15
  263. package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
  264. package/build/dist/Server/Types/Database/QueryHelper.js +27 -0
  265. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  266. package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js +46 -0
  267. package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js.map +1 -1
  268. package/build/dist/Server/Utils/ChannelVerification.js +348 -0
  269. package/build/dist/Server/Utils/ChannelVerification.js.map +1 -0
  270. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +13 -12
  271. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
  272. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +20 -17
  273. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  274. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +17 -14
  275. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  276. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +95 -0
  277. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -0
  278. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +313 -0
  279. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -0
  280. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +3 -3
  281. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +67 -26
  282. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  283. package/build/dist/Server/Utils/TotpAuth.js +127 -8
  284. package/build/dist/Server/Utils/TotpAuth.js.map +1 -1
  285. package/build/dist/Server/Utils/UserRegistrationToken.js +170 -0
  286. package/build/dist/Server/Utils/UserRegistrationToken.js.map +1 -0
  287. package/build/dist/Server/Utils/VerificationCode.js +120 -0
  288. package/build/dist/Server/Utils/VerificationCode.js.map +1 -0
  289. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  290. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  291. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +42 -21
  292. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  293. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +77 -5
  294. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  295. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +18 -6
  296. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  297. package/build/dist/Types/Monitor/ServiceAlertTemplates.js +1028 -0
  298. package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -0
  299. package/build/dist/Types/Permission.js +33 -0
  300. package/build/dist/Types/Permission.js.map +1 -1
  301. package/build/dist/Types/PushNotification/AndroidNotificationChannel.js +27 -0
  302. package/build/dist/Types/PushNotification/AndroidNotificationChannel.js.map +1 -0
  303. package/build/dist/Types/Service/ServiceLanguage.js +96 -0
  304. package/build/dist/Types/Service/ServiceLanguage.js.map +1 -0
  305. package/build/dist/Types/Text.js +82 -17
  306. package/build/dist/Types/Text.js.map +1 -1
  307. package/build/dist/UI/Components/Button/Button.js +22 -14
  308. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  309. package/build/dist/UI/Components/CodeBlock/CodeBlock.js +35 -3
  310. package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
  311. package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js +57 -0
  312. package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js.map +1 -0
  313. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +439 -0
  314. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -0
  315. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js +135 -0
  316. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js.map +1 -0
  317. package/build/dist/UI/Components/CommandPalette/PaletteRow.js +83 -0
  318. package/build/dist/UI/Components/CommandPalette/PaletteRow.js.map +1 -0
  319. package/build/dist/UI/Components/CommandPalette/RecentCommands.js +47 -0
  320. package/build/dist/UI/Components/CommandPalette/RecentCommands.js.map +1 -0
  321. package/build/dist/UI/Components/CommandPalette/Types.js +2 -0
  322. package/build/dist/UI/Components/CommandPalette/Types.js.map +1 -0
  323. package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js +88 -0
  324. package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js.map +1 -0
  325. package/build/dist/UI/Components/Dropdown/Dropdown.js +6 -1
  326. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  327. package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
  328. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  329. package/build/dist/UI/Components/List/List.js +19 -5
  330. package/build/dist/UI/Components/List/List.js.map +1 -1
  331. package/build/dist/UI/Components/List/ListSkeleton.js +21 -0
  332. package/build/dist/UI/Components/List/ListSkeleton.js.map +1 -0
  333. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  334. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +52 -25
  335. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  336. package/build/dist/UI/Components/Modal/Modal.js +2 -2
  337. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  338. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  339. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +9 -2
  340. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  341. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +32 -1
  342. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  343. package/build/dist/UI/Components/Navbar/NavBar.js +5 -1
  344. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  345. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +29 -5
  346. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
  347. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -1
  348. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
  349. package/build/dist/UI/Components/SideOver/SideOver.js +23 -2
  350. package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
  351. package/build/dist/UI/Components/Skeleton/Skeleton.js +23 -0
  352. package/build/dist/UI/Components/Skeleton/Skeleton.js.map +1 -0
  353. package/build/dist/UI/Components/Table/Table.js +35 -24
  354. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  355. package/build/dist/UI/Components/Table/TableSkeletonRows.js +52 -0
  356. package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -0
  357. package/build/dist/UI/Components/Tabs/Tab.js +1 -1
  358. package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
  359. package/build/dist/UI/Components/Toast/Toast.js +77 -23
  360. package/build/dist/UI/Components/Toast/Toast.js.map +1 -1
  361. package/build/dist/UI/Components/Toast/ToastInit.js +37 -15
  362. package/build/dist/UI/Components/Toast/ToastInit.js.map +1 -1
  363. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -9
  364. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  365. package/build/dist/UI/Utils/ModelListCache.js +126 -0
  366. package/build/dist/UI/Utils/ModelListCache.js.map +1 -0
  367. package/build/dist/UI/Utils/Project.js +24 -1
  368. package/build/dist/UI/Utils/Project.js.map +1 -1
  369. package/build/dist/Utils/API.js +6 -1
  370. package/build/dist/Utils/API.js.map +1 -1
  371. package/jest.config.json +1 -1
  372. package/package.json +2 -3
  373. package/Server/Utils/Monitor/MonitorClusterContext.ts +0 -182
  374. package/Tests/__mocks__/otpauth.js +0 -30
  375. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +0 -141
  376. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +0 -1
@@ -1542,6 +1542,159 @@ export default class AnalyticsDatabaseService<
1542
1542
  return { ...sort, _id: direction } as Sort<TBaseModel>;
1543
1543
  }
1544
1544
 
1545
+ /**
1546
+ * Bounds the rows an `ORDER BY ... , _id` find has to sort, without
1547
+ * changing which rows it returns.
1548
+ *
1549
+ * ClickHouse can read in sorting-key order — and stop early — only when the
1550
+ * ORDER BY lines up with the table's physical key. The `_id` tiebreaker
1551
+ * appended by `toPaginationStableSort` is deliberately NOT a sorting-key
1552
+ * column, so it denies that plan: the engine reads every row matching the
1553
+ * WHERE, sorts the lot in memory, then discards all but one page. On the
1554
+ * Logs viewer that turned a 100-row page into a full-window scan and tripped
1555
+ * the 3 GiB `max_memory_usage` ceiling (Code 241, surfacing as
1556
+ * "Server Error").
1557
+ *
1558
+ * Weakening the tiebreaker would reintroduce the skip/repeat paging bug it
1559
+ * was added to fix, so bound the input instead. `min(k)` over the top
1560
+ * `skip + limit` values of the leading sort key `k` is a rank statistic of
1561
+ * k's MULTISET: it does not depend on which of several tied rows the inner
1562
+ * LIMIT happened to keep. Every row that could reach position `skip + limit`
1563
+ * satisfies `k >= min(k)`, and every row tied at the boundary value is
1564
+ * admitted — so the page is exactly the page the unbounded query returns.
1565
+ * That is what makes this a pure cost change: every gate below is a
1566
+ * performance gate, and getting one wrong can only make a query slower,
1567
+ * never lose or repeat a row.
1568
+ *
1569
+ * The inner pass reads one narrow key column in physical order and stops at
1570
+ * `skip + limit` rows, so it costs far less than the wide outer read it
1571
+ * saves.
1572
+ *
1573
+ * `min()` is nested over a subquery rather than taken as the N-th value via
1574
+ * `ORDER BY k DESC LIMIT 1 OFFSET n - 1` on purpose: on a final page with
1575
+ * fewer than `skip + limit` matching rows the latter returns no row, the
1576
+ * scalar is NULL, `k >= NULL` is NULL, and the page comes back empty.
1577
+ * `min()` over the available rows degrades to the smallest matching value.
1578
+ */
1579
+ private toSortKeyBoundaryFilter(
1580
+ findBy: FindBy<TBaseModel>,
1581
+ sort: Sort<TBaseModel>,
1582
+ options: { hasGroupBy: boolean; databaseName: string },
1583
+ ): Statement | null {
1584
+ /*
1585
+ * Under GROUP BY the predicate would restrict the aggregation INPUT
1586
+ * rather than the page, so it would change results. Mirrors the
1587
+ * tiebreaker's own exemption, leaving grouped finds byte-identical.
1588
+ */
1589
+ if (options.hasGroupBy) {
1590
+ return null;
1591
+ }
1592
+
1593
+ /*
1594
+ * Without an `_id` column there is no off-key tiebreaker — the six
1595
+ * `includeBaseColumns: false` aggregate targets — so the ORDER BY is
1596
+ * already key-aligned and the bound would be pure overhead. Same
1597
+ * condition toPaginationStableSort bails on.
1598
+ */
1599
+ const hasIdColumn: boolean = this.model.tableColumns.some(
1600
+ (column: AnalyticsTableColumn): boolean => {
1601
+ return column.key === "_id";
1602
+ },
1603
+ );
1604
+
1605
+ if (!hasIdColumn) {
1606
+ return null;
1607
+ }
1608
+
1609
+ const leadingSortKey: string | undefined = Object.keys(sort || {})[0];
1610
+
1611
+ if (!leadingSortKey) {
1612
+ return null;
1613
+ }
1614
+
1615
+ /*
1616
+ * Only a column in the physical sorting key can be read in order, so
1617
+ * only there can the off-key `_id` term have cost anything. Off-key
1618
+ * leads (e.g. Log by `severityText`) already plan a bounded top-N and
1619
+ * an extra pass would be pure waste.
1620
+ */
1621
+ if (!this.model.sortKeys.includes(leadingSortKey)) {
1622
+ return null;
1623
+ }
1624
+
1625
+ /*
1626
+ * A non-required column is `Nullable(...)` in the DDL, and `k >= NULL`
1627
+ * is NULL — the predicate would DROP those rows rather than just
1628
+ * narrowing the scan. Fail safe to today's behavior.
1629
+ */
1630
+ const leadingSortColumn: AnalyticsTableColumn | null =
1631
+ this.model.getTableColumn(leadingSortKey);
1632
+
1633
+ if (!leadingSortColumn || !leadingSortColumn.required) {
1634
+ return null;
1635
+ }
1636
+
1637
+ /*
1638
+ * `findOneById` funnels a point lookup through `findOneBy` carrying the
1639
+ * model's default sort. At most one row can match, so bounding it would
1640
+ * double a query that has no ordering problem to begin with.
1641
+ */
1642
+ if ((findBy.query as Record<string, unknown> | undefined)?.["_id"]) {
1643
+ return null;
1644
+ }
1645
+
1646
+ /*
1647
+ * The predicate is valid only BECAUSE of LIMIT/OFFSET: it admits the
1648
+ * rows that can reach position `skip + limit` and no more. Guards the
1649
+ * `new PositiveNumber(undefined)` -> NaN path that exists upstream.
1650
+ */
1651
+ const limit: number = Number(findBy.limit);
1652
+ const skip: number = Number(findBy.skip);
1653
+
1654
+ if (!Number.isFinite(limit) || !Number.isFinite(skip)) {
1655
+ return null;
1656
+ }
1657
+
1658
+ const boundaryRowCount: number = skip + limit;
1659
+
1660
+ if (boundaryRowCount <= 0) {
1661
+ return null;
1662
+ }
1663
+
1664
+ const isDescending: boolean =
1665
+ (sort as Record<string, SortOrder | undefined>)[leadingSortKey] !==
1666
+ SortOrder.Ascending;
1667
+
1668
+ /*
1669
+ * The bound must be computed over the same row set the outer query
1670
+ * filters on, so the WHERE is regenerated rather than shared: appending
1671
+ * one Statement into two places would bind its parameters twice.
1672
+ */
1673
+ const boundaryWhereStatement: Statement =
1674
+ this.statementGenerator.toWhereStatement(findBy.query);
1675
+
1676
+ const boundaryStatement: Statement = SQL` AND ${leadingSortKey} `;
1677
+
1678
+ boundaryStatement.append(isDescending ? SQL`>=` : SQL`<=`);
1679
+ boundaryStatement.append(
1680
+ isDescending
1681
+ ? SQL` (SELECT min(${leadingSortKey}) FROM (SELECT ${leadingSortKey} FROM ${options.databaseName}.${this.model.tableName} WHERE TRUE `
1682
+ : SQL` (SELECT max(${leadingSortKey}) FROM (SELECT ${leadingSortKey} FROM ${options.databaseName}.${this.model.tableName} WHERE TRUE `,
1683
+ );
1684
+ boundaryStatement.append(boundaryWhereStatement);
1685
+ boundaryStatement.append(this.getRetentionReadFilter());
1686
+ boundaryStatement.append(SQL` ORDER BY ${leadingSortKey} `);
1687
+ boundaryStatement.append(isDescending ? SQL`DESC` : SQL`ASC`);
1688
+ boundaryStatement.append(
1689
+ SQL` LIMIT ${{
1690
+ value: boundaryRowCount,
1691
+ type: TableColumnType.Number,
1692
+ }}))`,
1693
+ );
1694
+
1695
+ return boundaryStatement;
1696
+ }
1697
+
1545
1698
  public toFindStatement(findBy: FindBy<TBaseModel>): {
1546
1699
  statement: Statement;
1547
1700
  columns: Array<string>;
@@ -1577,6 +1730,18 @@ export default class AnalyticsDatabaseService<
1577
1730
  }),
1578
1731
  );
1579
1732
 
1733
+ /*
1734
+ * Keeps the `_id` tiebreaker affordable. Result-preserving by
1735
+ * construction, so it is appended to the WHERE without touching the
1736
+ * ORDER BY above. Null whenever the bound cannot help or cannot be
1737
+ * proven safe.
1738
+ */
1739
+ const sortKeyBoundaryStatement: Statement | null =
1740
+ this.toSortKeyBoundaryFilter(findBy, findBy.sort!, {
1741
+ hasGroupBy: Boolean(groupByStatement),
1742
+ databaseName: databaseName,
1743
+ });
1744
+
1580
1745
  const statement: Statement = SQL``;
1581
1746
 
1582
1747
  statement.append(SQL`SELECT `.append(select.statement));
@@ -1586,6 +1751,10 @@ export default class AnalyticsDatabaseService<
1586
1751
  .append(whereStatement)
1587
1752
  .append(this.getRetentionReadFilter());
1588
1753
 
1754
+ if (sortKeyBoundaryStatement) {
1755
+ statement.append(sortKeyBoundaryStatement);
1756
+ }
1757
+
1589
1758
  if (groupByStatement) {
1590
1759
  statement.append(SQL` GROUP BY `).append(groupByStatement);
1591
1760
  }
@@ -2886,6 +2886,107 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
2886
2886
  await repository.manager.query(sql, params);
2887
2887
  }
2888
2888
 
2889
+ /*
2890
+ * Add one to a numeric column and return what it became, in a SINGLE
2891
+ * statement.
2892
+ *
2893
+ * This exists for counters that GATE something, where the read and the
2894
+ * write cannot be allowed to come apart. The motivating case is the failed
2895
+ * attempt counter on a notification-channel verification code: read the
2896
+ * count, decide, then write it back, and N requests racing each other all
2897
+ * read the same pre-increment value and all decide they are under the
2898
+ * limit — which is precisely the shape an attacker running a brute-force
2899
+ * loop produces. `atomicAddToColumnsByIdWithoutHooks` closes the
2900
+ * lost-update half of that but cannot answer "and what is it now?", and
2901
+ * `getRepository().increment()` cannot either.
2902
+ *
2903
+ * RETURNING makes the increment and the observation the same operation, so
2904
+ * every concurrent caller gets a distinct value and the k-th attempt is
2905
+ * refused no matter how the requests interleave.
2906
+ *
2907
+ * COALESCE so a NULL counter starts from zero rather than staying NULL.
2908
+ *
2909
+ * The column and table identifiers come from entity metadata, never from
2910
+ * the caller, and the id is bound as a parameter. Hooks, the `version`
2911
+ * bump and access control are all skipped: use ONLY for trusted internal
2912
+ * counter writes.
2913
+ */
2914
+ @CaptureSpan()
2915
+ public async atomicIncrementColumnValueByOneAndGetValue(data: {
2916
+ id: ObjectID;
2917
+ columnName: keyof TBaseModel;
2918
+ }): Promise<number> {
2919
+ if (!data.id) {
2920
+ throw new BadDataException("id is required");
2921
+ }
2922
+
2923
+ const repository: Repository<TBaseModel> = this.getRepository();
2924
+ const metadata: EntityMetadata = repository.metadata;
2925
+
2926
+ const column: ColumnMetadata | undefined =
2927
+ metadata.findColumnWithPropertyName(data.columnName as string);
2928
+
2929
+ if (!column) {
2930
+ throw new BadDataException(
2931
+ `atomicIncrementColumnValueByOneAndGetValue: unknown column "${String(
2932
+ data.columnName,
2933
+ )}" on "${metadata.tableName}"`,
2934
+ );
2935
+ }
2936
+
2937
+ const primaryColumnName: string =
2938
+ metadata.primaryColumns[0]?.databaseName || "_id";
2939
+
2940
+ const quoted: string = `"${column.databaseName}"`;
2941
+
2942
+ const sql: string = `UPDATE "${metadata.tableName}" SET ${quoted} = COALESCE(${quoted}, 0) + 1 WHERE "${primaryColumnName}" = $1 RETURNING ${quoted}`;
2943
+
2944
+ const result: unknown = await repository.manager.query(sql, [
2945
+ data.id.toString(),
2946
+ ]);
2947
+
2948
+ /*
2949
+ * TypeORM's Postgres driver returns `[rows, affectedCount]` for an UPDATE
2950
+ * and a bare row array for everything else, so both shapes are unwrapped
2951
+ * rather than one being assumed.
2952
+ */
2953
+ const rows: Array<unknown> = Array.isArray(result)
2954
+ ? Array.isArray(result[0])
2955
+ ? (result[0] as Array<unknown>)
2956
+ : (result as Array<unknown>)
2957
+ : [];
2958
+
2959
+ const row: unknown = rows[0];
2960
+
2961
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
2962
+ /*
2963
+ * Nothing came back, so the row does not exist — deleted underneath us,
2964
+ * or never there. Callers gate on the returned count, so this must be an
2965
+ * error and not a zero, which would read as a fresh allowance.
2966
+ */
2967
+ throw new BadDataException(
2968
+ `Item with ID ${data.id.toString()} not found`,
2969
+ );
2970
+ }
2971
+
2972
+ const value: unknown = (row as Record<string, unknown>)[
2973
+ column.databaseName
2974
+ ];
2975
+
2976
+ const parsed: number =
2977
+ typeof value === "number" ? value : parseInt(String(value), 10);
2978
+
2979
+ if (!Number.isFinite(parsed)) {
2980
+ throw new BadDataException(
2981
+ `atomicIncrementColumnValueByOneAndGetValue: "${String(
2982
+ data.columnName,
2983
+ )}" did not return a number`,
2984
+ );
2985
+ }
2986
+
2987
+ return parsed;
2988
+ }
2989
+
2889
2990
  @CaptureSpan()
2890
2991
  protected async atomicIncrementColumnValueByOne(data: {
2891
2992
  id: ObjectID;
@@ -282,6 +282,7 @@ export class LogAggregationService {
282
282
  getQuerySettings({
283
283
  maxExecutionTimeInSeconds: 45,
284
284
  timeoutOverflowMode: "break",
285
+ boundScanMemory: true,
285
286
  additionalSettings: { optimize_use_projections: 1 },
286
287
  }),
287
288
  );
@@ -392,6 +393,7 @@ export class LogAggregationService {
392
393
  getQuerySettings({
393
394
  maxExecutionTimeInSeconds: 45,
394
395
  timeoutOverflowMode: "break",
396
+ boundScanMemory: true,
395
397
  }),
396
398
  );
397
399
 
@@ -634,6 +636,7 @@ export class LogAggregationService {
634
636
  getQuerySettings({
635
637
  maxExecutionTimeInSeconds: 45,
636
638
  timeoutOverflowMode: "break",
639
+ boundScanMemory: true,
637
640
  }),
638
641
  );
639
642
 
@@ -714,6 +717,7 @@ export class LogAggregationService {
714
717
  getQuerySettings({
715
718
  maxExecutionTimeInSeconds: 45,
716
719
  timeoutOverflowMode: "break",
720
+ boundScanMemory: true,
717
721
  }),
718
722
  );
719
723
 
@@ -783,6 +787,7 @@ export class LogAggregationService {
783
787
  getQuerySettings({
784
788
  maxExecutionTimeInSeconds: 45,
785
789
  timeoutOverflowMode: "break",
790
+ boundScanMemory: true,
786
791
  }),
787
792
  );
788
793
 
@@ -26,6 +26,7 @@ import UserPush from "../../Models/DatabaseModels/UserPush";
26
26
  import PushNotificationLog from "../../Models/DatabaseModels/PushNotificationLog";
27
27
  import PushNotificationLogService from "./PushNotificationLogService";
28
28
  import PushStatus from "../../Types/PushNotification/PushStatus";
29
+ import AndroidNotificationChannel from "../../Types/PushNotification/AndroidNotificationChannel";
29
30
 
30
31
  /*
31
32
  * The push services the browsers actually use. A Web Push subscription is a
@@ -67,6 +68,43 @@ export interface PushNotificationOptions {
67
68
  teamId?: ObjectID | undefined;
68
69
  }
69
70
 
71
+ /*
72
+ * What Expo needs to be told for a notification to ring through a silenced
73
+ * handset. The two platforms disagree about where the answer lives, so this is
74
+ * computed once and reused by both the direct-SDK and relay send paths.
75
+ *
76
+ * iOS reads the payload: a `sound` object with `critical: true` plus
77
+ * `interruptionLevel: "critical"` is what makes APNs ignore the ringer switch
78
+ * and Focus, and it is refused unless the app carries Apple's critical-alert
79
+ * entitlement.
80
+ *
81
+ * Android ignores all of that and reads the CHANNEL. Sound, importance and Do
82
+ * Not Disturb bypass are baked into the channel when the app creates it and
83
+ * cannot be raised by a payload, so the only lever the server has is which
84
+ * channel id it names.
85
+ */
86
+ export type ExpoPushSound =
87
+ | string
88
+ | null
89
+ | {
90
+ critical?: boolean;
91
+ name?: string | null;
92
+ volume?: number;
93
+ };
94
+
95
+ export type ExpoInterruptionLevel =
96
+ | "active"
97
+ | "critical"
98
+ | "passive"
99
+ | "time-sensitive";
100
+
101
+ export interface ExpoDeliveryOptions {
102
+ channelId: string;
103
+ sound: ExpoPushSound;
104
+ priority: "high";
105
+ interruptionLevel?: ExpoInterruptionLevel;
106
+ }
107
+
70
108
  export default class PushNotificationService {
71
109
  public static isWebPushInitialized = false;
72
110
  private static expoClient: Expo = new Expo(
@@ -408,6 +446,57 @@ export default class PushNotificationService {
408
446
  }
409
447
  }
410
448
 
449
+ /*
450
+ * Volume is pinned rather than exposed as a setting. A critical alert exists
451
+ * to wake somebody, and a responder who has already opted this device in and
452
+ * granted the OS permission has not asked to be woken quietly.
453
+ */
454
+ public static readonly CRITICAL_ALERT_VOLUME: number = 1;
455
+
456
+ public static getExpoDeliveryOptions(
457
+ message: PushNotificationMessage,
458
+ deviceType: PushDeviceType,
459
+ ): ExpoDeliveryOptions {
460
+ const isAndroid: boolean = deviceType === PushDeviceType.Android;
461
+ const isCritical: boolean = Boolean(message.isCriticalAlert);
462
+
463
+ /*
464
+ * iOS has no channels, so "default" here is the payload's own sound rather
465
+ * than a channel id. Android without the critical flag stays on
466
+ * oncall_high, which is what every push has used until now.
467
+ */
468
+ const channelId: string = isAndroid
469
+ ? isCritical
470
+ ? AndroidNotificationChannel.Critical
471
+ : AndroidNotificationChannel.High
472
+ : "default";
473
+
474
+ if (!isCritical) {
475
+ return {
476
+ channelId: channelId,
477
+ sound: "default",
478
+ priority: "high",
479
+ };
480
+ }
481
+
482
+ return {
483
+ channelId: channelId,
484
+ /*
485
+ * Sent to Android too, and harmlessly ignored there. Expo forwards it to
486
+ * APNs where it is the whole mechanism, and to FCM where the channel has
487
+ * already decided the sound; keeping one shape avoids a per-platform
488
+ * branch that could silently drop the iOS half.
489
+ */
490
+ sound: {
491
+ critical: true,
492
+ name: "default",
493
+ volume: PushNotificationService.CRITICAL_ALERT_VOLUME,
494
+ },
495
+ priority: "high",
496
+ interruptionLevel: "critical",
497
+ };
498
+ }
499
+
411
500
  private static async sendExpoPushNotification(
412
501
  expoPushToken: string,
413
502
  message: PushNotificationMessage,
@@ -430,8 +519,10 @@ export default class PushNotificationService {
430
519
  dataPayload["url"] = message.url || message.clickAction || "";
431
520
  }
432
521
 
433
- const channelId: string =
434
- deviceType === PushDeviceType.Android ? "oncall_high" : "default";
522
+ const delivery: ExpoDeliveryOptions = this.getExpoDeliveryOptions(
523
+ message,
524
+ deviceType,
525
+ );
435
526
 
436
527
  // If EXPO_ACCESS_TOKEN is not set, relay through the push notification gateway
437
528
  if (!ExpoAccessToken) {
@@ -439,7 +530,7 @@ export default class PushNotificationService {
439
530
  expoPushToken,
440
531
  message,
441
532
  dataPayload,
442
- channelId,
533
+ delivery,
443
534
  deviceType,
444
535
  );
445
536
  return;
@@ -452,9 +543,12 @@ export default class PushNotificationService {
452
543
  title: message.title,
453
544
  body: message.body,
454
545
  data: dataPayload,
455
- sound: "default",
456
- priority: "high",
457
- channelId: channelId,
546
+ sound: delivery.sound,
547
+ priority: delivery.priority,
548
+ channelId: delivery.channelId,
549
+ ...(delivery.interruptionLevel
550
+ ? { interruptionLevel: delivery.interruptionLevel }
551
+ : {}),
458
552
  };
459
553
 
460
554
  const tickets: ExpoPushTicket[] =
@@ -500,7 +594,7 @@ export default class PushNotificationService {
500
594
  expoPushToken: string,
501
595
  message: PushNotificationMessage,
502
596
  dataPayload: { [key: string]: string },
503
- channelId: string,
597
+ delivery: ExpoDeliveryOptions,
504
598
  deviceType: PushDeviceType,
505
599
  ): Promise<void> {
506
600
  logger.info(
@@ -516,9 +610,19 @@ export default class PushNotificationService {
516
610
  title: message.title || "",
517
611
  body: message.body || "",
518
612
  data: dataPayload,
519
- sound: "default",
520
- priority: "high",
521
- channelId: channelId,
613
+ /*
614
+ * The relay re-sends exactly what it is given, so a critical page
615
+ * that loses its sound object here arrives on the responder's
616
+ * phone as an ordinary silent notification. That is the failure
617
+ * this feature exists to prevent, so the whole delivery shape
618
+ * crosses the wire rather than the channel id alone.
619
+ */
620
+ sound: delivery.sound,
621
+ priority: delivery.priority,
622
+ channelId: delivery.channelId,
623
+ ...(delivery.interruptionLevel
624
+ ? { interruptionLevel: delivery.interruptionLevel }
625
+ : {}),
522
626
  },
523
627
  });
524
628
 
@@ -552,9 +656,10 @@ export default class PushNotificationService {
552
656
  title?: string;
553
657
  body?: string;
554
658
  data?: { [key: string]: string };
555
- sound?: string;
659
+ sound?: ExpoPushSound;
556
660
  priority?: string;
557
661
  channelId?: string;
662
+ interruptionLevel?: ExpoInterruptionLevel;
558
663
  }): Promise<void> {
559
664
  if (!ExpoAccessToken) {
560
665
  throw new Error(
@@ -562,14 +667,24 @@ export default class PushNotificationService {
562
667
  );
563
668
  }
564
669
 
670
+ /*
671
+ * `sound: null` is a caller asking for a silent notification and has to
672
+ * survive, so the fallback tests for undefined rather than falsiness.
673
+ */
674
+ const sound: ExpoPushSound =
675
+ data.sound === undefined ? "default" : data.sound;
676
+
565
677
  const expoPushMessage: ExpoPushMessage = {
566
678
  to: data.to,
567
679
  title: data.title || "",
568
680
  body: data.body || "",
569
681
  data: data.data || {},
570
- sound: (data.sound as "default" | null) || "default",
682
+ sound: sound,
571
683
  priority: (data.priority as "default" | "normal" | "high") || "high",
572
684
  channelId: data.channelId || "default",
685
+ ...(data.interruptionLevel
686
+ ? { interruptionLevel: data.interruptionLevel }
687
+ : {}),
573
688
  };
574
689
 
575
690
  const tickets: ExpoPushTicket[] =
@@ -10,6 +10,7 @@ import UpdateBy from "../Types/Database/UpdateBy";
10
10
  import Errors from "../Utils/Errors";
11
11
  import logger, { LogAttributes } from "../Utils/Logger";
12
12
  import ProductAnalytics from "../Utils/ProductAnalytics";
13
+ import UserRegistrationToken from "../Utils/UserRegistrationToken";
13
14
  import AccessTokenService from "./AccessTokenService";
14
15
  import BillingService from "./BillingService";
15
16
  import DatabaseService from "./DatabaseService";
@@ -166,8 +167,23 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
166
167
  ? (createBy.miscDataProps["name"] as string).trim()
167
168
  : undefined;
168
169
 
169
- let user: User | null = await UserService.findByEmail(email, {
170
- isRoot: true,
170
+ /*
171
+ * `password` comes back too, because whether this person has finished
172
+ * registering decides both which link the invitation carries and whether
173
+ * that link needs a registration token. UserService.findByEmail selects
174
+ * only the id, so it cannot answer that.
175
+ */
176
+ let user: User | null = await UserService.findOneBy({
177
+ query: {
178
+ email: email,
179
+ },
180
+ select: {
181
+ _id: true,
182
+ password: true,
183
+ },
184
+ props: {
185
+ isRoot: true,
186
+ },
171
187
  });
172
188
 
173
189
  let isNewUser: boolean = false;
@@ -228,6 +244,39 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
228
244
  const host: Hostname = await DatabaseConfig.getHost();
229
245
  const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
230
246
 
247
+ /*
248
+ * "Still has to register", not "we just created the row". Someone
249
+ * invited to a second project before they ever signed up already has a
250
+ * user row and still has no password, and used to be sent the sign-in
251
+ * link — which they could not use, having no password to sign in with.
252
+ */
253
+ const needsRegistration: boolean = isNewUser || !user.password;
254
+
255
+ /*
256
+ * The token is what lets the recipient claim this account, so it exists
257
+ * only on the link inside this email and only for someone who has not
258
+ * registered yet. An already-registered invitee gets the plain link;
259
+ * they sign in instead, and minting a claim token for an account that
260
+ * cannot be claimed would just be a spare key lying around.
261
+ */
262
+ const registerLink: string = needsRegistration
263
+ ? (
264
+ await UserRegistrationToken.generateRegistrationLink({
265
+ userId: user.id!,
266
+ email: email,
267
+ })
268
+ ).toString()
269
+ : URL.fromString(
270
+ new URL(
271
+ httpProtocol,
272
+ host,
273
+ new Route(AccountsRoute.toString()),
274
+ ).toString(),
275
+ )
276
+ .addRoute("/register")
277
+ .addQueryParam("email", email.toString(), true)
278
+ .toString();
279
+
231
280
  MailService.sendMail(
232
281
  {
233
282
  toEmail: email,
@@ -240,17 +289,8 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
240
289
  new Route(AccountsRoute.toString()),
241
290
  ).toString(),
242
291
  ).toString(),
243
- registerLink: URL.fromString(
244
- new URL(
245
- httpProtocol,
246
- host,
247
- new Route(AccountsRoute.toString()),
248
- ).toString(),
249
- )
250
- .addRoute("/register")
251
- .addQueryParam("email", email.toString(), true)
252
- .toString(),
253
- isNewUser: isNewUser.toString(),
292
+ registerLink: registerLink,
293
+ isNewUser: needsRegistration.toString(),
254
294
  /*
255
295
  * An auto-accepted member has nothing left to accept, so the
256
296
  * template drops the "sign in to accept your invitation" framing