From 9dfbc056181d20d220f14637fe5a14b95a9d91dd Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 14 Feb 2026 14:53:26 +0000 Subject: [PATCH 01/44] fix: Nginx/Dockerfile.tpl to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-ALPINE322-LIBPNG-15062355 - https://snyk.io/vuln/SNYK-ALPINE322-LIBPNG-15062356 - https://snyk.io/vuln/SNYK-ALPINE322-OPENSSL-15121112 - https://snyk.io/vuln/SNYK-ALPINE322-OPENSSL-15121113 - https://snyk.io/vuln/SNYK-ALPINE322-OPENSSL-15121196 --- Nginx/Dockerfile.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nginx/Dockerfile.tpl b/Nginx/Dockerfile.tpl index e1aa0ace7c..1884343f05 100644 --- a/Nginx/Dockerfile.tpl +++ b/Nginx/Dockerfile.tpl @@ -1,4 +1,4 @@ -FROM nginx:1.29.3-alpine +FROM nginx:1.29.5-alpine ARG GIT_SHA From 01fd5263caf7f35fd83b78b77884bfce9957a131 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Mon, 16 Feb 2026 15:58:33 +0000 Subject: [PATCH 02/44] fix: upgrade playwright from 1.57.0 to 1.58.0 Snyk has created this PR to upgrade playwright from 1.57.0 to 1.58.0. See this package in npm: playwright See this project in Snyk: https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/49c81d9c-12c2-4e8e-b9e8-72f98b1b595c?utm_source=github&utm_medium=referral&page=upgrade-pr --- Probe/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Probe/package.json b/Probe/package.json index 12bfaa2f92..71e5aff821 100644 --- a/Probe/package.json +++ b/Probe/package.json @@ -29,7 +29,7 @@ "https-proxy-agent": "^7.0.5", "net-snmp": "^3.26.1", "ping": "^0.4.4", - "playwright": "^1.57.0", + "playwright": "^1.58.0", "ts-node": "^10.9.1" }, "devDependencies": { From 895af10755277a88ce1f5f89be22ab46827d08ea Mon Sep 17 00:00:00 2001 From: simlarsen <104437376+simlarsen@users.noreply.github.com> Date: Tue, 17 Feb 2026 02:28:09 +0000 Subject: [PATCH 03/44] chore: npm audit fix --- CLI/package-lock.json | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/CLI/package-lock.json b/CLI/package-lock.json index d83223abe1..1d8a2ed46d 100644 --- a/CLI/package-lock.json +++ b/CLI/package-lock.json @@ -83,7 +83,6 @@ "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", @@ -118,7 +117,6 @@ "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz", "integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==", "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", @@ -290,7 +288,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -811,7 +808,6 @@ "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-5.23.0.tgz", "integrity": "sha512-iI/Ssl8T5ZEn9s899Qz67m92M6RU8thf/aqD7cUHB2yHmkCjqbw7s7NaODTsyArAsnyu7DGJMWm7EhbfFXDNgQ==", "license": "MIT", - "peer": true, "dependencies": { "@bull-board/api": "5.23.0" } @@ -2312,7 +2308,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -4765,7 +4760,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4808,7 +4802,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5019,7 +5012,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5816,7 +5808,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6446,9 +6437,9 @@ }, "node_modules/Common": { "name": "@oneuptime/common", - "version": "9.5.10", - "resolved": "https://registry.npmjs.org/@oneuptime/common/-/common-9.5.10.tgz", - "integrity": "sha512-4UZ553L89Kd6pNVrUAZlB/eSiBI1e8QEm/5hWkHc+Pfv54yzkj1Smb7VssPlrLHU811I5iimI1/ZK+mD4B7aKA==", + "version": "9.5.13", + "resolved": "https://registry.npmjs.org/@oneuptime/common/-/common-9.5.13.tgz", + "integrity": "sha512-hHLUeApW4ILQcte5avvmj87m7w//tfcClDWWLsqkBWUSU94a4NSvpTEXh3dEN8WX3ty9/r7eYnobrjJK+7zIDA==", "license": "Apache-2.0", "dependencies": { "@asteasolutions/zod-to-openapi": "^7.3.2", @@ -6853,7 +6844,6 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10" } @@ -7275,7 +7265,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -9227,7 +9216,6 @@ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.3.tgz", "integrity": "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA==", "license": "MIT", - "peer": true, "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", @@ -9626,7 +9614,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -11956,6 +11943,7 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", + "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -11966,6 +11954,7 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -11975,6 +11964,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -12583,7 +12573,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -13205,7 +13194,6 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -13502,7 +13490,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -13587,7 +13574,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -15998,7 +15984,6 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -16311,7 +16296,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17157,7 +17141,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -17166,8 +17149,7 @@ "version": "0.15.1", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/zustand": { "version": "4.5.7", From b6ed3643c3694dc0ef3ae0af546b5a22bce0639f Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 20:57:17 +0000 Subject: [PATCH 04/44] fix: update splash screen background color and replace asset images --- MobileApp/app.json | 2 +- MobileApp/assets/adaptive-icon.png | Bin 17547 -> 41824 bytes MobileApp/assets/favicon.png | Bin 1466 -> 2069 bytes MobileApp/assets/icon.png | Bin 22380 -> 52610 bytes MobileApp/assets/splash-icon.png | Bin 17547 -> 45347 bytes 5 files changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileApp/app.json b/MobileApp/app.json index 0c65778632..0c2b4c6318 100644 --- a/MobileApp/app.json +++ b/MobileApp/app.json @@ -11,7 +11,7 @@ "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", - "backgroundColor": "#FFFFFF" + "backgroundColor": "#0D1117" }, "ios": { "supportsTablet": true, diff --git a/MobileApp/assets/adaptive-icon.png b/MobileApp/assets/adaptive-icon.png index 03d6f6b6c6727954aec1d8206222769afd178d8d..7e6722851cae0e46d99f0898fdff0def6ce5a25d 100644 GIT binary patch literal 41824 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 Y$A;eR_F6sLZu#-WM8>mDMC9)K3-IOo7ytkO delta 1459 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 literal 22380 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 diff --git a/MobileApp/assets/splash-icon.png b/MobileApp/assets/splash-icon.png index 03d6f6b6c6727954aec1d8206222769afd178d8d..10a95fa59a41ba2fd4bc663d4a9eba02f7d23382 100644 GIT binary patch literal 45347 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 Date: Tue, 17 Feb 2026 21:07:19 +0000 Subject: [PATCH 05/44] refactor: replace Pressable with TouchableOpacity for theme selection options --- MobileApp/src/screens/SettingsScreen.tsx | 137 +++++++++++------------ 1 file changed, 66 insertions(+), 71 deletions(-) diff --git a/MobileApp/src/screens/SettingsScreen.tsx b/MobileApp/src/screens/SettingsScreen.tsx index eba41406f0..e4b7e8d5bd 100644 --- a/MobileApp/src/screens/SettingsScreen.tsx +++ b/MobileApp/src/screens/SettingsScreen.tsx @@ -1,5 +1,12 @@ import React, { useState, useEffect } from "react"; -import { View, Text, ScrollView, Switch, Pressable } from "react-native"; +import { + View, + Text, + ScrollView, + Switch, + Pressable, + TouchableOpacity, +} from "react-native"; import { Ionicons } from "@expo/vector-icons"; import { LinearGradient } from "expo-linear-gradient"; import { useTheme, ThemeMode } from "../theme"; @@ -244,82 +251,70 @@ export default function SettingsScreen(): React.JSX.Element { Appearance - - - {(["dark", "light", "system"] as ThemeMode[]).map( - (mode: ThemeMode) => { - const isActive: boolean = themeMode === mode; - return ( - { - return [ - { - flex: 1, - flexDirection: "row" as const, - alignItems: "center" as const, - justifyContent: "center" as const, - paddingVertical: 10, - borderRadius: 10, - gap: 6, - overflow: "hidden" as const, - opacity: pressed ? 0.7 : 1, - }, - isActive - ? { - backgroundColor: theme.colors.actionPrimary, - shadowColor: theme.colors.actionPrimary, - shadowOpacity: 0.3, - shadowOffset: { width: 0, height: 2 }, - shadowRadius: 6, - elevation: 3, - } - : undefined, - ]; - }} - onPress={() => { - return handleThemeChange(mode); - }} - > - - - {mode.charAt(0).toUpperCase() + mode.slice(1)} - - - ); - }, - )} - - + {(["dark", "light", "system"] as ThemeMode[]).map( + (mode: ThemeMode, index: number) => { + const isActive: boolean = themeMode === mode; + return ( + { + return handleThemeChange(mode); + }} + style={{ + flex: 1, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + paddingVertical: 10, + borderRadius: 10, + marginLeft: index > 0 ? 4 : 0, + backgroundColor: isActive + ? theme.colors.actionPrimary + : "transparent", + }} + > + + + {mode.charAt(0).toUpperCase() + mode.slice(1)} + + + ); + }, + )} From 636a419cbd104d147fee7ee784c67b50c6150569 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:10:54 +0000 Subject: [PATCH 06/44] refactor: replace Pressable with TouchableOpacity for improved touch handling in SegmentedControl, AlertDetailScreen, and IncidentDetailScreen --- MobileApp/src/components/SegmentedControl.tsx | 50 +++-- MobileApp/src/screens/AlertDetailScreen.tsx | 172 ++++++++++-------- .../src/screens/IncidentDetailScreen.tsx | 172 ++++++++++-------- 3 files changed, 209 insertions(+), 185 deletions(-) diff --git a/MobileApp/src/components/SegmentedControl.tsx b/MobileApp/src/components/SegmentedControl.tsx index 996c4a0059..2e6d69f649 100644 --- a/MobileApp/src/components/SegmentedControl.tsx +++ b/MobileApp/src/components/SegmentedControl.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { View, Text, Pressable } from "react-native"; +import { View, Text, TouchableOpacity } from "react-native"; import { useTheme } from "../theme"; interface Segment { @@ -25,46 +25,42 @@ export default function SegmentedControl({ return ( - {segments.map((segment: Segment) => { + {segments.map((segment: Segment, index: number) => { const isActive: boolean = segment.key === selected; return ( - { - return [ - { - flex: 1, - alignItems: "center" as const, - paddingVertical: 10, - borderRadius: 12, - opacity: pressed ? 0.7 : 1, - }, - isActive - ? { - backgroundColor: theme.colors.actionPrimary, - shadowColor: theme.colors.actionPrimary, - shadowOpacity: theme.isDark ? 0.28 : 0.18, - shadowOffset: { width: 0, height: 5 }, - shadowRadius: 10, - elevation: 4, - } - : undefined, - ]; - }} + activeOpacity={0.7} onPress={() => { return onSelect(segment.key); }} + style={{ + flex: 1, + alignItems: "center", + paddingVertical: 10, + borderRadius: 12, + marginLeft: index > 0 ? 4 : 0, + backgroundColor: isActive + ? theme.colors.actionPrimary + : "transparent", + }} > ({ > {segment.label} - + ); })} diff --git a/MobileApp/src/screens/AlertDetailScreen.tsx b/MobileApp/src/screens/AlertDetailScreen.tsx index 00cfb3ac6a..97834f36b7 100644 --- a/MobileApp/src/screens/AlertDetailScreen.tsx +++ b/MobileApp/src/screens/AlertDetailScreen.tsx @@ -373,93 +373,107 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { borderColor: theme.colors.borderGlass, }} > - + {!isAcknowledged && !isResolved && acknowledgeState ? ( - { - return handleStateChange( - acknowledgeState._id, - acknowledgeState.name, - ); - }} - disabled={changingState} - accessibilityRole="button" - accessibilityLabel="Acknowledge alert" - > - {changingState ? ( - - ) : ( - <> - - - Acknowledge - - - )} - + + { + return handleStateChange( + acknowledgeState._id, + acknowledgeState.name, + ); + }} + disabled={changingState} + accessibilityRole="button" + accessibilityLabel="Acknowledge alert" + > + {changingState ? ( + + ) : ( + <> + + + Acknowledge + + + )} + + ) : null} {resolveState ? ( - { - return handleStateChange( - resolveState._id, - resolveState.name, - ); - }} - disabled={changingState} - accessibilityRole="button" - accessibilityLabel="Resolve alert" > - {changingState ? ( - - ) : ( - <> - - - Resolve - - - )} - + { + return handleStateChange( + resolveState._id, + resolveState.name, + ); + }} + disabled={changingState} + accessibilityRole="button" + accessibilityLabel="Resolve alert" + > + {changingState ? ( + + ) : ( + <> + + + Resolve + + + )} + + ) : null} diff --git a/MobileApp/src/screens/IncidentDetailScreen.tsx b/MobileApp/src/screens/IncidentDetailScreen.tsx index f41347f775..d68dd6798a 100644 --- a/MobileApp/src/screens/IncidentDetailScreen.tsx +++ b/MobileApp/src/screens/IncidentDetailScreen.tsx @@ -408,93 +408,107 @@ export default function IncidentDetailScreen({ borderColor: theme.colors.borderGlass, }} > - + {!isAcknowledged && !isResolved && acknowledgeState ? ( - { - return handleStateChange( - acknowledgeState._id, - acknowledgeState.name, - ); - }} - disabled={changingState} - accessibilityRole="button" - accessibilityLabel="Acknowledge incident" - > - {changingState ? ( - - ) : ( - <> - - - Acknowledge - - - )} - + + { + return handleStateChange( + acknowledgeState._id, + acknowledgeState.name, + ); + }} + disabled={changingState} + accessibilityRole="button" + accessibilityLabel="Acknowledge incident" + > + {changingState ? ( + + ) : ( + <> + + + Acknowledge + + + )} + + ) : null} {resolveState ? ( - { - return handleStateChange( - resolveState._id, - resolveState.name, - ); - }} - disabled={changingState} - accessibilityRole="button" - accessibilityLabel="Resolve incident" > - {changingState ? ( - - ) : ( - <> - - - Resolve - - - )} - + { + return handleStateChange( + resolveState._id, + resolveState.name, + ); + }} + disabled={changingState} + accessibilityRole="button" + accessibilityLabel="Resolve incident" + > + {changingState ? ( + + ) : ( + <> + + + Resolve + + + )} + + ) : null} From 32c4c1666d6a19f01a5e52d438780147828a3101 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:12:52 +0000 Subject: [PATCH 07/44] refactor: update button layout and styles in AlertEpisodeDetailScreen and IncidentEpisodeDetailScreen for consistency --- .../src/screens/AlertEpisodeDetailScreen.tsx | 164 ++++++++++-------- .../screens/IncidentEpisodeDetailScreen.tsx | 164 ++++++++++-------- 2 files changed, 178 insertions(+), 150 deletions(-) diff --git a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx index 3e7e596747..9874cfac18 100644 --- a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx +++ b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx @@ -381,88 +381,102 @@ export default function AlertEpisodeDetailScreen({ borderColor: theme.colors.borderGlass, }} > - + {!isAcknowledged && !isResolved && acknowledgeState ? ( - { - return handleStateChange( - acknowledgeState._id, - acknowledgeState.name, - ); - }} - disabled={changingState} - > - {changingState ? ( - - ) : ( - <> - - - Acknowledge - - - )} - + + { + return handleStateChange( + acknowledgeState._id, + acknowledgeState.name, + ); + }} + disabled={changingState} + > + {changingState ? ( + + ) : ( + <> + + + Acknowledge + + + )} + + ) : null} {resolveState ? ( - { - return handleStateChange( - resolveState._id, - resolveState.name, - ); - }} - disabled={changingState} > - {changingState ? ( - - ) : ( - <> - - - Resolve - - - )} - + { + return handleStateChange( + resolveState._id, + resolveState.name, + ); + }} + disabled={changingState} + > + {changingState ? ( + + ) : ( + <> + + + Resolve + + + )} + + ) : null} diff --git a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx index c62237bcd0..4bc63e5170 100644 --- a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx +++ b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx @@ -388,88 +388,102 @@ export default function IncidentEpisodeDetailScreen({ borderColor: theme.colors.borderGlass, }} > - + {!isAcknowledged && !isResolved && acknowledgeState ? ( - { - return handleStateChange( - acknowledgeState._id, - acknowledgeState.name, - ); - }} - disabled={changingState} - > - {changingState ? ( - - ) : ( - <> - - - Acknowledge - - - )} - + + { + return handleStateChange( + acknowledgeState._id, + acknowledgeState.name, + ); + }} + disabled={changingState} + > + {changingState ? ( + + ) : ( + <> + + + Acknowledge + + + )} + + ) : null} {resolveState ? ( - { - return handleStateChange( - resolveState._id, - resolveState.name, - ); - }} - disabled={changingState} > - {changingState ? ( - - ) : ( - <> - - - Resolve - - - )} - + { + return handleStateChange( + resolveState._id, + resolveState.name, + ); + }} + disabled={changingState} + > + {changingState ? ( + + ) : ( + <> + + + Resolve + + + )} + + ) : null} From c07c89e3dda72dc49415a1497bd19647fcd4e754 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:16:58 +0000 Subject: [PATCH 08/44] refactor: replace Pressable with TouchableOpacity in StatCard for improved touch handling and update layout for better spacing --- MobileApp/src/screens/HomeScreen.tsx | 112 ++++++++++++++------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/MobileApp/src/screens/HomeScreen.tsx b/MobileApp/src/screens/HomeScreen.tsx index 9cc09a033c..2dd62bea1b 100644 --- a/MobileApp/src/screens/HomeScreen.tsx +++ b/MobileApp/src/screens/HomeScreen.tsx @@ -6,6 +6,7 @@ import { RefreshControl, ActivityIndicator, Pressable, + TouchableOpacity, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import { LinearGradient } from "expo-linear-gradient"; @@ -48,18 +49,15 @@ function StatCard({ }; return ( - { - return { - flex: 1, - borderRadius: 24, - overflow: "hidden" as const, - opacity: pressed ? 0.7 : 1, - }; - }} + - + ); } @@ -340,7 +338,7 @@ export default function HomeScreen(): React.JSX.Element { - + Incidents - - { - return navigation.navigate("Incidents"); - }} - /> - { - return navigation.navigate("Incidents"); - }} - /> + + + { + return navigation.navigate("Incidents"); + }} + /> + + + { + return navigation.navigate("Incidents"); + }} + /> + @@ -490,27 +492,31 @@ export default function HomeScreen(): React.JSX.Element { > Alerts - - { - return navigation.navigate("Alerts"); - }} - /> - { - return navigation.navigate("Alerts"); - }} - /> + + + { + return navigation.navigate("Alerts"); + }} + /> + + + { + return navigation.navigate("Alerts"); + }} + /> + From 917f27fe115a8f1b8d506dcd2aa17c42e3d0bc66 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:25:03 +0000 Subject: [PATCH 09/44] Refactor styles in multiple screens to use inline styles instead of class names for consistency and improved readability. Updated layout properties and adjusted padding, margin, and font sizes across IncidentsScreen, MyOnCallPoliciesScreen, SettingsScreen, LoginScreen, and ServerUrlScreen for better UI alignment and responsiveness. --- MobileApp/src/components/AddNoteModal.tsx | 60 +++-- MobileApp/src/components/AlertCard.tsx | 141 +++++++++--- MobileApp/src/components/EmptyState.tsx | 39 +++- MobileApp/src/components/EpisodeCard.tsx | 130 ++++++++--- MobileApp/src/components/FeedTimeline.tsx | 28 ++- MobileApp/src/components/GlassCard.tsx | 21 +- MobileApp/src/components/IncidentCard.tsx | 141 +++++++++--- MobileApp/src/components/NotesSection.tsx | 72 ++++-- MobileApp/src/components/OfflineBanner.tsx | 26 ++- MobileApp/src/components/ProjectBadge.tsx | 18 +- MobileApp/src/components/RootCauseCard.tsx | 12 +- MobileApp/src/components/SectionHeader.tsx | 19 +- MobileApp/src/components/SeverityBadge.tsx | 17 +- MobileApp/src/components/SkeletonCard.tsx | 180 +++++++++++---- MobileApp/src/components/StateBadge.tsx | 29 ++- MobileApp/src/components/SwipeableCard.tsx | 56 ++++- MobileApp/src/navigation/MainTabNavigator.tsx | 7 +- MobileApp/src/screens/AlertDetailScreen.tsx | 141 ++++++++---- .../src/screens/AlertEpisodeDetailScreen.tsx | 152 +++++++++---- MobileApp/src/screens/AlertsScreen.tsx | 26 ++- MobileApp/src/screens/BiometricLockScreen.tsx | 19 +- MobileApp/src/screens/HomeScreen.tsx | 123 +++++++---- .../src/screens/IncidentDetailScreen.tsx | 157 +++++++++---- .../screens/IncidentEpisodeDetailScreen.tsx | 161 +++++++++----- MobileApp/src/screens/IncidentsScreen.tsx | 26 ++- .../src/screens/MyOnCallPoliciesScreen.tsx | 123 +++++++---- MobileApp/src/screens/SettingsScreen.tsx | 207 ++++++++++++------ MobileApp/src/screens/auth/LoginScreen.tsx | 61 +++--- .../src/screens/auth/ServerUrlScreen.tsx | 42 ++-- 29 files changed, 1600 insertions(+), 634 deletions(-) diff --git a/MobileApp/src/components/AddNoteModal.tsx b/MobileApp/src/components/AddNoteModal.tsx index b126b42ef5..b7065f746d 100644 --- a/MobileApp/src/components/AddNoteModal.tsx +++ b/MobileApp/src/components/AddNoteModal.tsx @@ -49,13 +49,19 @@ export default function AddNoteModal({ onRequestClose={handleClose} > - + - + @@ -85,8 +108,9 @@ export default function AddNoteModal({ /> - + { return { @@ -131,14 +160,17 @@ export default function AddNoteModal({ disabled={isSubmitting} > Cancel - + - - - + + + {projectName ? : null} - + {timeString} - + {alert.title} @@ -139,21 +174,40 @@ export default function AlertCard({ /> - + {alert.currentAlertState ? ( {alert.currentAlertState.name} @@ -162,12 +216,21 @@ export default function AlertCard({ {alert.alertSeverity ? ( {alert.alertSeverity.name} @@ -177,15 +240,25 @@ export default function AlertCard({ {alert.monitor ? ( - + {alert.monitor.name} Linked monitor diff --git a/MobileApp/src/components/EmptyState.tsx b/MobileApp/src/components/EmptyState.tsx index f68ea683d3..54fbef3116 100644 --- a/MobileApp/src/components/EmptyState.tsx +++ b/MobileApp/src/components/EmptyState.tsx @@ -32,10 +32,22 @@ export default function EmptyState({ const { theme } = useTheme(); return ( - + @@ -47,20 +59,35 @@ export default function EmptyState({ {title} {subtitle ? ( - + {subtitle} ) : null} {actionLabel && onAction ? ( - + ) : null} diff --git a/MobileApp/src/components/EpisodeCard.tsx b/MobileApp/src/components/EpisodeCard.tsx index ed73345afa..907eb0db58 100644 --- a/MobileApp/src/components/EpisodeCard.tsx +++ b/MobileApp/src/components/EpisodeCard.tsx @@ -73,8 +73,9 @@ export default function EpisodeCard( accessibilityLabel={`${type === "incident" ? "Incident" : "Alert"} episode ${episode.episodeNumberWithPrefix || episode.episodeNumber}, ${episode.title}. State: ${state?.name ?? "unknown"}. Severity: ${severity?.name ?? "unknown"}.`} > - - - + + + {projectName ? : null} - + {timeString} - + {episode.title} @@ -172,21 +207,40 @@ export default function EpisodeCard( /> - + {state ? ( {state.name} @@ -195,12 +249,21 @@ export default function EpisodeCard( {severity ? ( {severity.name} @@ -209,12 +272,21 @@ export default function EpisodeCard( {childCount > 0 ? ( {childCount} {type === "incident" ? "incident" : "alert"} {childCount !== 1 ? "s" : ""} diff --git a/MobileApp/src/components/FeedTimeline.tsx b/MobileApp/src/components/FeedTimeline.tsx index 438efc3038..11b97d2225 100644 --- a/MobileApp/src/components/FeedTimeline.tsx +++ b/MobileApp/src/components/FeedTimeline.tsx @@ -28,24 +28,35 @@ export default function FeedTimeline({ const moreText: string | undefined = entry.moreInformationInMarkdown; return ( - - + + {!isLast ? ( ) : null} {moreText ? ( - + ) : null} {timeString} diff --git a/MobileApp/src/components/GlassCard.tsx b/MobileApp/src/components/GlassCard.tsx index 614d460a11..50e7ce9d54 100644 --- a/MobileApp/src/components/GlassCard.tsx +++ b/MobileApp/src/components/GlassCard.tsx @@ -17,17 +17,16 @@ export default function GlassCard({ return ( {children} diff --git a/MobileApp/src/components/IncidentCard.tsx b/MobileApp/src/components/IncidentCard.tsx index 3e49ef68fb..cfb34df7f2 100644 --- a/MobileApp/src/components/IncidentCard.tsx +++ b/MobileApp/src/components/IncidentCard.tsx @@ -53,8 +53,9 @@ export default function IncidentCard({ accessibilityLabel={`Incident ${incident.incidentNumberWithPrefix || incident.incidentNumber}, ${incident.title}. State: ${incident.currentIncidentState?.name ?? "unknown"}. Severity: ${incident.incidentSeverity?.name ?? "unknown"}.`} > - - - + + + {projectName ? : null} - + {timeString} - + {incident.title} @@ -148,21 +183,40 @@ export default function IncidentCard({ /> - + {incident.currentIncidentState ? ( {incident.currentIncidentState.name} @@ -171,12 +225,21 @@ export default function IncidentCard({ {incident.incidentSeverity ? ( {incident.incidentSeverity.name} @@ -186,15 +249,25 @@ export default function IncidentCard({ {monitorCount > 0 ? ( - + {monitorNames} {monitorCount} monitor{monitorCount !== 1 ? "s" : ""} diff --git a/MobileApp/src/components/NotesSection.tsx b/MobileApp/src/components/NotesSection.tsx index 338f069b5d..c995b693ef 100644 --- a/MobileApp/src/components/NotesSection.tsx +++ b/MobileApp/src/components/NotesSection.tsx @@ -21,9 +21,16 @@ export default function NotesSection({ : "#FFFFFF"; return ( - - - + + + Internal Notes @@ -60,8 +72,11 @@ export default function NotesSection({ style={{ marginRight: 4 }} /> Add Note @@ -76,8 +91,10 @@ export default function NotesSection({ return ( - + {noteText} - + {note.createdByUser ? ( {authorName} ) : null} {formatDateTime(note.createdAt)} @@ -121,16 +151,20 @@ export default function NotesSection({ {notes && notes.length === 0 ? ( No notes yet. diff --git a/MobileApp/src/components/OfflineBanner.tsx b/MobileApp/src/components/OfflineBanner.tsx index 7ecc9e35dc..3ef000169a 100644 --- a/MobileApp/src/components/OfflineBanner.tsx +++ b/MobileApp/src/components/OfflineBanner.tsx @@ -26,8 +26,15 @@ export default function OfflineBanner(): React.JSX.Element | null { return ( - + - + No internet connection diff --git a/MobileApp/src/components/ProjectBadge.tsx b/MobileApp/src/components/ProjectBadge.tsx index 4140e88822..bbbc044bf7 100644 --- a/MobileApp/src/components/ProjectBadge.tsx +++ b/MobileApp/src/components/ProjectBadge.tsx @@ -13,14 +13,22 @@ export default function ProjectBadge({ }: ProjectBadgeProps): React.JSX.Element { const { theme } = useTheme(); return ( - + {name} diff --git a/MobileApp/src/components/RootCauseCard.tsx b/MobileApp/src/components/RootCauseCard.tsx index 6db238ca45..126d4d7351 100644 --- a/MobileApp/src/components/RootCauseCard.tsx +++ b/MobileApp/src/components/RootCauseCard.tsx @@ -14,20 +14,24 @@ export default function RootCauseCard({ return ( - + {rootCauseText ? ( ) : ( No root cause documented yet. diff --git a/MobileApp/src/components/SectionHeader.tsx b/MobileApp/src/components/SectionHeader.tsx index 4e1b19783b..9d4ce31ceb 100644 --- a/MobileApp/src/components/SectionHeader.tsx +++ b/MobileApp/src/components/SectionHeader.tsx @@ -14,10 +14,21 @@ export default function SectionHeader({ }: SectionHeaderProps): React.JSX.Element { const { theme } = useTheme(); return ( - + {displayLabel.toUpperCase()} diff --git a/MobileApp/src/components/SkeletonCard.tsx b/MobileApp/src/components/SkeletonCard.tsx index b649729dfd..deab0c9818 100644 --- a/MobileApp/src/components/SkeletonCard.tsx +++ b/MobileApp/src/components/SkeletonCard.tsx @@ -59,8 +59,10 @@ export default function SkeletonCard({ if (variant === "compact") { return ( - - + + @@ -98,14 +120,15 @@ export default function SkeletonCard({ if (variant === "detail") { return ( - + - + - + {Array.from({ length: 3 }).map((_: unknown, index: number) => { return ( - + ); @@ -171,8 +225,10 @@ export default function SkeletonCard({ return ( - - + + - + {Array.from({ length: Math.max(lines - 1, 1) }).map( @@ -217,8 +307,10 @@ export default function SkeletonCard({ return ( + - + {displayLabel.charAt(0).toUpperCase() + displayLabel.slice(1)} diff --git a/MobileApp/src/components/SwipeableCard.tsx b/MobileApp/src/components/SwipeableCard.tsx index 520d55a793..b379c096fb 100644 --- a/MobileApp/src/components/SwipeableCard.tsx +++ b/MobileApp/src/components/SwipeableCard.tsx @@ -94,25 +94,63 @@ export default function SwipeableCard({ ).current; return ( - + {/* Background actions */} - + {leftAction ? ( - + {leftAction.label} ) : null} {rightAction ? ( - + {rightAction.label} @@ -121,8 +159,8 @@ export default function SwipeableCard({ {/* Foreground content */} + {focused ? ( diff --git a/MobileApp/src/screens/AlertDetailScreen.tsx b/MobileApp/src/screens/AlertDetailScreen.tsx index 97834f36b7..07240be6f0 100644 --- a/MobileApp/src/screens/AlertDetailScreen.tsx +++ b/MobileApp/src/screens/AlertDetailScreen.tsx @@ -140,8 +140,7 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { if (isLoading) { return ( @@ -151,12 +150,15 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { if (!alert) { return ( Alert not found. @@ -203,8 +205,10 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { > {/* Header card */} - + {alert.alertNumberWithPrefix || `#${alert.alertNumber}`} - + {alert.currentAlertState ? ( {alert.currentAlertState.name} @@ -272,12 +302,21 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { {alert.alertSeverity ? ( {alert.alertSeverity.name} @@ -289,19 +328,23 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { {/* Description */} {descriptionText ? ( - + {descriptionText} @@ -309,49 +352,60 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { ) : null} - + {/* Details */} - + - - + + Created {formatDateTime(alert.createdAt)} {alert.monitor ? ( - + Monitor {alert.monitor.name} @@ -363,11 +417,12 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { {/* State Change Actions */} {!isResolved ? ( - + 0 ? ( - + diff --git a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx index 9874cfac18..9eb6aa2044 100644 --- a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx +++ b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx @@ -148,8 +148,7 @@ export default function AlertEpisodeDetailScreen({ if (isLoading) { return ( @@ -159,12 +158,15 @@ export default function AlertEpisodeDetailScreen({ if (!episode) { return ( Episode not found. @@ -210,8 +212,10 @@ export default function AlertEpisodeDetailScreen({ } > - + {episode.episodeNumberWithPrefix || `#${episode.episodeNumber}`} {episode.title} - + {episode.currentAlertState ? ( {episode.currentAlertState.name} @@ -268,12 +301,21 @@ export default function AlertEpisodeDetailScreen({ ) : null} {episode.alertSeverity ? ( {episode.alertSeverity.name} @@ -284,19 +326,23 @@ export default function AlertEpisodeDetailScreen({ {descriptionText ? ( - + {descriptionText} @@ -304,19 +350,21 @@ export default function AlertEpisodeDetailScreen({ ) : null} - + - + - - + + Created {formatDateTime(episode.createdAt)} - + Alerts {episode.alertCount ?? 0} @@ -371,11 +430,12 @@ export default function AlertEpisodeDetailScreen({ {!isResolved ? ( - + 0 ? ( - + diff --git a/MobileApp/src/screens/AlertsScreen.tsx b/MobileApp/src/screens/AlertsScreen.tsx index a77e140506..70c938bd5c 100644 --- a/MobileApp/src/screens/AlertsScreen.tsx +++ b/MobileApp/src/screens/AlertsScreen.tsx @@ -60,8 +60,7 @@ function SectionHeader({ const { theme } = useTheme(); return ( - + @@ -325,8 +329,7 @@ export default function AlertsScreen(): React.JSX.Element { }; return ( @@ -47,8 +51,10 @@ export default function BiometricLockScreen({ Use {biometricType.toLowerCase()} to unlock - + - + @@ -103,8 +109,9 @@ function StatCard({ /> } > - + You don't have access to any projects. Contact your administrator or pull to refresh. - + @@ -248,10 +269,12 @@ export default function HomeScreen(): React.JSX.Element { /> } > - + - + - + {getGreeting()} - + Total active items - - + + - + My On-Call Policies {onCallLoading ? "Loading assignments..." @@ -423,10 +461,11 @@ export default function HomeScreen(): React.JSX.Element { - + @@ -163,12 +162,15 @@ export default function IncidentDetailScreen({ if (!incident) { return ( Incident not found. @@ -217,8 +219,10 @@ export default function IncidentDetailScreen({ > {/* Header card */} - + {incident.incidentNumberWithPrefix || `#${incident.incidentNumber}`} - + {incident.currentIncidentState ? ( {incident.currentIncidentState.name} @@ -286,12 +316,21 @@ export default function IncidentDetailScreen({ {incident.incidentSeverity ? ( {incident.incidentSeverity.name} @@ -303,19 +342,23 @@ export default function IncidentDetailScreen({ {/* Description */} {descriptionText ? ( - + {descriptionText} @@ -323,66 +366,83 @@ export default function IncidentDetailScreen({ ) : null} - + {/* Details */} - + - + {incident.declaredAt ? ( - + Declared {formatDateTime(incident.declaredAt)} ) : null} - + Created {formatDateTime(incident.createdAt)} {incident.monitors?.length > 0 ? ( - + Monitors {incident.monitors .map((m: NamedEntity) => { @@ -398,11 +458,12 @@ export default function IncidentDetailScreen({ {/* State Change Actions */} {!isResolved ? ( - + 0 ? ( - + diff --git a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx index 4bc63e5170..0fbae8d473 100644 --- a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx +++ b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx @@ -155,8 +155,7 @@ export default function IncidentEpisodeDetailScreen({ if (isLoading) { return ( @@ -166,12 +165,15 @@ export default function IncidentEpisodeDetailScreen({ if (!episode) { return ( Episode not found. @@ -219,8 +221,10 @@ export default function IncidentEpisodeDetailScreen({ } > - + {episode.episodeNumberWithPrefix || `#${episode.episodeNumber}`} {episode.title} - + {episode.currentIncidentState ? ( {episode.currentIncidentState.name} @@ -277,12 +310,21 @@ export default function IncidentEpisodeDetailScreen({ ) : null} {episode.incidentSeverity ? ( {episode.incidentSeverity.name} @@ -293,19 +335,23 @@ export default function IncidentEpisodeDetailScreen({ {descriptionText ? ( - + {descriptionText} @@ -313,62 +359,78 @@ export default function IncidentEpisodeDetailScreen({ ) : null} - + - + - + {episode.declaredAt ? ( - + Declared {formatDateTime(episode.declaredAt)} ) : null} - + Created {formatDateTime(episode.createdAt)} - + Incidents {episode.incidentCount ?? 0} @@ -378,11 +440,12 @@ export default function IncidentEpisodeDetailScreen({ {!isResolved ? ( - + 0 ? ( - + diff --git a/MobileApp/src/screens/IncidentsScreen.tsx b/MobileApp/src/screens/IncidentsScreen.tsx index a0ff398fae..bca775a768 100644 --- a/MobileApp/src/screens/IncidentsScreen.tsx +++ b/MobileApp/src/screens/IncidentsScreen.tsx @@ -63,8 +63,7 @@ function SectionHeader({ const { theme } = useTheme(); return ( - + @@ -330,8 +334,7 @@ export default function IncidentsScreen(): React.JSX.Element { }; return ( - - + + - + Live duty assignments @@ -204,16 +213,19 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { {summaryText} @@ -233,8 +244,9 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { {projects.length === 0 ? ( ) : ( - + {projects.map((projectData: ProjectOnCallAssignments) => { return ( - + {projectData.projectName} @@ -290,16 +313,17 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { {projectData.assignments.length} active @@ -345,18 +369,27 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { ]; }} > - + {assignment.policyName} @@ -366,38 +399,48 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { color={badge.color} /> {badge.label} - - + + Rule: {assignment.escalationRuleName} - + {assignment.assignmentDetail} diff --git a/MobileApp/src/screens/SettingsScreen.tsx b/MobileApp/src/screens/SettingsScreen.tsx index e4b7e8d5bd..4ad92a8c52 100644 --- a/MobileApp/src/screens/SettingsScreen.tsx +++ b/MobileApp/src/screens/SettingsScreen.tsx @@ -41,21 +41,30 @@ function SettingsRow({ const content: React.JSX.Element = ( - + {iconName ? ( ) : null} {value} @@ -154,8 +164,11 @@ export default function SettingsScreen(): React.JSX.Element { > {/* Header */} - + - + Preferences - + {serverUrl || "oneuptime.com"} @@ -243,10 +268,17 @@ export default function SettingsScreen(): React.JSX.Element { {/* Appearance */} - + Appearance @@ -320,16 +352,24 @@ export default function SettingsScreen(): React.JSX.Element { {/* Security */} {biometric.isAvailable ? ( - + Security Require biometrics to unlock the app @@ -362,16 +407,24 @@ export default function SettingsScreen(): React.JSX.Element { ) : null} {/* Server */} - + Server {/* Account */} - + Account {/* About */} - + About {/* Footer */} - + - - + + Thank you for supporting open source software. Built and maintained by contributors around the world. - + Licensed under Apache 2.0 diff --git a/MobileApp/src/screens/auth/LoginScreen.tsx b/MobileApp/src/screens/auth/LoginScreen.tsx index f64366552b..e72ad61a50 100644 --- a/MobileApp/src/screens/auth/LoginScreen.tsx +++ b/MobileApp/src/screens/auth/LoginScreen.tsx @@ -74,19 +74,23 @@ export default function LoginScreen(): React.JSX.Element { return ( - - + + @@ -94,8 +98,9 @@ export default function LoginScreen(): React.JSX.Element { Sign in to continue {serverUrl ? ( {serverUrl} @@ -128,14 +134,17 @@ export default function LoginScreen(): React.JSX.Element { Email { setEmail(text); @@ -178,14 +186,17 @@ export default function LoginScreen(): React.JSX.Element { Password { setPassword(text); @@ -227,7 +237,7 @@ export default function LoginScreen(): React.JSX.Element { {error ? ( - + {error} ) : null} - + - + - - + + @@ -85,8 +89,9 @@ export default function ServerUrlScreen(): React.JSX.Element { Connect to your OneUptime instance @@ -103,14 +107,17 @@ export default function ServerUrlScreen(): React.JSX.Element { Server URL { setUrl(text); @@ -155,7 +161,7 @@ export default function ServerUrlScreen(): React.JSX.Element { {error ? ( - + {error} ) : null} - + Self-hosting? Enter your OneUptime server URL above. From 1d0016412e5bbbd6156da3e6387f259253cbf6d9 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:27:38 +0000 Subject: [PATCH 10/44] refactor: add marginBottom to SwipeableCard for improved layout spacing --- MobileApp/src/components/SwipeableCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileApp/src/components/SwipeableCard.tsx b/MobileApp/src/components/SwipeableCard.tsx index b379c096fb..2e779546db 100644 --- a/MobileApp/src/components/SwipeableCard.tsx +++ b/MobileApp/src/components/SwipeableCard.tsx @@ -94,7 +94,7 @@ export default function SwipeableCard({ ).current; return ( - + {/* Background actions */} Date: Tue, 17 Feb 2026 21:28:48 +0000 Subject: [PATCH 11/44] refactor: move marginBottom to parent View in EpisodeCard for improved layout consistency --- MobileApp/src/components/EpisodeCard.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MobileApp/src/components/EpisodeCard.tsx b/MobileApp/src/components/EpisodeCard.tsx index 907eb0db58..6177dfa7f8 100644 --- a/MobileApp/src/components/EpisodeCard.tsx +++ b/MobileApp/src/components/EpisodeCard.tsx @@ -61,10 +61,10 @@ export default function EpisodeCard( ); return ( + { return { - marginBottom: 12, opacity: pressed ? 0.7 : muted ? 0.5 : 1, }; }} @@ -297,5 +297,6 @@ export default function EpisodeCard( + ); } From 991dc1c8426901f4e21a3962ebf540075f3a5963 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:29:59 +0000 Subject: [PATCH 12/44] refactor: update icon in AlertCard and IncidentCard from desktop-outline to pulse-outline for improved visual representation --- MobileApp/src/components/AlertCard.tsx | 2 +- MobileApp/src/components/IncidentCard.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MobileApp/src/components/AlertCard.tsx b/MobileApp/src/components/AlertCard.tsx index 4d3e084fe5..c69b40533f 100644 --- a/MobileApp/src/components/AlertCard.tsx +++ b/MobileApp/src/components/AlertCard.tsx @@ -261,7 +261,7 @@ export default function AlertCard({ }} > diff --git a/MobileApp/src/components/IncidentCard.tsx b/MobileApp/src/components/IncidentCard.tsx index cfb34df7f2..87d02bc0ef 100644 --- a/MobileApp/src/components/IncidentCard.tsx +++ b/MobileApp/src/components/IncidentCard.tsx @@ -270,7 +270,7 @@ export default function IncidentCard({ }} > From 2a20807126ba3026a9139098fdde371b347d19d9 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:31:10 +0000 Subject: [PATCH 13/44] refactor: update padding values in MyOnCallPoliciesScreen for improved layout consistency --- MobileApp/src/screens/MyOnCallPoliciesScreen.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx index 6d4bed479d..fc9938be26 100644 --- a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx +++ b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx @@ -131,7 +131,7 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { return ( { return [ { - paddingHorizontal: 16, - paddingVertical: 14, + paddingHorizontal: 20, + paddingVertical: 16, opacity: pressed ? 0.82 : 1, }, assignmentIndex !== From 3433a815f36e0ca5a6ec862f2c4f7a02ee0d11c8 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:32:54 +0000 Subject: [PATCH 14/44] refactor: simplify Pressable style and adjust layout properties in HomeScreen for improved accessibility and visual consistency --- MobileApp/src/screens/HomeScreen.tsx | 36 +++++++++++++--------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/MobileApp/src/screens/HomeScreen.tsx b/MobileApp/src/screens/HomeScreen.tsx index dafa1e5fee..9cdfd29cb6 100644 --- a/MobileApp/src/screens/HomeScreen.tsx +++ b/MobileApp/src/screens/HomeScreen.tsx @@ -387,27 +387,22 @@ export default function HomeScreen(): React.JSX.Element { lightImpact(); navigation.navigate("OnCall"); }} - style={({ pressed }: { pressed: boolean }) => { - return { + style={({ pressed }: { pressed: boolean }) => ({ + opacity: pressed ? 0.8 : 1, + })} + accessibilityRole="button" + accessibilityLabel="View my on-call assignments" + > + + }} + > @@ -480,6 +475,7 @@ export default function HomeScreen(): React.JSX.Element { /> + From a4ff718d61e6232395533f5036b9d3d03effd0e4 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:40:03 +0000 Subject: [PATCH 15/44] refactor: remove unnecessary shadow properties and simplify layout in MyOnCallPoliciesScreen for improved performance and consistency --- .../src/screens/MyOnCallPoliciesScreen.tsx | 59 ++++--------------- 1 file changed, 13 insertions(+), 46 deletions(-) diff --git a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx index fc9938be26..ca543519e8 100644 --- a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx +++ b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx @@ -143,34 +143,13 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { - @@ -270,13 +249,6 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { backgroundColor: theme.colors.backgroundElevated, borderWidth: 1, borderColor: theme.colors.borderGlass, - shadowColor: theme.isDark - ? "#000" - : theme.colors.accentGradientMid, - shadowOpacity: theme.isDark ? 0.2 : 0.08, - shadowOffset: { width: 0, height: 6 }, - shadowRadius: 14, - elevation: 4, }} > { - return [ - { - paddingHorizontal: 20, - paddingVertical: 16, - opacity: pressed ? 0.82 : 1, - }, - assignmentIndex !== - projectData.assignments.length - 1 - ? { - borderBottomWidth: 1, - borderBottomColor: - theme.colors.borderSubtle, - } - : undefined, - ]; + style={{ + paddingHorizontal: 20, + paddingVertical: 16, + ...(assignmentIndex !== + projectData.assignments.length - 1 + ? { + borderBottomWidth: 1, + borderBottomColor: + theme.colors.borderSubtle, + } + : {}), }} > @@ -447,7 +414,7 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { - + ); }, )} From 4c3b4d23ffc099f32fcd99e9630235c8f3a9f5cb Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:42:26 +0000 Subject: [PATCH 16/44] refactor: update assignment badge colors and icon in MyOnCallPoliciesScreen for improved visual consistency --- MobileApp/src/screens/MyOnCallPoliciesScreen.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx index ca543519e8..e3c7a9132b 100644 --- a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx +++ b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx @@ -4,10 +4,8 @@ import { Text, ScrollView, RefreshControl, - Pressable, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; -import { LinearGradient } from "expo-linear-gradient"; import { useTheme } from "../theme"; import { useHaptics } from "../hooks/useHaptics"; import { useAllProjectOnCallPolicies } from "../hooks/useAllProjectOnCallPolicies"; @@ -33,8 +31,8 @@ function getAssignmentBadge( successBg: string; info: string; infoBg: string; - warning: string; - warningBg: string; + purple: string; + purpleBg: string; }, ): AssignmentBadgeConfig { switch (type) { @@ -54,10 +52,10 @@ function getAssignmentBadge( }; case "schedule": return { - icon: "time-outline", + icon: "calendar-outline", label: "Schedule", - color: colors.warning, - background: colors.warningBg, + color: colors.purple, + background: colors.purpleBg, }; } } @@ -315,8 +313,8 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { successBg: theme.colors.oncallActiveBg, info: theme.colors.severityInfo, infoBg: theme.colors.severityInfoBg, - warning: theme.colors.severityWarning, - warningBg: theme.colors.severityWarningBg, + purple: "#A855F7", + purpleBg: "rgba(168, 85, 247, 0.12)", }, ); From b8e44a1bcf5ae00bb7deb2a26890bd8eb048c6ca Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 21:55:20 +0000 Subject: [PATCH 17/44] refactor: update layout and styling in AddNoteModal and NotesSection for improved consistency and visual clarity --- MobileApp/src/components/AddNoteModal.tsx | 25 +++++++++++-------- MobileApp/src/components/NotesSection.tsx | 8 +++--- .../src/screens/MyOnCallPoliciesScreen.tsx | 3 ++- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/MobileApp/src/components/AddNoteModal.tsx b/MobileApp/src/components/AddNoteModal.tsx index b7065f746d..49ac55a4aa 100644 --- a/MobileApp/src/components/AddNoteModal.tsx +++ b/MobileApp/src/components/AddNoteModal.tsx @@ -140,17 +140,21 @@ export default function AddNoteModal({ /> { return { flex: 1, - paddingVertical: 12, + height: 50, borderRadius: 12, alignItems: "center" as const, justifyContent: "center" as const, - minHeight: 48, borderWidth: 1, borderColor: theme.colors.borderDefault, opacity: pressed ? 0.7 : 1, @@ -170,14 +174,13 @@ export default function AddNoteModal({ - - - + diff --git a/MobileApp/src/components/NotesSection.tsx b/MobileApp/src/components/NotesSection.tsx index c995b693ef..4f31348554 100644 --- a/MobileApp/src/components/NotesSection.tsx +++ b/MobileApp/src/components/NotesSection.tsx @@ -16,9 +16,7 @@ export default function NotesSection({ setNoteModalVisible, }: NotesSectionProps): React.JSX.Element { const { theme } = useTheme(); - const addNoteContentColor: string = theme.isDark - ? theme.colors.backgroundPrimary - : "#FFFFFF"; + const addNoteContentColor: string = "#FFFFFF"; return ( @@ -57,7 +55,9 @@ export default function NotesSection({ borderRadius: 8, paddingHorizontal: 12, paddingVertical: 6, - backgroundColor: theme.colors.actionPrimary, + backgroundColor: theme.isDark + ? theme.colors.accentGradientStart + : theme.colors.actionPrimary, opacity: pressed ? 0.85 : 1, }; }} diff --git a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx index e3c7a9132b..19a56a4380 100644 --- a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx +++ b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx @@ -243,7 +243,6 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { key={projectData.projectId} style={{ borderRadius: 24, - overflow: "hidden", backgroundColor: theme.colors.backgroundElevated, borderWidth: 1, borderColor: theme.colors.borderGlass, @@ -259,6 +258,8 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { borderBottomWidth: 1, borderBottomColor: theme.colors.borderSubtle, backgroundColor: theme.colors.backgroundSecondary, + borderTopLeftRadius: 23, + borderTopRightRadius: 23, }} > From 3aab280dcdff3c0d4c40a763f9fbd1e6a6d398e5 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 22:04:56 +0000 Subject: [PATCH 18/44] refactor: standardize shadow properties and background colors across components for improved consistency --- MobileApp/src/App.tsx | 2 +- MobileApp/src/components/AddNoteModal.tsx | 4 +- MobileApp/src/components/AlertCard.tsx | 4 +- MobileApp/src/components/EpisodeCard.tsx | 4 +- MobileApp/src/components/GradientButton.tsx | 4 +- MobileApp/src/components/IncidentCard.tsx | 4 +- MobileApp/src/components/NotesSection.tsx | 10 +- MobileApp/src/components/SegmentedControl.tsx | 4 +- MobileApp/src/navigation/MainTabNavigator.tsx | 6 +- MobileApp/src/navigation/RootNavigator.tsx | 2 +- MobileApp/src/screens/AlertDetailScreen.tsx | 4 +- .../src/screens/AlertEpisodeDetailScreen.tsx | 4 +- MobileApp/src/screens/HomeScreen.tsx | 8 +- .../src/screens/IncidentDetailScreen.tsx | 4 +- .../screens/IncidentEpisodeDetailScreen.tsx | 4 +- MobileApp/src/screens/SettingsScreen.tsx | 104 +----------------- MobileApp/src/storage/preferences.ts | 15 --- MobileApp/src/theme/ThemeContext.tsx | 75 ++----------- MobileApp/src/theme/colors.ts | 72 ------------ MobileApp/src/theme/index.ts | 4 +- 20 files changed, 44 insertions(+), 294 deletions(-) diff --git a/MobileApp/src/App.tsx b/MobileApp/src/App.tsx index ee7d2fc19e..1189ec2732 100644 --- a/MobileApp/src/App.tsx +++ b/MobileApp/src/App.tsx @@ -51,7 +51,7 @@ function AppContent(): React.JSX.Element { borderRadius: 999, }} /> - + diff --git a/MobileApp/src/components/AddNoteModal.tsx b/MobileApp/src/components/AddNoteModal.tsx index 49ac55a4aa..28e26e04ee 100644 --- a/MobileApp/src/components/AddNoteModal.tsx +++ b/MobileApp/src/components/AddNoteModal.tsx @@ -62,9 +62,7 @@ export default function AddNoteModal({ borderTopRightRadius: 24, padding: 20, paddingBottom: 36, - backgroundColor: theme.isDark - ? theme.colors.backgroundElevated - : theme.colors.backgroundPrimary, + backgroundColor: theme.colors.backgroundElevated, borderWidth: 1, borderBottomWidth: 0, borderColor: theme.colors.borderGlass, diff --git a/MobileApp/src/components/AlertCard.tsx b/MobileApp/src/components/AlertCard.tsx index c69b40533f..627d7bc077 100644 --- a/MobileApp/src/components/AlertCard.tsx +++ b/MobileApp/src/components/AlertCard.tsx @@ -51,8 +51,8 @@ export default function AlertCard({ backgroundColor: theme.colors.backgroundElevated, borderWidth: 1, borderColor: theme.colors.borderGlass, - shadowColor: theme.isDark ? "#000" : "#111827", - shadowOpacity: theme.isDark ? 0.22 : 0.08, + shadowColor: "#000", + shadowOpacity: 0.22, shadowOffset: { width: 0, height: 8 }, shadowRadius: 14, elevation: 5, diff --git a/MobileApp/src/components/EpisodeCard.tsx b/MobileApp/src/components/EpisodeCard.tsx index 6177dfa7f8..ae9a75690e 100644 --- a/MobileApp/src/components/EpisodeCard.tsx +++ b/MobileApp/src/components/EpisodeCard.tsx @@ -79,8 +79,8 @@ export default function EpisodeCard( backgroundColor: theme.colors.backgroundElevated, borderWidth: 1, borderColor: theme.colors.borderGlass, - shadowColor: theme.isDark ? "#000" : "#111827", - shadowOpacity: theme.isDark ? 0.22 : 0.08, + shadowColor: "#000", + shadowOpacity: 0.22, shadowOffset: { width: 0, height: 8 }, shadowRadius: 14, elevation: 5, diff --git a/MobileApp/src/components/GradientButton.tsx b/MobileApp/src/components/GradientButton.tsx index 3eeed771f1..1cd65c72bf 100644 --- a/MobileApp/src/components/GradientButton.tsx +++ b/MobileApp/src/components/GradientButton.tsx @@ -29,9 +29,7 @@ export default function GradientButton({ style, }: GradientButtonProps): React.JSX.Element { const { theme } = useTheme(); - const primaryContentColor: string = theme.isDark - ? theme.colors.backgroundPrimary - : "#FFFFFF"; + const primaryContentColor: string = theme.colors.backgroundPrimary; const isDisabled: boolean = disabled || loading; diff --git a/MobileApp/src/components/IncidentCard.tsx b/MobileApp/src/components/IncidentCard.tsx index 87d02bc0ef..a83bee1135 100644 --- a/MobileApp/src/components/IncidentCard.tsx +++ b/MobileApp/src/components/IncidentCard.tsx @@ -59,8 +59,8 @@ export default function IncidentCard({ backgroundColor: theme.colors.backgroundElevated, borderWidth: 1, borderColor: theme.colors.borderGlass, - shadowColor: theme.isDark ? "#000" : "#111827", - shadowOpacity: theme.isDark ? 0.22 : 0.08, + shadowColor: "#000", + shadowOpacity: 0.22, shadowOffset: { width: 0, height: 8 }, shadowRadius: 14, elevation: 5, diff --git a/MobileApp/src/components/NotesSection.tsx b/MobileApp/src/components/NotesSection.tsx index 4f31348554..998781dd52 100644 --- a/MobileApp/src/components/NotesSection.tsx +++ b/MobileApp/src/components/NotesSection.tsx @@ -55,9 +55,7 @@ export default function NotesSection({ borderRadius: 8, paddingHorizontal: 12, paddingVertical: 6, - backgroundColor: theme.isDark - ? theme.colors.accentGradientStart - : theme.colors.actionPrimary, + backgroundColor: theme.colors.accentGradientStart, opacity: pressed ? 0.85 : 1, }; }} @@ -98,10 +96,8 @@ export default function NotesSection({ backgroundColor: theme.colors.backgroundElevated, borderWidth: 1, borderColor: theme.colors.borderGlass, - shadowColor: theme.isDark - ? "#000" - : theme.colors.accentGradientMid, - shadowOpacity: theme.isDark ? 0.16 : 0.06, + shadowColor: "#000", + shadowOpacity: 0.16, shadowOffset: { width: 0, height: 5 }, shadowRadius: 10, elevation: 3, diff --git a/MobileApp/src/components/SegmentedControl.tsx b/MobileApp/src/components/SegmentedControl.tsx index 2e6d69f649..1bd19cf1c3 100644 --- a/MobileApp/src/components/SegmentedControl.tsx +++ b/MobileApp/src/components/SegmentedControl.tsx @@ -19,9 +19,7 @@ export default function SegmentedControl({ onSelect, }: SegmentedControlProps): React.JSX.Element { const { theme } = useTheme(); - const activeContentColor: string = theme.isDark - ? theme.colors.backgroundPrimary - : "#FFFFFF"; + const activeContentColor: string = theme.colors.backgroundPrimary; return ( = useBiometric(); const { selectionFeedback } = useHaptics(); const [serverUrl, setServerUrlState] = useState(""); - const activeThemeOptionColor: string = theme.isDark - ? theme.colors.backgroundPrimary - : "#FFFFFF"; useEffect(() => { getServerUrl().then(setServerUrlState); }, []); - const handleThemeChange: (mode: ThemeMode) => void = ( - mode: ThemeMode, - ): void => { - selectionFeedback(); - setThemeMode(mode); - }; - const handleBiometricToggle: (value: boolean) => Promise = async ( value: boolean, ): Promise => { @@ -172,8 +161,8 @@ export default function SettingsScreen(): React.JSX.Element { backgroundColor: theme.colors.backgroundElevated, borderWidth: 1, borderColor: theme.colors.borderGlass, - shadowColor: theme.isDark ? "#000" : theme.colors.accentGradientMid, - shadowOpacity: theme.isDark ? 0.28 : 0.12, + shadowColor: "#000", + shadowOpacity: 0.28, shadowOffset: { width: 0, height: 10 }, shadowRadius: 18, elevation: 7, @@ -267,89 +256,6 @@ export default function SettingsScreen(): React.JSX.Element { - {/* Appearance */} - - - Appearance - - - {(["dark", "light", "system"] as ThemeMode[]).map( - (mode: ThemeMode, index: number) => { - const isActive: boolean = themeMode === mode; - return ( - { - return handleThemeChange(mode); - }} - style={{ - flex: 1, - flexDirection: "row", - alignItems: "center", - justifyContent: "center", - paddingVertical: 10, - borderRadius: 10, - marginLeft: index > 0 ? 4 : 0, - backgroundColor: isActive - ? theme.colors.actionPrimary - : "transparent", - }} - > - - - {mode.charAt(0).toUpperCase() + mode.slice(1)} - - - ); - }, - )} - - - {/* Security */} {biometric.isAvailable ? ( @@ -527,7 +433,7 @@ export default function SettingsScreen(): React.JSX.Element { right: 0, height: 3, backgroundColor: theme.colors.actionPrimary, - opacity: theme.isDark ? 0.45 : 0.85, + opacity: 0.45, }} /> diff --git a/MobileApp/src/storage/preferences.ts b/MobileApp/src/storage/preferences.ts index ecd9210824..6ae2fe6779 100644 --- a/MobileApp/src/storage/preferences.ts +++ b/MobileApp/src/storage/preferences.ts @@ -1,26 +1,11 @@ import AsyncStorage from "@react-native-async-storage/async-storage"; -import type { ThemeMode } from "../theme"; const KEYS: { - readonly THEME_MODE: "oneuptime_theme_mode"; readonly BIOMETRIC_ENABLED: "oneuptime_biometric_enabled"; } = { - THEME_MODE: "oneuptime_theme_mode", BIOMETRIC_ENABLED: "oneuptime_biometric_enabled", } as const; -export async function getThemeMode(): Promise { - const stored: string | null = await AsyncStorage.getItem(KEYS.THEME_MODE); - if (stored === "dark" || stored === "light" || stored === "system") { - return stored; - } - return "dark"; -} - -export async function setThemeMode(mode: ThemeMode): Promise { - await AsyncStorage.setItem(KEYS.THEME_MODE, mode); -} - export async function getBiometricEnabled(): Promise { const stored: string | null = await AsyncStorage.getItem( KEYS.BIOMETRIC_ENABLED, diff --git a/MobileApp/src/theme/ThemeContext.tsx b/MobileApp/src/theme/ThemeContext.tsx index 1192b15dfe..5f8c20cf4c 100644 --- a/MobileApp/src/theme/ThemeContext.tsx +++ b/MobileApp/src/theme/ThemeContext.tsx @@ -1,33 +1,19 @@ -import React, { - createContext, - useContext, - useState, - useEffect, - useMemo, - ReactNode, -} from "react"; -import { View, useColorScheme } from "react-native"; -import { ColorTokens, darkColors, lightColors } from "./colors"; -import { - getThemeMode as loadThemeMode, - setThemeMode as saveThemeMode, -} from "../storage/preferences"; - -export type ThemeMode = "dark" | "light" | "system"; +import React, { createContext, useContext, ReactNode } from "react"; +import { View } from "react-native"; +import { ColorTokens, darkColors } from "./colors"; export interface Theme { colors: ColorTokens; - isDark: boolean; } interface ThemeContextValue { theme: Theme; - themeMode: ThemeMode; - setThemeMode: (mode: ThemeMode) => void; } -const ThemeContext: React.Context = - createContext(undefined); +const theme: Theme = { colors: darkColors }; + +const ThemeContext: React.Context = + createContext({ theme }); interface ThemeProviderProps { children: ReactNode; @@ -36,56 +22,13 @@ interface ThemeProviderProps { export function ThemeProvider({ children, }: ThemeProviderProps): React.JSX.Element { - const systemColorScheme: "light" | "dark" | null | undefined = - useColorScheme(); - const [themeMode, setThemeModeState] = useState("light"); - - // Load persisted theme on mount - useEffect(() => { - loadThemeMode().then((mode: ThemeMode) => { - setThemeModeState(mode); - }); - }, []); - - const setThemeMode: (mode: ThemeMode) => void = (mode: ThemeMode): void => { - setThemeModeState(mode); - saveThemeMode(mode); - }; - - const theme: Theme = useMemo((): Theme => { - let isDark: boolean; - - if (themeMode === "system") { - isDark = systemColorScheme !== "light"; - } else { - isDark = themeMode === "dark"; - } - - return { - colors: isDark ? darkColors : lightColors, - isDark, - }; - }, [themeMode, systemColorScheme]); - - const value: ThemeContextValue = useMemo((): ThemeContextValue => { - return { - theme, - themeMode, - setThemeMode, - }; - }, [theme, themeMode]); - return ( - + {children} ); } export function useTheme(): ThemeContextValue { - const context: ThemeContextValue | undefined = useContext(ThemeContext); - if (!context) { - throw new Error("useTheme must be used within a ThemeProvider"); - } - return context; + return useContext(ThemeContext); } diff --git a/MobileApp/src/theme/colors.ts b/MobileApp/src/theme/colors.ts index 8c5851df2f..6907b0e9e9 100644 --- a/MobileApp/src/theme/colors.ts +++ b/MobileApp/src/theme/colors.ts @@ -141,75 +141,3 @@ export const darkColors: ColorTokens = { statusError: "#EF4444", statusErrorBg: "rgba(239, 68, 68, 0.12)", }; - -export const lightColors: ColorTokens = { - // Background — clean white with warm gray tones - backgroundPrimary: "#FFFFFF", - backgroundSecondary: "#F9FAFB", - backgroundTertiary: "#F3F4F6", - backgroundElevated: "#FFFFFF", - - // Accent - cardAccent: "rgba(0, 0, 0, 0.02)", - backgroundGlass: "rgba(255, 255, 255, 0.80)", - iconBackground: "rgba(0, 0, 0, 0.05)", - - // Gradient — neutral monochrome accent - accentGradientStart: "#52525B", - accentGradientMid: "#3F3F46", - accentGradientEnd: "#27272A", - accentCyan: "#52525B", - accentCyanBg: "rgba(82, 82, 91, 0.08)", - surfaceGlow: "rgba(0, 0, 0, 0.04)", - headerGradient: "rgba(0, 0, 0, 0.03)", - gradientStart: "rgba(0, 0, 0, 0.04)", - gradientEnd: "transparent", - - // Border - borderDefault: "#E5E7EB", - borderSubtle: "#F3F4F6", - borderGlass: "rgba(0, 0, 0, 0.05)", - - // Text - textPrimary: "#111827", - textSecondary: "#6B7280", - textTertiary: "#9CA3AF", - textInverse: "#FFFFFF", - - // Severity - severityCritical: "#DC2626", - severityCriticalBg: "rgba(220, 38, 38, 0.08)", - severityMajor: "#EA580C", - severityMajorBg: "rgba(234, 88, 12, 0.08)", - severityMinor: "#CA8A04", - severityMinorBg: "rgba(202, 138, 4, 0.08)", - severityWarning: "#D97706", - severityWarningBg: "rgba(217, 119, 6, 0.08)", - severityInfo: "#2563EB", - severityInfoBg: "rgba(37, 99, 235, 0.08)", - - // State - stateCreated: "#DC2626", - stateAcknowledged: "#D97706", - stateResolved: "#16A34A", - stateInvestigating: "#EA580C", - stateMuted: "#9CA3AF", - - // On-Call - oncallActive: "#16A34A", - oncallActiveBg: "rgba(22, 163, 74, 0.08)", - oncallInactive: "#9CA3AF", - oncallInactiveBg: "rgba(156, 163, 175, 0.08)", - - // Action — neutral accent - actionPrimary: "#27272A", - actionPrimaryPressed: "#3F3F46", - actionDestructive: "#DC2626", - actionDestructivePressed: "#B91C1C", - - // Status - statusSuccess: "#16A34A", - statusSuccessBg: "rgba(22, 163, 74, 0.08)", - statusError: "#DC2626", - statusErrorBg: "rgba(220, 38, 38, 0.08)", -}; diff --git a/MobileApp/src/theme/index.ts b/MobileApp/src/theme/index.ts index 1604d23a9d..2784bd5d58 100644 --- a/MobileApp/src/theme/index.ts +++ b/MobileApp/src/theme/index.ts @@ -1,4 +1,4 @@ -export { darkColors, lightColors } from "./colors"; +export { darkColors } from "./colors"; export type { ColorTokens } from "./colors"; export { ThemeProvider, useTheme } from "./ThemeContext"; -export type { Theme, ThemeMode } from "./ThemeContext"; +export type { Theme } from "./ThemeContext"; From 89ccde1bc44749130f85767e2914ca5fa2f56220 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Tue, 17 Feb 2026 22:12:43 +0000 Subject: [PATCH 19/44] refactor: standardize device type strings to lowercase in registerPushDevice function for consistency --- MobileApp/src/api/pushDevice.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MobileApp/src/api/pushDevice.ts b/MobileApp/src/api/pushDevice.ts index e9e103f043..fc5fac8d19 100644 --- a/MobileApp/src/api/pushDevice.ts +++ b/MobileApp/src/api/pushDevice.ts @@ -8,10 +8,10 @@ export async function registerPushDevice(params: { }): Promise { const deviceType: string = Platform.OS === "ios" - ? "iOS" + ? "ios" : Platform.OS === "android" - ? "Android" - : "Web"; + ? "android" + : "web"; try { await apiClient.post("/api/user-push/register", { From 9d5faca3ec2af8b4712dd445251ebf94ffc3c679 Mon Sep 17 00:00:00 2001 From: simlarsen <104437376+simlarsen@users.noreply.github.com> Date: Wed, 18 Feb 2026 02:31:53 +0000 Subject: [PATCH 20/44] chore: npm audit fix --- CLI/package-lock.json | 115 ++++------ Common/package-lock.json | 138 +++++------- Dashboard/package-lock.json | 405 ++++++++++++++++----------------- MCP/package-lock.json | 6 +- MobileApp/package-lock.json | 35 ++- Scripts/package-lock.json | 12 +- StatusPage/package-lock.json | 405 ++++++++++++++++----------------- package-lock.json | 424 ++++++++++++++++++----------------- 8 files changed, 733 insertions(+), 807 deletions(-) diff --git a/CLI/package-lock.json b/CLI/package-lock.json index 1d8a2ed46d..8dfb70b4f4 100644 --- a/CLI/package-lock.json +++ b/CLI/package-lock.json @@ -813,54 +813,42 @@ } }, "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz", + "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/gast": "11.1.1", + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz", + "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "node_modules/@chevrotain/gast/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz", + "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==", "license": "Apache-2.0" }, "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz", + "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==", "license": "Apache-2.0" }, "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz", + "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==", "license": "Apache-2.0" }, "node_modules/@clickhouse/client": { @@ -2147,12 +2135,12 @@ "license": "MIT" }, "node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz", + "integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==", "license": "MIT", "dependencies": { - "langium": "3.3.1" + "langium": "^4.0.0" } }, "node_modules/@monaco-editor/loader": { @@ -6155,17 +6143,17 @@ } }, "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz", + "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/cst-dts-gen": "11.1.1", + "@chevrotain/gast": "11.1.1", + "@chevrotain/regexp-to-ast": "11.1.1", + "@chevrotain/types": "11.1.1", + "@chevrotain/utils": "11.1.1", + "lodash-es": "4.17.23" } }, "node_modules/chevrotain-allstar": { @@ -6180,12 +6168,6 @@ "chevrotain": "^11.0.0" } }, - "node_modules/chevrotain/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -10509,19 +10491,20 @@ } }, "node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", + "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", "license": "MIT", "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", + "chevrotain": "~11.1.1", + "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" + "vscode-uri": "~3.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.10.0", + "npm": ">=10.2.3" } }, "node_modules/layout-base": { @@ -11168,14 +11151,14 @@ } }, "node_modules/mermaid": { - "version": "11.12.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", - "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", + "version": "11.12.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", + "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", + "@mermaid-js/parser": "^1.0.0", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", @@ -11187,7 +11170,7 @@ "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", - "lodash-es": "^4.17.21", + "lodash-es": "^4.17.23", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", @@ -16738,9 +16721,9 @@ "license": "MIT" }, "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, "node_modules/walker": { diff --git a/Common/package-lock.json b/Common/package-lock.json index c05b216352..595fd63448 100644 --- a/Common/package-lock.json +++ b/Common/package-lock.json @@ -423,7 +423,6 @@ "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -926,60 +925,47 @@ "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-5.23.0.tgz", "integrity": "sha512-iI/Ssl8T5ZEn9s899Qz67m92M6RU8thf/aqD7cUHB2yHmkCjqbw7s7NaODTsyArAsnyu7DGJMWm7EhbfFXDNgQ==", "license": "MIT", - "peer": true, "dependencies": { "@bull-board/api": "5.23.0" } }, "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz", + "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/gast": "11.1.1", + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz", + "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "node_modules/@chevrotain/gast/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz", + "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==", "license": "Apache-2.0" }, "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz", + "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==", "license": "Apache-2.0" }, "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz", + "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==", "license": "Apache-2.0" }, "node_modules/@clickhouse/client": { @@ -2202,12 +2188,12 @@ "license": "MIT" }, "node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz", + "integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==", "license": "MIT", "dependencies": { - "langium": "3.3.1" + "langium": "^4.0.0" } }, "node_modules/@monaco-editor/loader": { @@ -2366,7 +2352,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -5516,7 +5501,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5692,7 +5676,8 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz", "integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/unist": { "version": "2.0.11", @@ -5862,7 +5847,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6755,7 +6739,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001669", "electron-to-chromium": "^1.5.41", @@ -7078,17 +7061,17 @@ } }, "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz", + "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/cst-dts-gen": "11.1.1", + "@chevrotain/gast": "11.1.1", + "@chevrotain/regexp-to-ast": "11.1.1", + "@chevrotain/types": "11.1.1", + "@chevrotain/utils": "11.1.1", + "lodash-es": "4.17.23" } }, "node_modules/chevrotain-allstar": { @@ -7103,12 +7086,6 @@ "chevrotain": "^11.0.0" } }, - "node_modules/chevrotain/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -7593,7 +7570,6 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10" } @@ -8015,7 +7991,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -10795,7 +10770,6 @@ "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^28.1.3", "@jest/types": "^28.1.3", @@ -12185,19 +12159,20 @@ } }, "node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", + "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", "license": "MIT", "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", + "chevrotain": "~11.1.1", + "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" + "vscode-uri": "~3.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.10.0", + "npm": ">=10.2.3" } }, "node_modules/layout-base": { @@ -13099,14 +13074,14 @@ } }, "node_modules/mermaid": { - "version": "11.12.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", - "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", + "version": "11.12.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", + "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", + "@mermaid-js/parser": "^1.0.0", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", @@ -13118,7 +13093,7 @@ "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", - "lodash-es": "^4.17.21", + "lodash-es": "^4.17.23", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", @@ -14511,7 +14486,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -14894,7 +14868,6 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -15198,7 +15171,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -15283,7 +15255,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -15804,8 +15775,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/refractor": { "version": "5.0.0", @@ -18630,9 +18600,9 @@ "license": "MIT" }, "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, "node_modules/w3c-xmlserializer": { @@ -19102,7 +19072,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -19111,8 +19080,7 @@ "version": "0.14.10", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/zustand": { "version": "4.5.5", diff --git a/Dashboard/package-lock.json b/Dashboard/package-lock.json index 35c13bd772..f793f45760 100644 --- a/Dashboard/package-lock.json +++ b/Dashboard/package-lock.json @@ -386,45 +386,45 @@ } }, "../Common/node_modules/@aws-sdk/client-sso": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.980.0.tgz", - "integrity": "sha512-AhNXQaJ46C1I+lQ+6Kj+L24il5K9lqqIanJd8lMszPmP7bLnmX0wTKK0dxywcvrLdij3zhWttjAKEBNgLtS8/A==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.990.0.tgz", + "integrity": "sha512-xTEaPjZwOqVjGbLOP7qzwbdOWJOo1ne2mUhTZwEBBkPvNk4aXB/vcYwWwrjoSWUqtit4+GDbO75ePc/S6TUJYQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-endpoints": "3.990.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -436,9 +436,9 @@ } }, "../Common/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", - "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -453,20 +453,20 @@ } }, "../Common/node_modules/@aws-sdk/core": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz", - "integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==", + "version": "3.973.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.10.tgz", + "integrity": "sha512-4u/FbyyT3JqzfsESI70iFg6e2yp87MB5kS2qcxIA66m52VSTN1fvuvbCY1h/LKq1LvuxIrlJ1ItcyjvcKoaPLg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", - "@aws-sdk/xml-builder": "^3.972.2", - "@smithy/core": "^3.22.0", + "@aws-sdk/xml-builder": "^3.972.4", + "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", @@ -478,13 +478,13 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.3.tgz", - "integrity": "sha512-OBYNY4xQPq7Rx+oOhtyuyO0AQvdJSpXRg7JuPNBJH4a1XXIzJQl4UHQTPKZKwfJXmYLpv4+OkcFen4LYmDPd3g==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.8.tgz", + "integrity": "sha512-r91OOPAcHnLCSxaeu/lzZAVRCZ/CtTNuwmJkUwpwSDshUrP7bkX1OmFn2nUMWd9kN53Q4cEo8b7226G4olt2Mg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", @@ -495,21 +495,21 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.5.tgz", - "integrity": "sha512-GpvBgEmSZPvlDekd26Zi+XsI27Qz7y0utUx0g2fSTSiDzhnd1FSa1owuodxR0BcUKNL7U2cOVhhDxgZ4iSoPVg==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.10.tgz", + "integrity": "sha512-DTtuyXSWB+KetzLcWaSahLJCtTUe/3SXtlGp4ik9PCe9xD6swHEkG8n8/BNsQ9dsihb9nhFvuUB4DpdBGDcvVg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -517,20 +517,20 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.3.tgz", - "integrity": "sha512-rMQAIxstP7cLgYfsRGrGOlpyMl0l8JL2mcke3dsIPLWke05zKOFyR7yoJzWCsI/QiIxjRbxpvPiAeKEA6CoYkg==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.8.tgz", + "integrity": "sha512-n2dMn21gvbBIEh00E8Nb+j01U/9rSqFIamWRdGm/mE5e+vHQ9g0cBNdrYFlM6AAiryKVHZmShWT9D1JAWJ3ISw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/credential-provider-env": "^3.972.3", - "@aws-sdk/credential-provider-http": "^3.972.5", - "@aws-sdk/credential-provider-login": "^3.972.3", - "@aws-sdk/credential-provider-process": "^3.972.3", - "@aws-sdk/credential-provider-sso": "^3.972.3", - "@aws-sdk/credential-provider-web-identity": "^3.972.3", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/credential-provider-env": "^3.972.8", + "@aws-sdk/credential-provider-http": "^3.972.10", + "@aws-sdk/credential-provider-login": "^3.972.8", + "@aws-sdk/credential-provider-process": "^3.972.8", + "@aws-sdk/credential-provider-sso": "^3.972.8", + "@aws-sdk/credential-provider-web-identity": "^3.972.8", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -543,14 +543,14 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.3.tgz", - "integrity": "sha512-Gc3O91iVvA47kp2CLIXOwuo5ffo1cIpmmyIewcYjAcvurdFHQ8YdcBe1KHidnbbBO4/ZtywGBACsAX5vr3UdoA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.8.tgz", + "integrity": "sha512-rMFuVids8ICge/X9DF5pRdGMIvkVhDV9IQFQ8aTYk6iF0rl9jOUa1C3kjepxiXUlpgJQT++sLZkT9n0TMLHhQw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", @@ -563,18 +563,18 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.4.tgz", - "integrity": "sha512-UwerdzosMSY7V5oIZm3NsMDZPv2aSVzSkZxYxIOWHBeKTZlUqW7XpHtJMZ4PZpJ+HMRhgP+MDGQx4THndgqJfQ==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.9.tgz", + "integrity": "sha512-LfJfO0ClRAq2WsSnA9JuUsNyIicD2eyputxSlSL0EiMrtxOxELLRG6ZVYDf/a1HCepaYPXeakH4y8D5OLCauag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.3", - "@aws-sdk/credential-provider-http": "^3.972.5", - "@aws-sdk/credential-provider-ini": "^3.972.3", - "@aws-sdk/credential-provider-process": "^3.972.3", - "@aws-sdk/credential-provider-sso": "^3.972.3", - "@aws-sdk/credential-provider-web-identity": "^3.972.3", + "@aws-sdk/credential-provider-env": "^3.972.8", + "@aws-sdk/credential-provider-http": "^3.972.10", + "@aws-sdk/credential-provider-ini": "^3.972.8", + "@aws-sdk/credential-provider-process": "^3.972.8", + "@aws-sdk/credential-provider-sso": "^3.972.8", + "@aws-sdk/credential-provider-web-identity": "^3.972.8", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -587,13 +587,13 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.3.tgz", - "integrity": "sha512-xkSY7zjRqeVc6TXK2xr3z1bTLm0wD8cj3lAkproRGaO4Ku7dPlKy843YKnHrUOUzOnMezdZ4xtmFc0eKIDTo2w==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.8.tgz", + "integrity": "sha512-6cg26ffFltxM51OOS8NH7oE41EccaYiNlbd5VgUYwhiGCySLfHoGuGrLm2rMB4zhy+IO5nWIIG0HiodX8zdvHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -605,15 +605,15 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.3.tgz", - "integrity": "sha512-8Ww3F5Ngk8dZ6JPL/V5LhCU1BwMfQd3tLdoEuzaewX8FdnT633tPr+KTHySz9FK7fFPcz5qG3R5edVEhWQD4AA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.8.tgz", + "integrity": "sha512-35kqmFOVU1n26SNv+U37sM8b2TzG8LyqAcd6iM9gprqxyHEh/8IM3gzN4Jzufs3qM6IrH8e43ryZWYdvfVzzKQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.980.0", - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/token-providers": "3.980.0", + "@aws-sdk/client-sso": "3.990.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/token-providers": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -625,14 +625,14 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.3.tgz", - "integrity": "sha512-62VufdcH5rRfiRKZRcf1wVbbt/1jAntMj1+J0qAd+r5pQRg2t0/P9/Rz16B1o5/0Se9lVL506LRjrhIJAhYBfA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.8.tgz", + "integrity": "sha512-CZhN1bOc1J3ubQPqbmr5b4KaMJBgdDvYsmEIZuX++wFlzmZsKj1bwkaiTEb5U2V7kXuzLlpF5HJSOM9eY/6nGA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -692,16 +692,16 @@ } }, "../Common/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz", - "integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.10.tgz", + "integrity": "sha512-bBEL8CAqPQkI91ZM5a9xnFAzedpzH6NYCOtNyLarRAzTUTFN2DKqaC60ugBa7pnU1jSi4mA7WAXBsrod7nJltg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", - "@smithy/core": "^3.22.0", + "@aws-sdk/util-endpoints": "3.990.0", + "@smithy/core": "^3.23.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -711,9 +711,9 @@ } }, "../Common/node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", - "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -728,45 +728,45 @@ } }, "../Common/node_modules/@aws-sdk/nested-clients": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz", - "integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.990.0.tgz", + "integrity": "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-endpoints": "3.990.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -778,9 +778,9 @@ } }, "../Common/node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", - "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -812,14 +812,14 @@ } }, "../Common/node_modules/@aws-sdk/token-providers": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz", - "integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.990.0.tgz", + "integrity": "sha512-L3BtUb2v9XmYgQdfGBzbBtKMXaP5fV973y3Qdxeevs6oUTVXFmi/mV1+LnScA/1wVPJC9/hlK+1o5vbt7cG7EQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -888,13 +888,13 @@ } }, "../Common/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz", - "integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.8.tgz", + "integrity": "sha512-XJZuT0LWsFCW1C8dEpPAXSa7h6Pb3krr2y//1X0Zidpcl0vmgY5nL/X0JuBZlntpBzaN3+U4hvKjuijyiiR8zw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -913,9 +913,9 @@ } }, "../Common/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.3.tgz", - "integrity": "sha512-bCk63RsBNCWW4tt5atv5Sbrh+3J3e8YzgyF6aZb1JeXcdzG4k5SlPLeTMFOIXFuuFHIwgphUhn4i3uS/q49eww==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", + "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1687,54 +1687,42 @@ } }, "../Common/node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz", + "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/gast": "11.1.1", + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "../Common/node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "../Common/node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz", + "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "../Common/node_modules/@chevrotain/gast/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "../Common/node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz", + "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==", "license": "Apache-2.0" }, "../Common/node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz", + "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==", "license": "Apache-2.0" }, "../Common/node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz", + "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==", "license": "Apache-2.0" }, "../Common/node_modules/@clickhouse/client": { @@ -3039,12 +3027,12 @@ "license": "MIT" }, "../Common/node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz", + "integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==", "license": "MIT", "dependencies": { - "langium": "3.3.1" + "langium": "^4.0.0" } }, "../Common/node_modules/@monaco-editor/loader": { @@ -5084,9 +5072,9 @@ } }, "../Common/node_modules/@smithy/core": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", - "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.2.tgz", + "integrity": "sha512-HaaH4VbGie4t0+9nY3tNBRSxVTr96wzIqexUa6C2qx3MPePAuz7lIxPxYtt1Wc//SPfJLNoZJzfdt0B6ksj2jA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5096,7 +5084,7 @@ "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -5198,13 +5186,13 @@ } }, "../Common/node_modules/@smithy/middleware-endpoint": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", - "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.16.tgz", + "integrity": "sha512-L5GICFCSsNhbJ5JSKeWFGFy16Q2OhoBizb3X2DrxaJwXSEujVvjG9Jt386dpQn2t7jINglQl0b4K/Su69BdbMA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.22.1", + "@smithy/core": "^3.23.2", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -5218,16 +5206,16 @@ } }, "../Common/node_modules/@smithy/middleware-retry": { - "version": "4.4.30", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", - "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", + "version": "4.4.33", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.33.tgz", + "integrity": "sha512-jLqZOdJhtIL4lnA9hXnAG6GgnJlo1sD3FqsTxm9wSfjviqgWesY/TMBVnT84yr4O0Vfe0jWoXlfFbzsBVph3WA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -5284,9 +5272,9 @@ } }, "../Common/node_modules/@smithy/node-http-handler": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", - "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz", + "integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5405,18 +5393,18 @@ } }, "../Common/node_modules/@smithy/smithy-client": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", - "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.5.tgz", + "integrity": "sha512-xixwBRqoeP2IUgcAl3U9dvJXc+qJum4lzo3maaJxifsZxKUYLfVfCXvhT4/jD01sRrHg5zjd1cw2Zmjr4/SuKQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.22.1", - "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/core": "^3.23.2", + "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -5520,14 +5508,14 @@ } }, "../Common/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.29", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", - "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", + "version": "4.3.32", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.32.tgz", + "integrity": "sha512-092sjYfFMQ/iaPH798LY/OJFBcYu0sSK34Oy9vdixhsU36zlZu8OcYjF3TD4e2ARupyK7xaxPXl+T0VIJTEkkg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -5536,9 +5524,9 @@ } }, "../Common/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.32", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", - "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", + "version": "4.2.35", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.35.tgz", + "integrity": "sha512-miz/ggz87M8VuM29y7jJZMYkn7+IErM5p5UgKIf8OtqVs/h2bXr1Bt3uTsREsI/4nK8a0PQERbAPsVPVNIsG7Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5546,7 +5534,7 @@ "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -5612,14 +5600,14 @@ } }, "../Common/node_modules/@smithy/util-stream": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", - "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz", + "integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.9", + "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", @@ -8040,17 +8028,17 @@ } }, "../Common/node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz", + "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/cst-dts-gen": "11.1.1", + "@chevrotain/gast": "11.1.1", + "@chevrotain/regexp-to-ast": "11.1.1", + "@chevrotain/types": "11.1.1", + "@chevrotain/utils": "11.1.1", + "lodash-es": "4.17.23" } }, "../Common/node_modules/chevrotain-allstar": { @@ -8065,12 +8053,6 @@ "chevrotain": "^11.0.0" } }, - "../Common/node_modules/chevrotain/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "../Common/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -13296,19 +13278,20 @@ } }, "../Common/node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", + "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", "license": "MIT", "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", + "chevrotain": "~11.1.1", + "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" + "vscode-uri": "~3.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.10.0", + "npm": ">=10.2.3" } }, "../Common/node_modules/layout-base": { @@ -14106,14 +14089,14 @@ } }, "../Common/node_modules/mermaid": { - "version": "11.12.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", - "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", + "version": "11.12.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", + "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", + "@mermaid-js/parser": "^1.0.0", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", @@ -14125,7 +14108,7 @@ "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", - "lodash-es": "^4.17.21", + "lodash-es": "^4.17.23", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", @@ -19836,9 +19819,9 @@ "license": "MIT" }, "../Common/node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, "../Common/node_modules/w3c-xmlserializer": { diff --git a/MCP/package-lock.json b/MCP/package-lock.json index 66b698572b..fe4289214f 100644 --- a/MCP/package-lock.json +++ b/MCP/package-lock.json @@ -1297,9 +1297,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", diff --git a/MobileApp/package-lock.json b/MobileApp/package-lock.json index f22d5d6263..8724ad4874 100644 --- a/MobileApp/package-lock.json +++ b/MobileApp/package-lock.json @@ -97,7 +97,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1378,6 +1377,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -3171,7 +3171,6 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.28.tgz", "integrity": "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ==", "license": "MIT", - "peer": true, "dependencies": { "@react-navigation/core": "^7.14.0", "escape-string-regexp": "^4.0.0", @@ -3290,7 +3289,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.90.20" }, @@ -3408,7 +3406,6 @@ "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4130,7 +4127,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5206,7 +5202,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz", "integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.23", @@ -5294,7 +5289,6 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -7206,7 +7200,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8739,7 +8732,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -9071,7 +9063,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9091,7 +9082,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -9122,7 +9112,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", @@ -9486,6 +9475,7 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz", "integrity": "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==", "license": "MIT", + "peer": true, "dependencies": { "react-native-is-edge-to-edge": "1.2.1", "semver": "7.7.3" @@ -9501,6 +9491,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -9513,7 +9504,6 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -9524,7 +9514,6 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", "license": "MIT", - "peer": true, "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.2.1", @@ -9555,7 +9544,6 @@ "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", @@ -9588,6 +9576,7 @@ "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.3.tgz", "integrity": "sha512-m/CIUCHvLQulboBn0BtgpsesXjOTeubU7t+V0lCPpBj0t2ExigwqDHoKj3ck7OeErnjgkD27wdAtQCubYATe3g==", "license": "MIT", + "peer": true, "dependencies": { "@babel/plugin-transform-arrow-functions": "7.27.1", "@babel/plugin-transform-class-properties": "7.27.1", @@ -9612,6 +9601,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -9628,6 +9618,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", @@ -9648,6 +9639,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -9663,6 +9655,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -9679,6 +9672,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", @@ -9698,6 +9692,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -9794,7 +9789,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10626,7 +10620,6 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -10660,9 +10653,9 @@ } }, "node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", + "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -10855,7 +10848,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11416,7 +11408,6 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/Scripts/package-lock.json b/Scripts/package-lock.json index bc5777bafe..2a4c001fe2 100644 --- a/Scripts/package-lock.json +++ b/Scripts/package-lock.json @@ -1386,9 +1386,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -5737,9 +5737,9 @@ } }, "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/StatusPage/package-lock.json b/StatusPage/package-lock.json index 545e0c2186..cdb2d31e57 100644 --- a/StatusPage/package-lock.json +++ b/StatusPage/package-lock.json @@ -383,45 +383,45 @@ } }, "../Common/node_modules/@aws-sdk/client-sso": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.980.0.tgz", - "integrity": "sha512-AhNXQaJ46C1I+lQ+6Kj+L24il5K9lqqIanJd8lMszPmP7bLnmX0wTKK0dxywcvrLdij3zhWttjAKEBNgLtS8/A==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.990.0.tgz", + "integrity": "sha512-xTEaPjZwOqVjGbLOP7qzwbdOWJOo1ne2mUhTZwEBBkPvNk4aXB/vcYwWwrjoSWUqtit4+GDbO75ePc/S6TUJYQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-endpoints": "3.990.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -433,9 +433,9 @@ } }, "../Common/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", - "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -450,20 +450,20 @@ } }, "../Common/node_modules/@aws-sdk/core": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz", - "integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==", + "version": "3.973.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.10.tgz", + "integrity": "sha512-4u/FbyyT3JqzfsESI70iFg6e2yp87MB5kS2qcxIA66m52VSTN1fvuvbCY1h/LKq1LvuxIrlJ1ItcyjvcKoaPLg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", - "@aws-sdk/xml-builder": "^3.972.2", - "@smithy/core": "^3.22.0", + "@aws-sdk/xml-builder": "^3.972.4", + "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", @@ -475,13 +475,13 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.3.tgz", - "integrity": "sha512-OBYNY4xQPq7Rx+oOhtyuyO0AQvdJSpXRg7JuPNBJH4a1XXIzJQl4UHQTPKZKwfJXmYLpv4+OkcFen4LYmDPd3g==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.8.tgz", + "integrity": "sha512-r91OOPAcHnLCSxaeu/lzZAVRCZ/CtTNuwmJkUwpwSDshUrP7bkX1OmFn2nUMWd9kN53Q4cEo8b7226G4olt2Mg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", @@ -492,21 +492,21 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.5.tgz", - "integrity": "sha512-GpvBgEmSZPvlDekd26Zi+XsI27Qz7y0utUx0g2fSTSiDzhnd1FSa1owuodxR0BcUKNL7U2cOVhhDxgZ4iSoPVg==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.10.tgz", + "integrity": "sha512-DTtuyXSWB+KetzLcWaSahLJCtTUe/3SXtlGp4ik9PCe9xD6swHEkG8n8/BNsQ9dsihb9nhFvuUB4DpdBGDcvVg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -514,20 +514,20 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.3.tgz", - "integrity": "sha512-rMQAIxstP7cLgYfsRGrGOlpyMl0l8JL2mcke3dsIPLWke05zKOFyR7yoJzWCsI/QiIxjRbxpvPiAeKEA6CoYkg==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.8.tgz", + "integrity": "sha512-n2dMn21gvbBIEh00E8Nb+j01U/9rSqFIamWRdGm/mE5e+vHQ9g0cBNdrYFlM6AAiryKVHZmShWT9D1JAWJ3ISw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/credential-provider-env": "^3.972.3", - "@aws-sdk/credential-provider-http": "^3.972.5", - "@aws-sdk/credential-provider-login": "^3.972.3", - "@aws-sdk/credential-provider-process": "^3.972.3", - "@aws-sdk/credential-provider-sso": "^3.972.3", - "@aws-sdk/credential-provider-web-identity": "^3.972.3", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/credential-provider-env": "^3.972.8", + "@aws-sdk/credential-provider-http": "^3.972.10", + "@aws-sdk/credential-provider-login": "^3.972.8", + "@aws-sdk/credential-provider-process": "^3.972.8", + "@aws-sdk/credential-provider-sso": "^3.972.8", + "@aws-sdk/credential-provider-web-identity": "^3.972.8", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -540,14 +540,14 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.3.tgz", - "integrity": "sha512-Gc3O91iVvA47kp2CLIXOwuo5ffo1cIpmmyIewcYjAcvurdFHQ8YdcBe1KHidnbbBO4/ZtywGBACsAX5vr3UdoA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.8.tgz", + "integrity": "sha512-rMFuVids8ICge/X9DF5pRdGMIvkVhDV9IQFQ8aTYk6iF0rl9jOUa1C3kjepxiXUlpgJQT++sLZkT9n0TMLHhQw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", @@ -560,18 +560,18 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.4.tgz", - "integrity": "sha512-UwerdzosMSY7V5oIZm3NsMDZPv2aSVzSkZxYxIOWHBeKTZlUqW7XpHtJMZ4PZpJ+HMRhgP+MDGQx4THndgqJfQ==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.9.tgz", + "integrity": "sha512-LfJfO0ClRAq2WsSnA9JuUsNyIicD2eyputxSlSL0EiMrtxOxELLRG6ZVYDf/a1HCepaYPXeakH4y8D5OLCauag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.3", - "@aws-sdk/credential-provider-http": "^3.972.5", - "@aws-sdk/credential-provider-ini": "^3.972.3", - "@aws-sdk/credential-provider-process": "^3.972.3", - "@aws-sdk/credential-provider-sso": "^3.972.3", - "@aws-sdk/credential-provider-web-identity": "^3.972.3", + "@aws-sdk/credential-provider-env": "^3.972.8", + "@aws-sdk/credential-provider-http": "^3.972.10", + "@aws-sdk/credential-provider-ini": "^3.972.8", + "@aws-sdk/credential-provider-process": "^3.972.8", + "@aws-sdk/credential-provider-sso": "^3.972.8", + "@aws-sdk/credential-provider-web-identity": "^3.972.8", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", @@ -584,13 +584,13 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.3.tgz", - "integrity": "sha512-xkSY7zjRqeVc6TXK2xr3z1bTLm0wD8cj3lAkproRGaO4Ku7dPlKy843YKnHrUOUzOnMezdZ4xtmFc0eKIDTo2w==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.8.tgz", + "integrity": "sha512-6cg26ffFltxM51OOS8NH7oE41EccaYiNlbd5VgUYwhiGCySLfHoGuGrLm2rMB4zhy+IO5nWIIG0HiodX8zdvHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -602,15 +602,15 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.3.tgz", - "integrity": "sha512-8Ww3F5Ngk8dZ6JPL/V5LhCU1BwMfQd3tLdoEuzaewX8FdnT633tPr+KTHySz9FK7fFPcz5qG3R5edVEhWQD4AA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.8.tgz", + "integrity": "sha512-35kqmFOVU1n26SNv+U37sM8b2TzG8LyqAcd6iM9gprqxyHEh/8IM3gzN4Jzufs3qM6IrH8e43ryZWYdvfVzzKQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.980.0", - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/token-providers": "3.980.0", + "@aws-sdk/client-sso": "3.990.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/token-providers": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -622,14 +622,14 @@ } }, "../Common/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.3.tgz", - "integrity": "sha512-62VufdcH5rRfiRKZRcf1wVbbt/1jAntMj1+J0qAd+r5pQRg2t0/P9/Rz16B1o5/0Se9lVL506LRjrhIJAhYBfA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.8.tgz", + "integrity": "sha512-CZhN1bOc1J3ubQPqbmr5b4KaMJBgdDvYsmEIZuX++wFlzmZsKj1bwkaiTEb5U2V7kXuzLlpF5HJSOM9eY/6nGA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -689,16 +689,16 @@ } }, "../Common/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz", - "integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.10.tgz", + "integrity": "sha512-bBEL8CAqPQkI91ZM5a9xnFAzedpzH6NYCOtNyLarRAzTUTFN2DKqaC60ugBa7pnU1jSi4mA7WAXBsrod7nJltg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", - "@smithy/core": "^3.22.0", + "@aws-sdk/util-endpoints": "3.990.0", + "@smithy/core": "^3.23.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -708,9 +708,9 @@ } }, "../Common/node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", - "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -725,45 +725,45 @@ } }, "../Common/node_modules/@aws-sdk/nested-clients": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz", - "integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.990.0.tgz", + "integrity": "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.5", + "@aws-sdk/core": "^3.973.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-endpoints": "3.990.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -775,9 +775,9 @@ } }, "../Common/node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", - "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -809,14 +809,14 @@ } }, "../Common/node_modules/@aws-sdk/token-providers": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz", - "integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.990.0.tgz", + "integrity": "sha512-L3BtUb2v9XmYgQdfGBzbBtKMXaP5fV973y3Qdxeevs6oUTVXFmi/mV1+LnScA/1wVPJC9/hlK+1o5vbt7cG7EQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.5", - "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -885,13 +885,13 @@ } }, "../Common/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz", - "integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.8.tgz", + "integrity": "sha512-XJZuT0LWsFCW1C8dEpPAXSa7h6Pb3krr2y//1X0Zidpcl0vmgY5nL/X0JuBZlntpBzaN3+U4hvKjuijyiiR8zw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -910,9 +910,9 @@ } }, "../Common/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.3.tgz", - "integrity": "sha512-bCk63RsBNCWW4tt5atv5Sbrh+3J3e8YzgyF6aZb1JeXcdzG4k5SlPLeTMFOIXFuuFHIwgphUhn4i3uS/q49eww==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", + "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1684,54 +1684,42 @@ } }, "../Common/node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz", + "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/gast": "11.1.1", + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "../Common/node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "../Common/node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz", + "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "../Common/node_modules/@chevrotain/gast/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "../Common/node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz", + "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==", "license": "Apache-2.0" }, "../Common/node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz", + "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==", "license": "Apache-2.0" }, "../Common/node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz", + "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==", "license": "Apache-2.0" }, "../Common/node_modules/@clickhouse/client": { @@ -3092,12 +3080,12 @@ "license": "MIT" }, "../Common/node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz", + "integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==", "license": "MIT", "dependencies": { - "langium": "3.3.1" + "langium": "^4.0.0" } }, "../Common/node_modules/@monaco-editor/loader": { @@ -5137,9 +5125,9 @@ } }, "../Common/node_modules/@smithy/core": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", - "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.2.tgz", + "integrity": "sha512-HaaH4VbGie4t0+9nY3tNBRSxVTr96wzIqexUa6C2qx3MPePAuz7lIxPxYtt1Wc//SPfJLNoZJzfdt0B6ksj2jA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5149,7 +5137,7 @@ "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -5251,13 +5239,13 @@ } }, "../Common/node_modules/@smithy/middleware-endpoint": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", - "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.16.tgz", + "integrity": "sha512-L5GICFCSsNhbJ5JSKeWFGFy16Q2OhoBizb3X2DrxaJwXSEujVvjG9Jt386dpQn2t7jINglQl0b4K/Su69BdbMA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.22.1", + "@smithy/core": "^3.23.2", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -5271,16 +5259,16 @@ } }, "../Common/node_modules/@smithy/middleware-retry": { - "version": "4.4.30", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", - "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", + "version": "4.4.33", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.33.tgz", + "integrity": "sha512-jLqZOdJhtIL4lnA9hXnAG6GgnJlo1sD3FqsTxm9wSfjviqgWesY/TMBVnT84yr4O0Vfe0jWoXlfFbzsBVph3WA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -5337,9 +5325,9 @@ } }, "../Common/node_modules/@smithy/node-http-handler": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", - "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz", + "integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5458,18 +5446,18 @@ } }, "../Common/node_modules/@smithy/smithy-client": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", - "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.5.tgz", + "integrity": "sha512-xixwBRqoeP2IUgcAl3U9dvJXc+qJum4lzo3maaJxifsZxKUYLfVfCXvhT4/jD01sRrHg5zjd1cw2Zmjr4/SuKQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.22.1", - "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/core": "^3.23.2", + "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.11", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -5573,14 +5561,14 @@ } }, "../Common/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.29", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", - "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", + "version": "4.3.32", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.32.tgz", + "integrity": "sha512-092sjYfFMQ/iaPH798LY/OJFBcYu0sSK34Oy9vdixhsU36zlZu8OcYjF3TD4e2ARupyK7xaxPXl+T0VIJTEkkg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -5589,9 +5577,9 @@ } }, "../Common/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.32", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", - "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", + "version": "4.2.35", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.35.tgz", + "integrity": "sha512-miz/ggz87M8VuM29y7jJZMYkn7+IErM5p5UgKIf8OtqVs/h2bXr1Bt3uTsREsI/4nK8a0PQERbAPsVPVNIsG7Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5599,7 +5587,7 @@ "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", + "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -5665,14 +5653,14 @@ } }, "../Common/node_modules/@smithy/util-stream": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", - "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz", + "integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.9", + "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", @@ -8107,17 +8095,17 @@ } }, "../Common/node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz", + "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/cst-dts-gen": "11.1.1", + "@chevrotain/gast": "11.1.1", + "@chevrotain/regexp-to-ast": "11.1.1", + "@chevrotain/types": "11.1.1", + "@chevrotain/utils": "11.1.1", + "lodash-es": "4.17.23" } }, "../Common/node_modules/chevrotain-allstar": { @@ -8132,12 +8120,6 @@ "chevrotain": "^11.0.0" } }, - "../Common/node_modules/chevrotain/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "../Common/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -13618,19 +13600,20 @@ } }, "../Common/node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", + "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", "license": "MIT", "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", + "chevrotain": "~11.1.1", + "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" + "vscode-uri": "~3.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.10.0", + "npm": ">=10.2.3" } }, "../Common/node_modules/layout-base": { @@ -14428,14 +14411,14 @@ } }, "../Common/node_modules/mermaid": { - "version": "11.12.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", - "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", + "version": "11.12.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", + "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", + "@mermaid-js/parser": "^1.0.0", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", @@ -14447,7 +14430,7 @@ "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", - "lodash-es": "^4.17.21", + "lodash-es": "^4.17.23", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", @@ -20201,9 +20184,9 @@ "license": "MIT" }, "../Common/node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, "../Common/node_modules/w3c-xmlserializer": { diff --git a/package-lock.json b/package-lock.json index d040326977..4d5f0a04ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -568,9 +568,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -586,21 +586,21 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -609,18 +609,21 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -630,9 +633,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -641,7 +644,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -665,9 +668,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", - "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -677,21 +680,21 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -1348,7 +1351,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "devOptional": true, + "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1361,7 +1364,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "devOptional": true, + "dev": true, "engines": { "node": ">= 8" } @@ -1370,7 +1373,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "devOptional": true, + "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1390,15 +1393,15 @@ } }, "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/pkgr" } }, "node_modules/@selderee/plugin-htmlparser2": { @@ -1594,21 +1597,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz", - "integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz", + "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==", "devOptional": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/type-utils": "8.44.1", - "@typescript-eslint/utils": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/type-utils": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1618,8 +1620,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.44.1", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -1634,17 +1636,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz", - "integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz", + "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "devOptional": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1654,20 +1656,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz", - "integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz", + "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==", "devOptional": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.44.1", - "@typescript-eslint/types": "^8.44.1", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.56.0", + "@typescript-eslint/types": "^8.56.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1681,14 +1683,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz", - "integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz", + "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==", "devOptional": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1" + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1699,9 +1701,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz", - "integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz", + "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==", "devOptional": true, "license": "MIT", "engines": { @@ -1716,17 +1718,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz", - "integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz", + "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==", "devOptional": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1", - "@typescript-eslint/utils": "8.44.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1736,14 +1738,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz", - "integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz", + "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==", "devOptional": true, "license": "MIT", "engines": { @@ -1755,22 +1757,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz", - "integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz", + "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==", "devOptional": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.44.1", - "@typescript-eslint/tsconfig-utils": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.56.0", + "@typescript-eslint/tsconfig-utils": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1810,9 +1811,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "devOptional": true, "license": "ISC", "bin": { @@ -1823,16 +1824,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.1.tgz", - "integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz", + "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==", "devOptional": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1842,19 +1843,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz", - "integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz", + "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==", "devOptional": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.1", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.56.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1865,13 +1866,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", + "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", "devOptional": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3566,24 +3567,23 @@ } }, "node_modules/eslint": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", - "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.36.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -3626,10 +3626,11 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3638,13 +3639,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "dev": true, + "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -3655,7 +3657,7 @@ "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", - "eslint-config-prettier": "*", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -3727,13 +3729,13 @@ } }, "node_modules/eslint-plugin-unused-imports": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz", - "integrity": "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.4.1.tgz", + "integrity": "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==", "license": "MIT", "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", - "eslint": "^9.0.0 || ^8.0.0" + "eslint": "^10.0.0 || ^9.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@typescript-eslint/eslint-plugin": { @@ -3931,13 +3933,14 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3954,7 +3957,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "devOptional": true, + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3976,7 +3979,7 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "devOptional": true, + "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -4397,12 +4400,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "devOptional": true - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -5887,18 +5884,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-snapshot/node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, "node_modules/jest-snapshot/node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -5911,21 +5896,6 @@ "node": ">=10" } }, - "node_modules/jest-snapshot/node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, "node_modules/jest-util": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", @@ -6539,7 +6509,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "devOptional": true, + "dev": true, "engines": { "node": ">= 8" } @@ -7191,10 +7161,11 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -7287,7 +7258,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -7453,7 +7424,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "devOptional": true, + "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -7469,7 +7440,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -8142,19 +8113,18 @@ } }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", - "dev": true, + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "license": "MIT", "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/synckit" } }, "node_modules/syntax-error": { @@ -8207,6 +8177,54 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tlds": { "version": "1.261.0", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", @@ -8261,9 +8279,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "devOptional": true, "license": "MIT", "engines": { @@ -8620,16 +8638,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.1.tgz", - "integrity": "sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz", + "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.44.1", - "@typescript-eslint/parser": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1", - "@typescript-eslint/utils": "8.44.1" + "@typescript-eslint/eslint-plugin": "8.56.0", + "@typescript-eslint/parser": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8639,7 +8657,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, From d81682d02f914f6907efc13890ec8c6624b5d3e6 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 07:47:07 +0000 Subject: [PATCH 21/44] Refactor styles in various components for improved readability and consistency - Simplified style definitions in AlertCard, EpisodeCard, FeedTimeline, IncidentCard, AlertDetailScreen, AlertEpisodeDetailScreen, and others by using multi-line formatting. - Enhanced the layout of components in HomeScreen, MyOnCallPoliciesScreen, and SettingsScreen for better maintainability. - Updated text styles for better clarity and consistency across screens. --- MobileApp/src/components/AlertCard.tsx | 4 +- MobileApp/src/components/EpisodeCard.tsx | 370 +++++++++--------- MobileApp/src/components/FeedTimeline.tsx | 6 +- MobileApp/src/components/IncidentCard.tsx | 4 +- MobileApp/src/screens/AlertDetailScreen.tsx | 4 +- .../src/screens/AlertEpisodeDetailScreen.tsx | 4 +- MobileApp/src/screens/AlertsScreen.tsx | 12 +- MobileApp/src/screens/BiometricLockScreen.tsx | 15 +- MobileApp/src/screens/HomeScreen.tsx | 185 +++++---- .../src/screens/IncidentDetailScreen.tsx | 4 +- .../screens/IncidentEpisodeDetailScreen.tsx | 4 +- MobileApp/src/screens/IncidentsScreen.tsx | 12 +- .../src/screens/MyOnCallPoliciesScreen.tsx | 69 +++- MobileApp/src/screens/SettingsScreen.tsx | 54 ++- MobileApp/src/screens/auth/LoginScreen.tsx | 51 ++- .../src/screens/auth/ServerUrlScreen.tsx | 47 ++- 16 files changed, 508 insertions(+), 337 deletions(-) diff --git a/MobileApp/src/components/AlertCard.tsx b/MobileApp/src/components/AlertCard.tsx index 627d7bc077..591e7fdef6 100644 --- a/MobileApp/src/components/AlertCard.tsx +++ b/MobileApp/src/components/AlertCard.tsx @@ -138,9 +138,7 @@ export default function AlertCard({ color={theme.colors.textTertiary} style={{ marginRight: 4 }} /> - + {timeString} diff --git a/MobileApp/src/components/EpisodeCard.tsx b/MobileApp/src/components/EpisodeCard.tsx index ae9a75690e..f7f7c07b79 100644 --- a/MobileApp/src/components/EpisodeCard.tsx +++ b/MobileApp/src/components/EpisodeCard.tsx @@ -62,241 +62,241 @@ export default function EpisodeCard( return ( - { - return { - opacity: pressed ? 0.7 : muted ? 0.5 : 1, - }; - }} - onPress={onPress} - accessibilityRole="button" - accessibilityLabel={`${type === "incident" ? "Incident" : "Alert"} episode ${episode.episodeNumberWithPrefix || episode.episodeNumber}, ${episode.title}. State: ${state?.name ?? "unknown"}. Severity: ${severity?.name ?? "unknown"}.`} - > - { + return { + opacity: pressed ? 0.7 : muted ? 0.5 : 1, + }; }} + onPress={onPress} + accessibilityRole="button" + accessibilityLabel={`${type === "incident" ? "Incident" : "Alert"} episode ${episode.episodeNumberWithPrefix || episode.episodeNumber}, ${episode.title}. State: ${state?.name ?? "unknown"}. Severity: ${severity?.name ?? "unknown"}.`} > - + > + /> + - {projectName ? : null} + {projectName ? : null} + + + + {type === "incident" ? "INCIDENT EPISODE" : "ALERT EPISODE"} + + + + + {episode.episodeNumberWithPrefix || + `#${episode.episodeNumber}`} + + + + - {type === "incident" ? "INCIDENT EPISODE" : "ALERT EPISODE"} - - - - - {episode.episodeNumberWithPrefix || - `#${episode.episodeNumber}`} + {timeString} - - - - {timeString} - - - - - - {episode.title} - - - - - - {state ? ( - + {episode.title} + + + + + + {state ? ( - - {state.name} - - - ) : null} + + + {state.name} + + + ) : null} - {severity ? ( - - - {severity.name} - - - ) : null} + + {severity.name} + + + ) : null} - {childCount > 0 ? ( - - 0 ? ( + - {childCount} {type === "incident" ? "incident" : "alert"} - {childCount !== 1 ? "s" : ""} - - - ) : null} + + {childCount} {type === "incident" ? "incident" : "alert"} + {childCount !== 1 ? "s" : ""} + + + ) : null} + - - + ); } diff --git a/MobileApp/src/components/FeedTimeline.tsx b/MobileApp/src/components/FeedTimeline.tsx index 11b97d2225..0abbd6531b 100644 --- a/MobileApp/src/components/FeedTimeline.tsx +++ b/MobileApp/src/components/FeedTimeline.tsx @@ -69,7 +69,11 @@ export default function FeedTimeline({ ) : null} {timeString} diff --git a/MobileApp/src/components/IncidentCard.tsx b/MobileApp/src/components/IncidentCard.tsx index a83bee1135..8a120941f3 100644 --- a/MobileApp/src/components/IncidentCard.tsx +++ b/MobileApp/src/components/IncidentCard.tsx @@ -147,9 +147,7 @@ export default function IncidentCard({ color={theme.colors.textTertiary} style={{ marginRight: 4 }} /> - + {timeString} diff --git a/MobileApp/src/screens/AlertDetailScreen.tsx b/MobileApp/src/screens/AlertDetailScreen.tsx index ff527c7a7e..40e5689c79 100644 --- a/MobileApp/src/screens/AlertDetailScreen.tsx +++ b/MobileApp/src/screens/AlertDetailScreen.tsx @@ -157,9 +157,7 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { backgroundColor: theme.colors.backgroundPrimary, }} > - + Alert not found. diff --git a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx index d8dc0d7dbf..9488460c0d 100644 --- a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx +++ b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx @@ -165,9 +165,7 @@ export default function AlertEpisodeDetailScreen({ backgroundColor: theme.colors.backgroundPrimary, }} > - + Episode not found. diff --git a/MobileApp/src/screens/AlertsScreen.tsx b/MobileApp/src/screens/AlertsScreen.tsx index 70c938bd5c..7f6d10bd62 100644 --- a/MobileApp/src/screens/AlertsScreen.tsx +++ b/MobileApp/src/screens/AlertsScreen.tsx @@ -60,7 +60,13 @@ function SectionHeader({ const { theme } = useTheme(); return ( + Use {biometricType.toLowerCase()} to unlock diff --git a/MobileApp/src/screens/HomeScreen.tsx b/MobileApp/src/screens/HomeScreen.tsx index 35e8c129d4..36092d1d00 100644 --- a/MobileApp/src/screens/HomeScreen.tsx +++ b/MobileApp/src/screens/HomeScreen.tsx @@ -89,7 +89,14 @@ function StatCard({ elevation: 6, }} > - + } > - + @@ -269,7 +288,9 @@ export default function HomeScreen(): React.JSX.Element { /> } > - + - + Total active items ({ - opacity: pressed ? 0.8 : 1, - })} + style={({ pressed }: { pressed: boolean }) => { + return { + opacity: pressed ? 0.8 : 1, + }; + }} accessibilityRole="button" accessibilityLabel="View my on-call assignments" > @@ -403,78 +424,98 @@ export default function HomeScreen(): React.JSX.Element { borderColor: theme.colors.borderGlass, }} > - + - - + + + + + + + My On-Call Policies + + + {onCallLoading + ? "Loading assignments..." + : totalAssignments > 0 + ? `${totalAssignments} active ${totalAssignments === 1 ? "assignment" : "assignments"} across ${onCallProjects.length} ${onCallProjects.length === 1 ? "project" : "projects"}` + : "You are not currently on-call"} + + + + + + + {onCallLoading ? "--" : totalAssignments} + - - - My On-Call Policies - - - {onCallLoading - ? "Loading assignments..." - : totalAssignments > 0 - ? `${totalAssignments} active ${totalAssignments === 1 ? "assignment" : "assignments"} across ${onCallProjects.length} ${onCallProjects.length === 1 ? "project" : "projects"}` - : "You are not currently on-call"} - - - - - - {onCallLoading ? "--" : totalAssignments} - - - - diff --git a/MobileApp/src/screens/IncidentDetailScreen.tsx b/MobileApp/src/screens/IncidentDetailScreen.tsx index 26c2081e87..5f0b6706d0 100644 --- a/MobileApp/src/screens/IncidentDetailScreen.tsx +++ b/MobileApp/src/screens/IncidentDetailScreen.tsx @@ -169,9 +169,7 @@ export default function IncidentDetailScreen({ backgroundColor: theme.colors.backgroundPrimary, }} > - + Incident not found. diff --git a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx index b5b4a35aaf..9ed151971c 100644 --- a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx +++ b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx @@ -172,9 +172,7 @@ export default function IncidentEpisodeDetailScreen({ backgroundColor: theme.colors.backgroundPrimary, }} > - + Episode not found. diff --git a/MobileApp/src/screens/IncidentsScreen.tsx b/MobileApp/src/screens/IncidentsScreen.tsx index bca775a768..76f75c6304 100644 --- a/MobileApp/src/screens/IncidentsScreen.tsx +++ b/MobileApp/src/screens/IncidentsScreen.tsx @@ -63,7 +63,13 @@ function SectionHeader({ const { theme } = useTheme(); return ( + - - + Live duty assignments @@ -213,7 +217,12 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { {summaryText} @@ -262,7 +271,13 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { borderTopRightRadius: 23, }} > - + {projectData.assignments.length} active @@ -329,13 +348,18 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { projectData.assignments.length - 1 ? { borderBottomWidth: 1, - borderBottomColor: - theme.colors.borderSubtle, + borderBottomColor: theme.colors.borderSubtle, } : {}), }} > - + - + - + {rightElement ?? (value ? ( - + {value} ) : onPress ? ( @@ -224,7 +216,14 @@ export default function SettingsScreen(): React.JSX.Element { - + {serverUrl || "oneuptime.com"} @@ -437,8 +440,12 @@ export default function SettingsScreen(): React.JSX.Element { }} /> - - + + Thank you for supporting open source software. Built and maintained by contributors around the world. @@ -499,9 +517,7 @@ export default function SettingsScreen(): React.JSX.Element { borderColor: theme.colors.borderSubtle, }} > - + Licensed under Apache 2.0 diff --git a/MobileApp/src/screens/auth/LoginScreen.tsx b/MobileApp/src/screens/auth/LoginScreen.tsx index e72ad61a50..ede87e8d94 100644 --- a/MobileApp/src/screens/auth/LoginScreen.tsx +++ b/MobileApp/src/screens/auth/LoginScreen.tsx @@ -81,7 +81,9 @@ export default function LoginScreen(): React.JSX.Element { contentContainerStyle={{ flexGrow: 1 }} keyboardShouldPersistTaps="handled" > - + Sign in to continue @@ -134,7 +140,12 @@ export default function LoginScreen(): React.JSX.Element { Email @@ -163,7 +174,11 @@ export default function LoginScreen(): React.JSX.Element { style={{ marginRight: 10 }} /> { setEmail(text); @@ -186,7 +201,13 @@ export default function LoginScreen(): React.JSX.Element { Password @@ -215,7 +236,11 @@ export default function LoginScreen(): React.JSX.Element { style={{ marginRight: 10 }} /> { setPassword(text); @@ -237,7 +262,13 @@ export default function LoginScreen(): React.JSX.Element { {error ? ( - + {error} diff --git a/MobileApp/src/screens/auth/ServerUrlScreen.tsx b/MobileApp/src/screens/auth/ServerUrlScreen.tsx index 266b475845..57e5b76d2b 100644 --- a/MobileApp/src/screens/auth/ServerUrlScreen.tsx +++ b/MobileApp/src/screens/auth/ServerUrlScreen.tsx @@ -72,7 +72,9 @@ export default function ServerUrlScreen(): React.JSX.Element { contentContainerStyle={{ flexGrow: 1 }} keyboardShouldPersistTaps="handled" > - + Connect to your OneUptime instance @@ -107,7 +115,12 @@ export default function ServerUrlScreen(): React.JSX.Element { Server URL @@ -138,7 +151,11 @@ export default function ServerUrlScreen(): React.JSX.Element { style={{ marginRight: 10 }} /> { setUrl(text); @@ -161,7 +178,13 @@ export default function ServerUrlScreen(): React.JSX.Element { {error ? ( - + {error} @@ -187,7 +214,13 @@ export default function ServerUrlScreen(): React.JSX.Element { Self-hosting? Enter your OneUptime server URL above. From 14988c438adf180e0fdd1b6af8582747d004d289 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 07:48:31 +0000 Subject: [PATCH 22/44] refactor: update app.json slug and add permissions for biometric authentication --- MobileApp/app.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/MobileApp/app.json b/MobileApp/app.json index 0c2b4c6318..75fe56fc21 100644 --- a/MobileApp/app.json +++ b/MobileApp/app.json @@ -1,7 +1,7 @@ { "expo": { "name": "OneUptime", - "slug": "oneuptime", + "slug": "oneuptime-on-call", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", @@ -29,7 +29,11 @@ "backgroundColor": "#0D1117" }, "edgeToEdgeEnabled": false, - "package": "com.oneuptime.oncall" + "package": "com.oneuptime.oncall", + "permissions": [ + "android.permission.USE_BIOMETRIC", + "android.permission.USE_FINGERPRINT" + ] }, "plugins": [ [ @@ -43,6 +47,12 @@ ], "web": { "favicon": "./assets/favicon.png" - } + }, + "extra": { + "eas": { + "projectId": "d9f87edc-1c3e-466f-b032-1ced7621aa8a" + } + }, + "owner": "oneuptime" } } From 92e247d1689c28d5a85689363e7907c33e9f502d Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 07:57:47 +0000 Subject: [PATCH 23/44] refactor: update logo size and styling in LoginScreen and ServerUrlScreen for improved visual consistency --- MobileApp/src/screens/auth/LoginScreen.tsx | 12 +++++------- MobileApp/src/screens/auth/ServerUrlScreen.tsx | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/MobileApp/src/screens/auth/LoginScreen.tsx b/MobileApp/src/screens/auth/LoginScreen.tsx index ede87e8d94..f58bd8dd2b 100644 --- a/MobileApp/src/screens/auth/LoginScreen.tsx +++ b/MobileApp/src/screens/auth/LoginScreen.tsx @@ -87,16 +87,14 @@ export default function LoginScreen(): React.JSX.Element { - + - + Date: Wed, 18 Feb 2026 08:39:29 +0000 Subject: [PATCH 24/44] refactor: enhance push notification registration with improved error handling and retry logic --- Common/Server/API/UserPushAPI.ts | 9 +++-- MobileApp/src/api/pushDevice.ts | 22 ++++++++++- MobileApp/src/hooks/usePushNotifications.ts | 42 +++++++++++++++++++-- MobileApp/src/notifications/setup.ts | 24 ++++++++++-- 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/Common/Server/API/UserPushAPI.ts b/Common/Server/API/UserPushAPI.ts index 4f85a4a403..15e4d423a7 100644 --- a/Common/Server/API/UserPushAPI.ts +++ b/Common/Server/API/UserPushAPI.ts @@ -78,9 +78,12 @@ export default class UserPushAPI extends BaseAPI< }); if (existingDevice) { - // Mark as used and return a specific response indicating device was already registered - throw new BadDataException( - "This device is already registered for push notifications", + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "This device is already registered for push notifications", + ), ); } diff --git a/MobileApp/src/api/pushDevice.ts b/MobileApp/src/api/pushDevice.ts index fc5fac8d19..9ff0f269e7 100644 --- a/MobileApp/src/api/pushDevice.ts +++ b/MobileApp/src/api/pushDevice.ts @@ -20,11 +20,29 @@ export async function registerPushDevice(params: { deviceName: Device.modelName || "Unknown Device", projectId: params.projectId, }); - } catch (error: any) { + console.info( + `[PushNotifications] Device registered successfully for project ${params.projectId}`, + ); + } catch (error: unknown) { + const status: number | undefined = ( + error as { response?: { status?: number } } + )?.response?.status; + const message: string = + (error as { response?: { data?: { message?: string } } })?.response?.data + ?.message || String(error); + // Treat "already registered" as success - if (error?.response?.status === 400) { + if (status === 400 && message.includes("already registered")) { + console.info( + `[PushNotifications] Device already registered for project ${params.projectId}`, + ); return; } + + // Log and re-throw other errors + console.error( + `[PushNotifications] Registration failed (status=${status}): ${message}`, + ); throw error; } } diff --git a/MobileApp/src/hooks/usePushNotifications.ts b/MobileApp/src/hooks/usePushNotifications.ts index 259a357cc7..1e4a46f728 100644 --- a/MobileApp/src/hooks/usePushNotifications.ts +++ b/MobileApp/src/hooks/usePushNotifications.ts @@ -16,6 +16,9 @@ import { useAuth } from "./useAuth"; import { useProject } from "./useProject"; import { PUSH_TOKEN_KEY } from "./pushTokenUtils"; +const RETRY_DELAY_MS: number = 5000; +const MAX_RETRIES: number = 3; + export function usePushNotifications(navigationRef: unknown): void { const { isAuthenticated }: { isAuthenticated: boolean } = useAuth(); const { projectList }: { projectList: Array<{ _id: string }> } = useProject(); @@ -46,8 +49,31 @@ export function usePushNotifications(navigationRef: unknown): void { let cancelled: boolean = false; const register: () => Promise = async (): Promise => { - const token: string | null = await requestPermissionsAndGetToken(); + let token: string | null = null; + let attempt: number = 0; + + // Retry obtaining the push token + while (!token && attempt < MAX_RETRIES && !cancelled) { + token = await requestPermissionsAndGetToken(); + if (!token && !cancelled) { + attempt++; + if (attempt < MAX_RETRIES) { + console.warn( + `[PushNotifications] Push token not available, retrying in ${RETRY_DELAY_MS}ms (attempt ${attempt}/${MAX_RETRIES})`, + ); + await new Promise((resolve: () => void): void => { + setTimeout(resolve, RETRY_DELAY_MS); + }); + } + } + } + if (!token || cancelled) { + if (!token) { + console.warn( + "[PushNotifications] Could not obtain push token after all retries — device will not be registered", + ); + } return; } @@ -63,13 +89,21 @@ export function usePushNotifications(navigationRef: unknown): void { deviceToken: token, projectId: project._id, }); - } catch { - // Continue registering with other projects + } catch (error: unknown) { + console.warn( + `[PushNotifications] Failed to register device for project ${project._id}:`, + error, + ); } } }; - register(); + register().catch((error: unknown): void => { + console.error( + "[PushNotifications] Unexpected error during push registration:", + error, + ); + }); return (): void => { cancelled = true; diff --git a/MobileApp/src/notifications/setup.ts b/MobileApp/src/notifications/setup.ts index b2b0d2bf81..b5a2e5b65a 100644 --- a/MobileApp/src/notifications/setup.ts +++ b/MobileApp/src/notifications/setup.ts @@ -80,6 +80,9 @@ export async function setupNotificationCategories(): Promise { export async function requestPermissionsAndGetToken(): Promise { if (!Device.isDevice) { + console.warn( + "[PushNotifications] Not a physical device — skipping push token registration", + ); return null; } @@ -92,6 +95,10 @@ export async function requestPermissionsAndGetToken(): Promise { } if (finalStatus !== "granted") { + console.warn( + "[PushNotifications] Push notification permission not granted:", + finalStatus, + ); return null; } @@ -100,12 +107,21 @@ export async function requestPermissionsAndGetToken(): Promise { Constants.easConfig?.projectId; if (!projectId) { + console.warn( + "[PushNotifications] EAS project ID not found — cannot register for push notifications", + ); return null; } - const tokenData: ExpoPushToken = await Notifications.getExpoPushTokenAsync({ - projectId, - }); + try { + const tokenData: ExpoPushToken = + await Notifications.getExpoPushTokenAsync({ + projectId, + }); - return tokenData.data; + return tokenData.data; + } catch (error: unknown) { + console.error("[PushNotifications] Failed to get push token:", error); + return null; + } } From 2dc0dc4c960e9e38ff144c6f59f6d7c698dbea3e Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 08:54:10 +0000 Subject: [PATCH 25/44] refactor: add google services configuration for Firebase integration --- MobileApp/app.json | 1 + MobileApp/google-services.json | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 MobileApp/google-services.json diff --git a/MobileApp/app.json b/MobileApp/app.json index 75fe56fc21..d9f35cf5ca 100644 --- a/MobileApp/app.json +++ b/MobileApp/app.json @@ -30,6 +30,7 @@ }, "edgeToEdgeEnabled": false, "package": "com.oneuptime.oncall", + "googleServicesFile": "./google-services.json", "permissions": [ "android.permission.USE_BIOMETRIC", "android.permission.USE_FINGERPRINT" diff --git a/MobileApp/google-services.json b/MobileApp/google-services.json new file mode 100644 index 0000000000..c1ab824b3c --- /dev/null +++ b/MobileApp/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "877286215861", + "project_id": "oneuptime-on-call", + "storage_bucket": "oneuptime-on-call.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:877286215861:android:f783c4aa68be6cba54eefc", + "android_client_info": { + "package_name": "com.oneuptime.oncall" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyBVUYdIldBliGtG0mIGa-nNs6eUJkJmKOM" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} From 76cfa7186e6790a2271a9584c7fddbefebdca773 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 09:00:13 +0000 Subject: [PATCH 26/44] refactor: add eas.json configuration for build and submission settings --- MobileApp/eas.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 MobileApp/eas.json diff --git a/MobileApp/eas.json b/MobileApp/eas.json new file mode 100644 index 0000000000..bbabbc87ef --- /dev/null +++ b/MobileApp/eas.json @@ -0,0 +1,18 @@ +{ + "cli": { + "version": ">= 3.0.0" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal" + }, + "preview": { + "distribution": "internal" + }, + "production": {} + }, + "submit": { + "production": {} + } +} From 3cf7c7d1aee5aa0a432f0988d08fe55b8b4614b7 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 09:56:10 +0000 Subject: [PATCH 27/44] refactor: implement push notification relay and enhance Expo integration --- App/FeatureSet/Notification/API/PushRelay.ts | 137 ++++++++++++++++++ App/FeatureSet/Notification/Index.ts | 2 + Common/Server/API/UserPushAPI.ts | 46 +++--- Common/Server/EnvironmentConfig.ts | 7 + .../Services/PushNotificationService.ts | 93 ++++++++++-- .../Public/oneuptime/templates/_helpers.tpl | 6 + HelmChart/Public/oneuptime/values.schema.json | 18 +++ HelmChart/Public/oneuptime/values.yaml | 9 ++ config.example.env | 8 + docker-compose.base.yml | 3 + 10 files changed, 296 insertions(+), 33 deletions(-) create mode 100644 App/FeatureSet/Notification/API/PushRelay.ts diff --git a/App/FeatureSet/Notification/API/PushRelay.ts b/App/FeatureSet/Notification/API/PushRelay.ts new file mode 100644 index 0000000000..d63e2afbf8 --- /dev/null +++ b/App/FeatureSet/Notification/API/PushRelay.ts @@ -0,0 +1,137 @@ +import Express, { + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "Common/Server/Utils/Express"; +import Response from "Common/Server/Utils/Response"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import { Expo, ExpoPushMessage, ExpoPushTicket } from "expo-server-sdk"; +import { ExpoAccessToken } from "Common/Server/EnvironmentConfig"; +import logger from "Common/Server/Utils/Logger"; + +const router: ExpressRouter = Express.getRouter(); + +// Simple in-memory rate limiter by IP +const rateLimitMap: Map = + new Map(); +const RATE_LIMIT_WINDOW_MS: number = 60 * 1000; // 1 minute +const RATE_LIMIT_MAX_REQUESTS: number = 60; // 60 requests per minute per IP + +function isRateLimited(ip: string): boolean { + const now: number = Date.now(); + const entry: { count: number; resetTime: number } | undefined = + rateLimitMap.get(ip); + + if (!entry || now > entry.resetTime) { + rateLimitMap.set(ip, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS }); + return false; + } + + entry.count++; + + return entry.count > RATE_LIMIT_MAX_REQUESTS; +} + +// Clean up stale rate limit entries every 5 minutes +setInterval(() => { + const now: number = Date.now(); + for (const [ip, entry] of rateLimitMap.entries()) { + if (now > entry.resetTime) { + rateLimitMap.delete(ip); + } + } +}, 5 * 60 * 1000); + +// Expo client for sending push notifications (only available when EXPO_ACCESS_TOKEN is set) +const expoClient: Expo | null = ExpoAccessToken + ? new Expo({ accessToken: ExpoAccessToken }) + : null; + +router.post( + "/send", + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const clientIp: string = + (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim() || + req.socket.remoteAddress || + "unknown"; + + if (isRateLimited(clientIp)) { + res.status(429).json({ + message: "Rate limit exceeded. Please try again later.", + }); + return; + } + + if (!expoClient) { + throw new BadDataException( + "Push relay is not configured. EXPO_ACCESS_TOKEN is not set on this server.", + ); + } + + const body: JSONObject = req.body as JSONObject; + + const to: string | undefined = body["to"] as string | undefined; + + if (!to || !Expo.isExpoPushToken(to)) { + throw new BadDataException( + "Invalid or missing push token. Must be a valid Expo push token.", + ); + } + + const title: string | undefined = body["title"] as string | undefined; + const messageBody: string | undefined = body["body"] as + | string + | undefined; + + if (!title && !messageBody) { + throw new BadDataException( + "At least one of 'title' or 'body' must be provided.", + ); + } + + const expoPushMessage: ExpoPushMessage = { + to: to, + title: title, + body: messageBody, + data: (body["data"] as { [key: string]: string }) || {}, + sound: (body["sound"] as "default" | null) || "default", + priority: (body["priority"] as "default" | "normal" | "high") || "high", + channelId: (body["channelId"] as string) || "default", + }; + + const tickets: ExpoPushTicket[] = + await expoClient.sendPushNotificationsAsync([expoPushMessage]); + + const ticket: ExpoPushTicket | undefined = tickets[0]; + + if (ticket && ticket.status === "error") { + const errorTicket: ExpoPushTicket & { + message?: string; + details?: { error?: string }; + } = ticket as ExpoPushTicket & { + message?: string; + details?: { error?: string }; + }; + + logger.error( + `Push relay: Expo push notification error: ${errorTicket.message}`, + ); + + throw new BadDataException( + `Failed to send push notification: ${errorTicket.message}`, + ); + } + + logger.info(`Push relay: notification sent successfully to ${to}`); + + return Response.sendJsonObjectResponse(req, res, { success: true }); + } catch (err) { + return next(err); + } + }, +); + +export default router; diff --git a/App/FeatureSet/Notification/Index.ts b/App/FeatureSet/Notification/Index.ts index a13bc135fc..c78ebdaebb 100644 --- a/App/FeatureSet/Notification/Index.ts +++ b/App/FeatureSet/Notification/Index.ts @@ -4,6 +4,7 @@ import MailAPI from "./API/Mail"; import SmsAPI from "./API/SMS"; import WhatsAppAPI from "./API/WhatsApp"; import PushNotificationAPI from "./API/PushNotification"; +import PushRelayAPI from "./API/PushRelay"; import SMTPConfigAPI from "./API/SMTPConfig"; import PhoneNumberAPI from "./API/PhoneNumber"; import IncomingCallAPI from "./API/IncomingCall"; @@ -21,6 +22,7 @@ const NotificationFeatureSet: FeatureSet = { app.use([`/${APP_NAME}/sms`, "/sms"], SmsAPI); app.use([`/${APP_NAME}/whatsapp`, "/whatsapp"], WhatsAppAPI); app.use([`/${APP_NAME}/push`, "/push"], PushNotificationAPI); + app.use([`/${APP_NAME}/push-relay`, "/push-relay"], PushRelayAPI); app.use([`/${APP_NAME}/call`, "/call"], CallAPI); app.use([`/${APP_NAME}/smtp-config`, "/smtp-config"], SMTPConfigAPI); app.use([`/${APP_NAME}/phone-number`, "/phone-number"], PhoneNumberAPI); diff --git a/Common/Server/API/UserPushAPI.ts b/Common/Server/API/UserPushAPI.ts index 15e4d423a7..d2adbfad1c 100644 --- a/Common/Server/API/UserPushAPI.ts +++ b/Common/Server/API/UserPushAPI.ts @@ -13,11 +13,23 @@ import { import Response from "../Utils/Response"; import BaseAPI from "./BaseAPI"; import BadDataException from "../../Types/Exception/BadDataException"; +import NotAuthenticatedException from "../../Types/Exception/NotAuthenticatedException"; import ObjectID from "../../Types/ObjectID"; import PushDeviceType from "../../Types/PushNotification/PushDeviceType"; import UserPush from "../../Models/DatabaseModels/UserPush"; import PushNotificationMessage from "../../Types/PushNotification/PushNotificationMessage"; +function getAuthenticatedUserId(req: ExpressRequest): ObjectID { + const userId: ObjectID | undefined = (req as OneUptimeRequest) + .userAuthorization?.userId; + if (!userId) { + throw new NotAuthenticatedException( + "You must be logged in to perform this action.", + ); + } + return userId; +} + export default class UserPushAPI extends BaseAPI< UserPush, UserPushServiceType @@ -32,6 +44,8 @@ export default class UserPushAPI extends BaseAPI< try { req = req as OneUptimeRequest; + const userId: ObjectID = getAuthenticatedUserId(req); + if (!req.body.deviceToken) { return Response.sendErrorResponse( req, @@ -65,7 +79,7 @@ export default class UserPushAPI extends BaseAPI< // Check if device is already registered const existingDevice: UserPush | null = await this.service.findOneBy({ query: { - userId: (req as OneUptimeRequest).userAuthorization!.userId!, + userId: userId, projectId: new ObjectID(req.body.projectId), deviceToken: req.body.deviceToken, }, @@ -89,9 +103,7 @@ export default class UserPushAPI extends BaseAPI< // Create new device registration const userPush: UserPush = new UserPush(); - userPush.userId = ( - req as OneUptimeRequest - ).userAuthorization!.userId!; + userPush.userId = userId; userPush.projectId = new ObjectID(req.body.projectId); userPush.deviceToken = req.body.deviceToken; userPush.deviceType = req.body.deviceType; @@ -122,6 +134,8 @@ export default class UserPushAPI extends BaseAPI< try { req = req as OneUptimeRequest; + const userId: ObjectID = getAuthenticatedUserId(req); + if (!req.body.deviceToken) { return Response.sendErrorResponse( req, @@ -130,9 +144,6 @@ export default class UserPushAPI extends BaseAPI< ); } - const userId: ObjectID = (req as OneUptimeRequest).userAuthorization! - .userId!; - await this.service.deleteBy({ query: { userId: userId, @@ -162,6 +173,8 @@ export default class UserPushAPI extends BaseAPI< try { req = req as OneUptimeRequest; + const userId: ObjectID = getAuthenticatedUserId(req); + if (!req.params["deviceId"]) { return Response.sendErrorResponse( req, @@ -195,10 +208,7 @@ export default class UserPushAPI extends BaseAPI< } // Check if the device belongs to the current user - if ( - device.userId?.toString() !== - (req as OneUptimeRequest).userAuthorization!.userId!.toString() - ) { + if (device.userId?.toString() !== userId.toString()) { return Response.sendErrorResponse( req, res, @@ -267,6 +277,8 @@ export default class UserPushAPI extends BaseAPI< try { req = req as OneUptimeRequest; + const userId: ObjectID = getAuthenticatedUserId(req); + if (!req.params["deviceId"]) { return Response.sendErrorResponse( req, @@ -294,10 +306,7 @@ export default class UserPushAPI extends BaseAPI< } // Check if the device belongs to the current user - if ( - device.userId?.toString() !== - (req as OneUptimeRequest).userAuthorization!.userId!.toString() - ) { + if (device.userId?.toString() !== userId.toString()) { return Response.sendErrorResponse( req, res, @@ -321,6 +330,8 @@ export default class UserPushAPI extends BaseAPI< try { req = req as OneUptimeRequest; + const userId: ObjectID = getAuthenticatedUserId(req); + if (!req.params["deviceId"]) { return Response.sendErrorResponse( req, @@ -348,10 +359,7 @@ export default class UserPushAPI extends BaseAPI< } // Check if the device belongs to the current user - if ( - device.userId?.toString() !== - (req as OneUptimeRequest).userAuthorization!.userId!.toString() - ) { + if (device.userId?.toString() !== userId.toString()) { return Response.sendErrorResponse( req, res, diff --git a/Common/Server/EnvironmentConfig.ts b/Common/Server/EnvironmentConfig.ts index 91dc48b530..ef5f62fd5b 100644 --- a/Common/Server/EnvironmentConfig.ts +++ b/Common/Server/EnvironmentConfig.ts @@ -529,6 +529,13 @@ export const VapidPrivateKey: string | undefined = export const VapidSubject: string = process.env["VAPID_SUBJECT"] || "mailto:support@oneuptime.com"; +export const ExpoAccessToken: string | undefined = + process.env["EXPO_ACCESS_TOKEN"] || undefined; + +export const PushNotificationRelayUrl: string = + process.env["PUSH_NOTIFICATION_RELAY_URL"] || + "https://oneuptime.com/api/notification/push-relay/send"; + export const EnterpriseLicenseValidationUrl: URL = URL.fromString( "https://oneuptime.com/api/enterprise-license/validate", ); diff --git a/Common/Server/Services/PushNotificationService.ts b/Common/Server/Services/PushNotificationService.ts index e1170e3ce0..5671d16110 100644 --- a/Common/Server/Services/PushNotificationService.ts +++ b/Common/Server/Services/PushNotificationService.ts @@ -10,9 +10,16 @@ import { VapidPublicKey, VapidPrivateKey, VapidSubject, + ExpoAccessToken, + PushNotificationRelayUrl, } from "../EnvironmentConfig"; import webpush from "web-push"; import { Expo, ExpoPushMessage, ExpoPushTicket } from "expo-server-sdk"; +import API from "../../Utils/API"; +import URL from "../../Types/API/URL"; +import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse"; +import HTTPResponse from "../../Types/API/HTTPResponse"; +import { JSONObject } from "../../Types/JSON"; import PushNotificationUtil from "../Utils/PushNotificationUtil"; import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax"; import UserPush from "../../Models/DatabaseModels/UserPush"; @@ -43,7 +50,9 @@ export interface PushNotificationOptions { export default class PushNotificationService { public static isWebPushInitialized = false; - private static expoClient: Expo = new Expo(); + private static expoClient: Expo = new Expo( + ExpoAccessToken ? { accessToken: ExpoAccessToken } : undefined, + ); public static initializeWebPush(): void { if (this.isWebPushInitialized) { @@ -340,20 +349,33 @@ export default class PushNotificationService { ); } + const dataPayload: { [key: string]: string } = {}; + if (message.data) { + for (const key of Object.keys(message.data)) { + dataPayload[key] = String(message.data[key]); + } + } + if (message.url || message.clickAction) { + dataPayload["url"] = message.url || message.clickAction || ""; + } + + const channelId: string = + deviceType === PushDeviceType.Android ? "oncall_high" : "default"; + + // If EXPO_ACCESS_TOKEN is not set, relay through the push notification gateway + if (!ExpoAccessToken) { + await this.sendViaRelay( + expoPushToken, + message, + dataPayload, + channelId, + deviceType, + ); + return; + } + + // Send directly via Expo SDK try { - const dataPayload: { [key: string]: string } = {}; - if (message.data) { - for (const key of Object.keys(message.data)) { - dataPayload[key] = String(message.data[key]); - } - } - if (message.url || message.clickAction) { - dataPayload["url"] = message.url || message.clickAction || ""; - } - - const channelId: string = - deviceType === PushDeviceType.Android ? "oncall_high" : "default"; - const expoPushMessage: ExpoPushMessage = { to: expoPushToken, title: message.title, @@ -403,6 +425,49 @@ export default class PushNotificationService { } } + private static async sendViaRelay( + expoPushToken: string, + message: PushNotificationMessage, + dataPayload: { [key: string]: string }, + channelId: string, + deviceType: PushDeviceType, + ): Promise { + logger.info( + `Sending ${deviceType} push notification via relay: ${PushNotificationRelayUrl}`, + ); + + try { + const response: HTTPErrorResponse | HTTPResponse = + await API.post({ + url: URL.fromString(PushNotificationRelayUrl), + data: { + to: expoPushToken, + title: message.title || "", + body: message.body || "", + data: dataPayload, + sound: "default", + priority: "high", + channelId: channelId, + }, + }); + + if (response instanceof HTTPErrorResponse) { + throw new Error( + `Push relay error: ${JSON.stringify(response.jsonData)}`, + ); + } + + logger.info( + `Push notification sent via relay successfully to ${deviceType} device`, + ); + } catch (error: any) { + logger.error( + `Failed to send push notification via relay to ${deviceType} device: ${error.message}`, + ); + throw error; + } + } + public static async sendPushNotificationToUser( userId: ObjectID, projectId: ObjectID, diff --git a/HelmChart/Public/oneuptime/templates/_helpers.tpl b/HelmChart/Public/oneuptime/templates/_helpers.tpl index 86c42e2fc4..e6c71e49e0 100644 --- a/HelmChart/Public/oneuptime/templates/_helpers.tpl +++ b/HelmChart/Public/oneuptime/templates/_helpers.tpl @@ -203,6 +203,12 @@ Usage: - name: VAPID_PRIVATE_KEY value: {{ $.Values.vapid.privateKey }} +- name: EXPO_ACCESS_TOKEN + value: {{ default "" $.Values.expo.accessToken | quote }} + +- name: PUSH_NOTIFICATION_RELAY_URL + value: {{ default "https://oneuptime.com/api/notification/push-relay/send" $.Values.pushNotification.relayUrl | quote }} + - name: SLACK_APP_CLIENT_SECRET value: {{ $.Values.slackApp.clientSecret }} diff --git a/HelmChart/Public/oneuptime/values.schema.json b/HelmChart/Public/oneuptime/values.schema.json index 9e0858dd9c..fcc40fb6e7 100644 --- a/HelmChart/Public/oneuptime/values.schema.json +++ b/HelmChart/Public/oneuptime/values.schema.json @@ -727,6 +727,24 @@ }, "additionalProperties": false }, + "expo": { + "type": "object", + "properties": { + "accessToken": { + "type": ["string", "null"] + } + }, + "additionalProperties": false + }, + "pushNotification": { + "type": "object", + "properties": { + "relayUrl": { + "type": ["string", "null"] + } + }, + "additionalProperties": false + }, "incidents": { "type": "object", "properties": { diff --git a/HelmChart/Public/oneuptime/values.yaml b/HelmChart/Public/oneuptime/values.yaml index 225880a91f..26de1ad81b 100644 --- a/HelmChart/Public/oneuptime/values.yaml +++ b/HelmChart/Public/oneuptime/values.yaml @@ -287,6 +287,15 @@ vapid: privateKey: subject: mailto:support@oneuptime.com +# Expo access token for sending mobile push notifications directly via Expo SDK. +# If not set, notifications are relayed through the push notification relay URL. +expo: + accessToken: + +# Push notification relay URL for self-hosted instances without Expo credentials +pushNotification: + relayUrl: https://oneuptime.com/api/notification/push-relay/send + incidents: disableAutomaticCreation: false diff --git a/config.example.env b/config.example.env index bf44916dd5..b7bee145e7 100644 --- a/config.example.env +++ b/config.example.env @@ -297,6 +297,14 @@ VAPID_PUBLIC_KEY= VAPID_PRIVATE_KEY= VAPID_SUBJECT=mailto:support@oneuptime.com +# Expo access token for sending mobile push notifications directly via Expo SDK. +# If not set, push notifications are relayed through the push notification relay URL below. +EXPO_ACCESS_TOKEN= + +# Push notification relay URL for self-hosted instances without Expo credentials. +# Self-hosted servers relay push notifications through this gateway. +PUSH_NOTIFICATION_RELAY_URL=https://oneuptime.com/api/notification/push-relay/send + # LLM Environment Variables # Hugging Face Token for LLM Server to downlod models from Hugging Face diff --git a/docker-compose.base.yml b/docker-compose.base.yml index c032e280f0..b227a62c0c 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -82,6 +82,9 @@ x-common-runtime-variables: &common-runtime-variables VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY} + EXPO_ACCESS_TOKEN: ${EXPO_ACCESS_TOKEN} + PUSH_NOTIFICATION_RELAY_URL: ${PUSH_NOTIFICATION_RELAY_URL} + DATABASE_PORT: ${DATABASE_PORT} DATABASE_USERNAME: ${DATABASE_USERNAME} DATABASE_PASSWORD: ${DATABASE_PASSWORD} From e92e9f08d3ff1bf3d7713768c571e201f477a67a Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 10:31:12 +0000 Subject: [PATCH 28/44] refactor: enhance push notification handling with PushNotificationService integration --- App/FeatureSet/Notification/API/PushRelay.ts | 46 +++----------- App/package-lock.json | 58 ++++++++++++++++++ App/package.json | 1 + .../Services/PushNotificationService.ts | 60 +++++++++++++++++++ 4 files changed, 126 insertions(+), 39 deletions(-) diff --git a/App/FeatureSet/Notification/API/PushRelay.ts b/App/FeatureSet/Notification/API/PushRelay.ts index d63e2afbf8..adcd5a1bf0 100644 --- a/App/FeatureSet/Notification/API/PushRelay.ts +++ b/App/FeatureSet/Notification/API/PushRelay.ts @@ -7,9 +7,7 @@ import Express, { import Response from "Common/Server/Utils/Response"; import BadDataException from "Common/Types/Exception/BadDataException"; import { JSONObject } from "Common/Types/JSON"; -import { Expo, ExpoPushMessage, ExpoPushTicket } from "expo-server-sdk"; -import { ExpoAccessToken } from "Common/Server/EnvironmentConfig"; -import logger from "Common/Server/Utils/Logger"; +import PushNotificationService from "Common/Server/Services/PushNotificationService"; const router: ExpressRouter = Express.getRouter(); @@ -44,11 +42,6 @@ setInterval(() => { } }, 5 * 60 * 1000); -// Expo client for sending push notifications (only available when EXPO_ACCESS_TOKEN is set) -const expoClient: Expo | null = ExpoAccessToken - ? new Expo({ accessToken: ExpoAccessToken }) - : null; - router.post( "/send", async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { @@ -65,7 +58,7 @@ router.post( return; } - if (!expoClient) { + if (!PushNotificationService.hasExpoAccessToken()) { throw new BadDataException( "Push relay is not configured. EXPO_ACCESS_TOKEN is not set on this server.", ); @@ -75,7 +68,7 @@ router.post( const to: string | undefined = body["to"] as string | undefined; - if (!to || !Expo.isExpoPushToken(to)) { + if (!to || !PushNotificationService.isValidExpoPushToken(to)) { throw new BadDataException( "Invalid or missing push token. Must be a valid Expo push token.", ); @@ -92,40 +85,15 @@ router.post( ); } - const expoPushMessage: ExpoPushMessage = { + await PushNotificationService.sendRelayPushNotification({ to: to, title: title, body: messageBody, data: (body["data"] as { [key: string]: string }) || {}, - sound: (body["sound"] as "default" | null) || "default", - priority: (body["priority"] as "default" | "normal" | "high") || "high", + sound: (body["sound"] as string) || "default", + priority: (body["priority"] as string) || "high", channelId: (body["channelId"] as string) || "default", - }; - - const tickets: ExpoPushTicket[] = - await expoClient.sendPushNotificationsAsync([expoPushMessage]); - - const ticket: ExpoPushTicket | undefined = tickets[0]; - - if (ticket && ticket.status === "error") { - const errorTicket: ExpoPushTicket & { - message?: string; - details?: { error?: string }; - } = ticket as ExpoPushTicket & { - message?: string; - details?: { error?: string }; - }; - - logger.error( - `Push relay: Expo push notification error: ${errorTicket.message}`, - ); - - throw new BadDataException( - `Failed to send push notification: ${errorTicket.message}`, - ); - } - - logger.info(`Push relay: notification sent successfully to ${to}`); + }); return Response.sendJsonObjectResponse(req, res, { success: true }); } catch (err) { diff --git a/App/package-lock.json b/App/package-lock.json index f62bc411cc..81d088ee4a 100644 --- a/App/package-lock.json +++ b/App/package-lock.json @@ -12,6 +12,7 @@ "@sendgrid/mail": "^8.1.0", "Common": "file:../Common", "ejs": "^3.1.9", + "expo-server-sdk": "^5.0.0", "handlebars": "^4.7.8", "nodemailer": "^6.9.7", "ts-node": "^10.9.1", @@ -2166,6 +2167,12 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2299,6 +2306,20 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/expo-server-sdk": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-5.0.0.tgz", + "integrity": "sha512-GEp1XYLU80iS/hdRo3c2n092E8TgTXcHSuw6Lw68dSoWaAgiLPI2R+e5hp5+hGF1TtJZOi2nxtJX63+XA3iz9g==", + "license": "MIT", + "dependencies": { + "promise-limit": "^2.7.0", + "promise-retry": "^2.0.1", + "undici": "^7.2.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4167,6 +4188,25 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -4290,6 +4330,15 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -4801,6 +4850,15 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", diff --git a/App/package.json b/App/package.json index fdbcd3e32d..a2e6cf4178 100644 --- a/App/package.json +++ b/App/package.json @@ -23,6 +23,7 @@ "@sendgrid/mail": "^8.1.0", "Common": "file:../Common", "ejs": "^3.1.9", + "expo-server-sdk": "^5.0.0", "handlebars": "^4.7.8", "nodemailer": "^6.9.7", "ts-node": "^10.9.1", diff --git a/Common/Server/Services/PushNotificationService.ts b/Common/Server/Services/PushNotificationService.ts index 5671d16110..2996799a87 100644 --- a/Common/Server/Services/PushNotificationService.ts +++ b/Common/Server/Services/PushNotificationService.ts @@ -468,6 +468,66 @@ export default class PushNotificationService { } } + public static isValidExpoPushToken(token: string): boolean { + return Expo.isExpoPushToken(token); + } + + public static hasExpoAccessToken(): boolean { + return Boolean(ExpoAccessToken); + } + + public static async sendRelayPushNotification(data: { + to: string; + title?: string; + body?: string; + data?: { [key: string]: string }; + sound?: string; + priority?: string; + channelId?: string; + }): Promise { + if (!ExpoAccessToken) { + throw new Error( + "Push relay is not configured. EXPO_ACCESS_TOKEN is not set on this server.", + ); + } + + const expoPushMessage: ExpoPushMessage = { + to: data.to, + title: data.title, + body: data.body, + data: data.data || {}, + sound: (data.sound as "default" | null) || "default", + priority: + (data.priority as "default" | "normal" | "high") || "high", + channelId: data.channelId || "default", + }; + + const tickets: ExpoPushTicket[] = + await this.expoClient.sendPushNotificationsAsync([expoPushMessage]); + + const ticket: ExpoPushTicket | undefined = tickets[0]; + + if (ticket && ticket.status === "error") { + const errorTicket: ExpoPushTicket & { + message?: string; + details?: { error?: string }; + } = ticket as ExpoPushTicket & { + message?: string; + details?: { error?: string }; + }; + + logger.error( + `Push relay: Expo push notification error: ${errorTicket.message}`, + ); + + throw new Error( + `Failed to send push notification: ${errorTicket.message}`, + ); + } + + logger.info(`Push relay: notification sent successfully to ${data.to}`); + } + public static async sendPushNotificationToUser( userId: ObjectID, projectId: ObjectID, From c3c90eef030ddb2cbe7152e4d12dffd3670936ca Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 12:25:14 +0000 Subject: [PATCH 29/44] fix: ensure title and body are defaulted to empty strings in push notification --- Common/Server/Services/PushNotificationService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/Server/Services/PushNotificationService.ts b/Common/Server/Services/PushNotificationService.ts index 2996799a87..b7c03ee71e 100644 --- a/Common/Server/Services/PushNotificationService.ts +++ b/Common/Server/Services/PushNotificationService.ts @@ -493,8 +493,8 @@ export default class PushNotificationService { const expoPushMessage: ExpoPushMessage = { to: data.to, - title: data.title, - body: data.body, + title: data.title || "", + body: data.body || "", data: data.data || {}, sound: (data.sound as "default" | null) || "default", priority: From eb33daf64fc7ae939456df6ce77d481e9f25082d Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 12:57:19 +0000 Subject: [PATCH 30/44] refactor: add expo-splash-screen plugin configuration to app.json --- MobileApp/app.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MobileApp/app.json b/MobileApp/app.json index d9f35cf5ca..d7d2d4a8d8 100644 --- a/MobileApp/app.json +++ b/MobileApp/app.json @@ -37,6 +37,14 @@ ] }, "plugins": [ + [ + "expo-splash-screen", + { + "backgroundColor": "#0D1117", + "image": "./assets/splash-icon.png", + "imageWidth": 200 + } + ], [ "expo-notifications", { From 736f8bb83ce5e9b6dbd536a06647e7bbfd2d50d1 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 13:05:25 +0000 Subject: [PATCH 31/44] chore: update adaptive and regular icons in MobileApp assets --- MobileApp/assets/adaptive-icon.png | Bin 41824 -> 21139 bytes MobileApp/assets/icon.png | Bin 52610 -> 36887 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/MobileApp/assets/adaptive-icon.png b/MobileApp/assets/adaptive-icon.png index 7e6722851cae0e46d99f0898fdff0def6ce5a25d..0787b7607ddd127a8f1e631f7a22cd85182c7db3 100644 GIT binary patch literal 21139 zcmeIaXH-*5_%FN@Ku|Ae>f zj0cn=B@jA@2?PiwkPt%lo$ddwcdhsP{dDj75?GM3XZFnV{MzKzI}miy{*>9V%b}@r!-8dRuY?_0Fs>K|!l!4{hG3XOb1U@2jY_%i1BcC$5_>o;CZVq;yf@*rSm+<~8^3^~!Yb z)gP`h_g`GQzn;F#jgBjwuYX{8ka3i1%xdT$U?LdTF6ErZ*?`X{Ht0pZ7X`mSb}Zzw zphraH5!x<>Jes1>$d8kz$Ro5_2zgXOp^zVcDIt$U{{LR|-$eYkBL3SN|J8{9isgS@ zp$UqZhWK5FKQ5C7t}NCWulBp7Mf1KU%45IA@9ds-;fH94zq~u2HZ!y3 zpEz;YJWlG_hf6uhceWY4kC#1CpmDY2O+XrCy){wpXbJb13P!DR$p4JycFCAr#U(SM zKYRQ^NJ*1V;bLL*O0NIfR7y%$wq?k?31a7Yjx*XX{)3KUewI1ogIl_ zXg017Gg|L%-ZA4>I#U?HZ}?>2x&82hDBL;i0cETYQCrQWahE$<^shrux-kmUQL?ai z>qs}&nW2<#Bg)h#9^R5gV^E4$Cg^gSRYjLb0#K(YKh*TZY*DhZK=OWl z*~x3#v?coN;Z2a;5idRjRR0Gb{{Qgd*5eaVfPJt;Wc6lGDW<@6S1Y^*4)PkOFleYl z1n`fAzpU#-8#KW(7)jQ`ah@irZ`ovX`4A#u|G#(YIyX0I&c`u!M)E;@$V>h@ zQq`Y?j$E8*aqY@7cOCzHZ)I86Gj@rqIyA9mP6!X%T!65 zmU_rYm|GaL%aWcEH-DS9t#cZgEoG@@-1k& zahKmZ)grU=cd=;DEsDkP7EDtCYo;*5T{PaL1Q2O zT%mxU#zof8l!mg3YK*Kkdo+4|ZYmk;qh^bb3X(5QoM1Mr&*z$ZU?mvU+tBvoiUI_F zXaiTUs|(2MeX*7gs*U`-^Ig4N`Sa*Mrs8 zH#q)4(Gf1N8`3fmZa+#*r8FqSQOs>k%knYO0ym73W#p4?I)4#4l1GyC=IQ>UaLvq} z)Oq{1)(yIr2F6>dPgI{hc89K6qusDE=bO@Pg{dD3r{}K(B`7Hgfy)1(W(}(d^;ui* z%$~58A;pBMA4nNxMVnXZ9apHVcn~ElHX$Y8HdNVbgMG$ogg2(XAMD86-89PmG5YkJ z-oTr7e9{NOxl!yOrPzF2kpP38auwbvBy7-wdN8qYaOzwHxy3I}cy3LRn@S?}za4Ad zVWj~E=Ck(Q&Eyg}Fm&Rf%8du4n(%Z~X3AzDYDhz7!jXBX1+T#+EU`0K$UVEqI`b>J z_$4pLWaWcDZ;eVuAC?-9Q_8$2;7QkQLEjbEYCg>=j2NchU@w#xM&%p9YaNBKjpwpr z-IHU5yW}Wk)bQmWN3<2!zFE#JN4Bcv^f)P1$i|{!z&m9)#2H2hktZ8AwA!YdQ&bdEP~H-HINj0uKS!$&vfLqs(U z^q1!Rnu3p8E+Q|@2E%LIdR(> zxo3{9^jxBIAa9O&A5{>VMMI`9Cv?z6>AEoiR}OQUk7b-|NejA@K@Ck1HD-41s)?Kr z$WR|;e?4-8Y&I(;=E+~=ypsPKn^mZZ=6k{BwW8r@pRH8zTW?QP&HcO zy{@EEMRA-SFSnrPMjjTF$Rod<8C7pD&XaI1pEZNxywBK$`q`-0zZQ z%1VxvYS-Dv>o2A;;)@#3Yu|jYAvj(waq`%CG}ugpFOc7K8&Li@juN55QW~m%O7XZb z-i|?UHCoVfivqnv73LJe_Jh5JptscAj*_iUY~f{1VOHys%(p%$iFj>WF28G{I&pa{ zueAN6r%r0*(!VQ5nZ%HrPyq7r|?>f+zOwlQPKBw1MJlFbuQMvV}d0XqxmNUJ#qi#&x zcLCRUPIa)(IecL5(GSp)LIbK7XV>YTcr1V2LT4o?^vrshQ%PB2qfsnPT<<%+S6yaj;vGoyF!vUAC@ik&D$t|b{BHFHt zx8Ih_M0QKBYGCIM)_;%iy7mHQXWcS~@-@0LCbr`1oUApO&cbTK6+u4C!NK~=Cuv@p{+Q0`UUvB`Bce$BP{+j!6|eGWG29=uNd z_w&&>`Yx!D)2}tP81XyVP%E4*+KtNF3`PB`gNCBCf5VKr<@WLxlfddnKFZv#RRTZ{ zqGxSsOS-HQDRlF^7}BM1k932vF^5qcO3^jfuGALS$`h5Uh7HkA4ut&ve89P90WR43 z?_cPQx5CNE){_>=DFk=Y0iV|Q1_I61(8ehMK=5&&j~YOZOaG)i1vsw9xzILt+WpHF zE+%Am(>BA|3k?9*C*y(#-WqXe*&DTe$K!e0j`m;A@5H4O7OuNa|06RUl<2DG^{c4l z=4w{Szw?(`_14d6$Nkn`4G`o!1#7IKPbxBEtqt!Oo*=wHHQ|pjN5ONWy!?3y9nZeJ zhOMZHI{P}Ot5*s1&8~Om_C4DxoYM0F(urgjm8`Vs6$vRZbusfO8rGVo0T=;-CR{-Y zd(XORmgKetmBpuIfO7Wy+JC!P@dS~k^`c~|>c9=|w-SwH!$*h3sVzTUU6BWF!w|3A9#63k+HLOyksxVp}UXWzKx zR+NHjJH6A`Cv>Oob+fBAUN1O`oC_HndVEA!yd^q55NscBIK-}3%n9CL{b58Rk12Zb zQRY(}vF9I-!}S~4%X}PUuQf=T4_5h2wgTK@+gMR~ICnofTL_hCwDfK1aZ==)`K_Hv zmr&3*b8u5erEV_&{-HkTQ|mp9)?p3{Y0=N7s{3s-cW3@<6$F=h5r+AknMFk^_<^SdudRZ$Qz;puzw(Erl*885?M=k+*fmmMjN1VfiIRqzX@vOB`C^g+AL|oEjCwIoaG@ZiKDTZ$!8h*`%1cDZ zv=BHjogo60Ec3Y5*wTNcJN=1!ND|iQf#%7u8f*;1ld#WcgH!i)GJ#aN+-{h{YGBp7 z_T^ob;^)s)0nLY|ZvFh+%R?XPIK71h_w38u3iEDySAM$ZW3gC;MUAFEk(o}!N>nZe zMOw`k=tQi}E`3u@dP-6x)O}AgtPFOz%Whb8o6uv2&5?p+Cf4+4-e_f?geTJ3kQ6VcZ75?^Rn5LX8^aM~Ecw^ZUs<)R`B*eEZz0u-3-I zRi2q-Y5sVReI&Vnvug;RLMrcT6Aj2t4Vi!CSdQvxMkCDg2W%`qu;*w)NY8s~*V+DJ z*VdxfAc)!7<*M^GhJD+M7Jm8wLG$GwsRYx<|-v0**@V{h_IQqWw#fdW_sfKMI za?kJ_cZN9n{}<7 zweO->_$v!lEqoUC?BXCBoy&kX8qZ)nvgiG3!76G?X<)ux+F%eGjQ|B$X;9_loKLfF ztkj&Se3w12$U2v96y`Ko7s05nO!aS7n*#Uq`do?reAVQpJ`b6?dDB7<>Un-yz}jF3 zpx5AgNyEqYLbQKI;)hU9dp8w2HI`r{JpAIMSZtXVhxkxSm8Ma6V>CsPxA1^Zz)Rb+ zB~oIlOMda5cI4|i-b$giN=Br={9=;$touOHWA&WkFP^_6c%IT||8Di^=)=Rfc zdI+pqMK3)uC?7|#dfF&LtjVA2@_#lKD7bj8_>z9!CNiQ~wNoO#p(oauIT+)j*l6wh znwtCwJ8JW(ox5%cK?ZHUDv;fIiPv45Z%)5W=@VGAzePS{-zA^T>zYv1WOcoZ9u6Y+ z>cMtt1l0PkG|kDNQrn6V~iPnhe&`lpj^R-t=mO`ObfJ zz>VcaGM5W5y5GOe@6&C8j|}g6?%X9z6@kK-zkO|> z(8TXA4(2d#+i*uCmw!w>IH+uWl71o?n#R4B2GmYBKY$m~5HM zPBN((0(&ZGK2kf0tF3)I9#OG?yz=%wBrL5p%k(U%BiGLd9d4$DyQ54Mkd^VUpu)E@ zYl-4ns0rv3F74o`s50E4pv(s? zLIK~OF`i|Go@ELcKw%$zmcn{j2ZLL1OJFO? zNfoeqV)`^X#(SG~^{;=Ho@8*}bfZk&<+l{eILSqjb2On&7~DeB4)=T*A;2^5!&BmB zq|VRQG8&>9FDOIx$H1Cr#;ax_?M170w#0uVjM@uj<_gc2-*g63rU^Df(2FC!>0q=n z7o&wykg>8wuVtd#)wpRpF&*@+0;yh2=($N$_y7?5=o|suyd9JY2Q44j2Q_uah{H6r zKFnQiOA8qp7?4L%{LD?og;BwaGAIN!K}-kfCR$f^8yLQAyD@9%r>nO-k!s8cD-U?x z7AJ-N-gU@AjnA-Sw87f-YdnMnt-xt+$r8^ zPA_bXegWqGYF+5!1;s7UsXy0`7jy!q!ePjr6?95=E(55o!`pP;fdYR4$+#G+$8I1V z1tRqVDD@$509XJ31YA0cf}Fnn&xb7Yvh#VsBFM*wVC8n1)c&3b zi(7RMcr<;)?>y-^vGn?n{kWcy004canGynjCYE-zYo_g`tZ{f50PQ&8O!_6r9(>#h zu)LY6cC&3llo`*Ct44(1O5I6mn#GWlFjT`?-VI8G%>DxCR=V7$>p#q_B|7ln?*`ik zdM6U*L!j@kjuZyII1HY1`j3-it1aPZ-%oagI>pVp~5sQuqbPYZHHF55v<1}Pf=H7Kd z5pAp*t<@Y%#EDi?UJ5`94)j$?rG?a)XOI)PFZS*k=HCv6E&Xa2g#1#AY9{Iwj`%oC z9~jpB|2|cLQhy=TyXe!VmE(A*h_h$xg+b7bjk>lDyvDD-smbh!iO7dta6w^-;;?4B zH*`N1f%i0e#SZ{>_u7O%oGHh+2c{2sqKqp4^j-)e?zf+DS($ZI ztwSsZ>Y9aJS8p6w%lDX+r)uC8>?@`A6WvC3IC03#wBLNrArVqC$VXlZmN`)+A0-x0 zSo;yDV+rZ_O_;Te--yjpMiV`{-#yd1QRv2RSP$A}{c;4@?rGbqZf^sZ3h3H<=d#0S zB4S+>)<)hkh~h9yM#AY}mhY^=x|nc8=EmuT%nyYt1V`ndYowigs>bi9;rTKJpy*(f zj)Vga#W#TtU!5Izn@bT0kbsMKm}<}3b-tx)S)3dUAiMf)GXcdN4cQn%F*l0kz4^4M|OgeXDIM7MFslYg7u3TOYQhjQd z201NAy~Y_d3MzKQ?>u;)190$J2-@n9`gIRc*Pz!mUug*Rqj2Z8&sq=B9#78ceZWCD zt-Ne=gLfzzuQ@k;sw5ekPVeECA&CTDE|OP*sCY5A>s~bbScEh7^co2NY7wB>KP#Jj{D9InFSrEY6?Ns-TQKW81;noK_6IBeb2mwS^)=pd7CSsMU zw>*SX-rmbTDbcU-^8g>`r-e{%Y z2@K#7ynJ_Jc2J^lKiel|Hj>n!#nQfNkw3MIFKaAT4YH05x>8$|)Z3uUA0rGG;ATUD zP)*O_QTsQw?9_5`cogQbPDH6PoglI9+ZFxJaktiMXo%5(!3R}>3I^)UN123JXbiX+ z+6ZUIjdqwM1_8QLEID=~k5RPJhZWWMr>`9Cv2gsU^hrpqF4Q)l#v+e4*FHpUwiVo0dlDLI51n-tFI9om-#* zKDE|F#z~@x1$+Nz(NgMQ)h1{Diea*Oe$;ACtw8 zga@n)E3VON^xFYUd$z$E9ZmM@Sa@l1OJaC?agZ~eNqcGY*DzREE1|Wa@qw2Dq1ke> zqwD9pn}JkNQ&VeB_dyX~Pizqm{YSOVr+O-x6K1RZMo>B)ut?t*`hL#1Uj=O>!^`%c z;F6U3yy)Q;fuG0K*?<6VYr*77+9mEh_^xj0Sz#V4$nWoZ`EI~>ZLj}$`LNbDt@-#8ZQ$0fm=)3Lg?CUR3qoWFkg65Yk``HP;9U$_HK;@GAW=6VTnVI@wnt zPufLIg&tfHr=Hc|uJu(ml%EB(S4n?nspFC`gzgj=uHD6d|1v0P<~Q=}2Zcia(aTbp`&jH7L@pPoKz6qOx7`nVpv*YsTOBI_+sA)^!Tv4*5?qi$lSd0r z($+y&2X1U5oe7bujPLXXP8H+V1mU$j8G)P9P@oTArvT{eYCw>rTp<9~(^nWVo^?2J z5m0-=05J0|F@!oP(?>=^Czr-W389&HvgsWpBo*<6SfHqKLcKjHl;8`R$khAn#2IEs zj+75BYNhuue+*)62DMpg36PXxNRSiCVN( z-*ZkU&2;4lQC~mKCK)ZA?9gzKB(d8=8cGWy6*WKOw72$M@eB++CKvEsH%)QPmwLGj z9u1vLKtvyllVSFLT9&)z84TdG7j#oYuxIvu&R_1p;MXxw+GF~RjNM+p*SnhEv9+DK}V0mCw2Tj3R(kEXF9Fleny*5XQ*#?Mz8`rAJUkMIdKmQ1p-_VO4&wqFR!01X**qb4j0O*9mPot#dTF`cuy_r zD}r~d5t{%#YG8UowQMp@d^lrc)~($1BDe5GsKR>v;NEsdC2pzz}i|3nXsxmo*@B=Won3Ui8B-C&7ZkK9IS@WAF0XT&$x?-pL^*Bbqa9TuBM_ ztj7^HR@yM|GA5<6Zc1|{tB$Nda{=&W&4XHbWMu=o9v>P z$pmZIP5@{;aJc+N(nK{~omY6WPD&SVT=DAfId{eneHBMm4Y^m6mUQ&@^BS@1d0C?? zv^nir(3Euf)i@k88?Oh%!j(9^e?~NvW6%w*!>?_ zD-?5Y2@}8Xx{55srXN17VIZ5;8IzUWrX1(36&TMUeVg!fTz-kDY$=ruEBVH{D&a*o z-&(jdp{M)KKJUDA?V`5(u%>vpIrWc}YM`l95fg8{QlPbcCr+c-)h(5(k{;?DmGO1G zJf*KZhI+W-Z!VdzK~4{|LO-%%5pvOK9&H<75^ODZMEG5y2Y>f71c^GjDo&zX*UF&c zHBb z=pp9yAuB>gROcQY3s3EO=1+ehl`)475vT6NSLuv~j7GH~jr=KrlSBBZ{+^^TQ|i{C zU}qeq8cSy%89Xo0RU&WR_3Pg!^!;#gPH3c;c|f5ge1~dDiYe>u%BY<%NT>cdGFrzS zh1Ut5(jBrR3-`mL&DoOovy?N_=;EptVEy!nw=55itXA9#c$?C`xEipxZl zi3sHVqq=><*_eVoQ5gR{|D<~Cgl;|1Sk&MZJba~rJEfhH0%B4tyyveJ=DyvroFE53 zXc}v*O=$kQEKApDZbU#rvQ^~7jw$YPN1-x2=-ar_6bU-w5 z#M!gXq+$^1e>lTrOD|YMxtmaT4AFhA(%;7j%Jv(R4x`R-nllmFHE{^)N#C`iDEH{A zF1(b|r~$gicvB6?4w`S2s_7M-ZxQe=EB~sB)3~_;B2bMgmRYu!CC%y|0W=ng%aA#y(PS7`c&~KT{y5E3a~#*qMv5ns`1L#YH6fVDXlY zCqe=MmjFn${~=l_cI<&d`1^_Yw(#^15hg976HPDS>(GX>;F{988opt{TAATM*bYrIbHN+oB9Je|C7W2=t`x;uh9V+y%d!}@|2?9 z5N@PYZ=9t%udipTCSv(&Vzxb8{D1}f)G)_&b;0@mvPjAR}iV_wVSG2n(s07K^1 z9#2aK@oQjiD@w2E9#E`Alt`qAB>mR=rka zTNP)K>q`yRa9`^!jP3>QF%y^vMPDb5V<>o+TSZk$!Qy9QbX40DKn1GjB1let?LqYR zYkfJGjp-Uw=d(|~DG^H zjA`h!;UuM90A}r>K_03z0C`cI@TmKo36jogDbj-MBLX!zEC$SDtZ-xM$6k}mHFx-e zVzcznDl$upGmvyi4x;%20&&$teT*>cqL?fF)$f)D|2Le{AJ80mL8(jI-u6sne84&g zCiM)UO|1a2?l|oLC4o#gVx$D^H}!mQSW+paVm}9HpKTfNS_Zj(aApJgRB4O6rAn49pET zVV=2V=HqSOhVT`WwSTSpoO1E#_BAVw{figwLWjD_%5Tp9QxbYCmG$LsRObUQetU80 zX1{CP4^~kh9aSyWt6GZvW5`Dx8A3$oyFH)XnS+T^pqqt+!Rw=VM6wAfr#*n2*fH0p zx(2CF1xlq;t|{N=xYJ^NPi@O@LX0_CFq%Q0aaoBjF2lAv1LX|g)K#U{oSHaP#acsJ(GAUlQ zE%`lD735f4r`qu*%m+45rgrsdzP^`LI*Iux7kZ^j;?Z~CpX+`JB=X1cT5h`|vA-8b z1FnnQsvBX}=zUh~w&#H`R}B~Vm73sS=v;<}W*e^)Rpit+;lGhIpPStfq(I!N6f}+r zpZK=a#86NV!5t`#-*u!;+W<$*0XNa}ih~lV&o{-v|I|_7>(0=wUXWO&M$^QX>Hyl* z07dpmAA#dD0#h;Y+L9H_vIS|!NM@Ijn|{LevhdJV8pJ3T;0{g?h#!Faq4<+%Jp zf}v^*K(*RjQ*eE@xmi!JulkmNsrEjS)16V^4STB)qc2LsvL<;NSL@1BSuf~q&C@Pz zQV~|_%L-UK;m@2>PNOUz4YT4^Kn?nEtn;Vq+tiTHiKv0H+z)|T>D5EqAcBVk{8a5O%8Z7Qg%u|mkH zF<7T%GHwknReX+b=Z;Cbj*5EWgN@yn5BN-cEBU+YXG!3q;ubUIPaCfN3z)kYkcd$k+n%(tJzQF!KU6*^Gj}kKs+pgrP^$3UcD^R?di8JSXv$UA4{euy zNf)^%&}hgh?E%!aS#0pj50D2z;)!y0s^^6|BfMeV1DteLUCw<-SKEHk3X{MkorZlu z>1zX_WovTTH~(nMF6n?Kml4*Sbh~OLe&NE-BSB|(9&ruCZEZIq@=5~z)P&RRF8}`S za6#$^_M%0C{A>0DGq6t$dVl}0C}#igjV{&NBGZiq$4Cry$jS3J0y32I)>s6XRnt4_ zl4NWn9sd2I++&wt7z6t~L$NqbXUfHjg zD~byy#j-RFuzqHQkP3&;@q1HXi)*lkL-3ls_4+y3LsPa)VB*^~*UNeQ06Q}TAW7b% zBp?K|H=75|ZoBz)r`uBmAt7N2gd@rJUu@ro8dm3JyG*($uG_5veCS|%BFS0J+s|w? zW>2LgTugy8HU=_~!wNY!*70dN5Rtc=B9MN`+URQ_@r-tT*`MG5lEsuBx8~ON&)%2% zhNLy77QToU()G;R2-qfZ&Nc6U*ws6_AA_i3NYBbCKJv0273n7$SLsnF|gq z`~K}6YrrD6=PhZ&CBd%EyF_#qDeqM`{1Yu-6Q-Wo_InC;ztl2MnO7s`4Z zoJfj`y_TzP_0I>&n zfNo_Id!^E90h;qvY1;ij?qc+6X-4n*q;rGhQH%M$$U}#GtRr&OMS zKv<-cR@vwGNBBxX1i5sRE9g11m)^Di9Y)yd*tW-~_y_Gz@Z`HL>1o7#b zIQ+qu>A9`@&faW%)PRqME53fBt{p~FN=S_VPs$D1gL&q@m`03EU^JEzx;`NW6{mQ9-%C_!$}D@Ai-;W=p?sOZ&|C2%nY< z>^WZo_K%j*ecmwNr&K?h%ckHSdjy7_YgyI@&f$_JfoVB~U@_C;fwyGC5$Qh+Gf{sc zhO-7-?Y6qG~lJ~(-}OEw2KGzFUv+oGqdzW-U|@N!NATTLh==1xc}3hXp81D zsc|XD=mikLcIHkwmEj1@THDr1* zZ@yXqPofAG*T6`EOd;&oV*_v1-)q8EkW&j-i&n9pRkmcR22M;ONn7dlSZB?5f@Q1J zDe5QI#jeslcZAuWlrtCQn(L^lmpj0z692h!avvkkh9J;YDh#cT5@1eh@L?#%{6IFJ zQrR$M|9I&3pTxrya33^)t9~sx)Tf| zLEPVFnfybJ+ zEa)-}V}Vg7mknDlSRQ-oy}PPBU)m5D<(*}pkH-G0GS~S-u@Z?wdVqmVbyU6%x6&YN zy#sI9H7$`aJij$0wvtnUWO^$&pa`c?p@r{YQt&c$2b*pS2;Yzq3m69VdvGb%wk`_^ znYe-`P&jTXA!214y45_#RNW!jH^8y05gi$LCOLS^F|f;gYPPha zK+(k<)+Z~|Jh34BlM6^5^Vz3+rzNz-`I|Lk@7swRp^2)ISt8*ei>!*INRjA-X1eWL zQ@k{Z6S(JF44U>;);ABU^i_xu84be0 zX(yEjI*iwewhniNtX139erb~JQ=mxy&!5rmE=N2 zKHvh%(tQ;CB($x6X*X`?jGnUIV7!|J+&QKfT(Ntck728>tCBi!;=MGHb!? z7j6G?UqrSl=-+RT32aFd=jNE#CK6DlB?wbF?M>FWdJ&vg#adEoCD6@&2_PPY1d9}q zeE-;st*;D%KB|L*(YKH@USrds6EU<{cS%9`{Df>%R% z5g_sO-`5)f^$#$&|0V*=z<(3*-*>qD_Z=?(eTU2cd*9*mUjsnW;QyC3fYb0geD~ug V^YAm@{E^X}GCyxtaNOzk{{q}v?&JUf literal 41824 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 z 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 From 97c461f7a38b09f94911f4078355dc45848f4e3e Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 13:08:38 +0000 Subject: [PATCH 32/44] fix: update key generation in MyOnCallPoliciesScreen to handle undefined policyId --- MobileApp/src/screens/MyOnCallPoliciesScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx index e42ce00f13..e3e9087577 100644 --- a/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx +++ b/MobileApp/src/screens/MyOnCallPoliciesScreen.tsx @@ -340,7 +340,7 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element { return ( Date: Wed, 18 Feb 2026 13:16:54 +0000 Subject: [PATCH 33/44] feat: implement default notification rules for verified communication methods in User APIs --- Common/Server/API/UserCallAPI.ts | 19 ++ Common/Server/API/UserEmailAPI.ts | 19 ++ Common/Server/API/UserPushAPI.ts | 33 +++ Common/Server/API/UserSmsAPI.ts | 19 ++ Common/Server/API/UserWhatsAppAPI.ts | 19 ++ .../Services/UserNotificationRuleService.ts | 264 ++++++++++-------- 6 files changed, 262 insertions(+), 111 deletions(-) diff --git a/Common/Server/API/UserCallAPI.ts b/Common/Server/API/UserCallAPI.ts index 04ababc076..a38cb15bf8 100644 --- a/Common/Server/API/UserCallAPI.ts +++ b/Common/Server/API/UserCallAPI.ts @@ -2,6 +2,7 @@ import UserMiddleware from "../Middleware/UserAuthorization"; import UserCallService, { Service as UserCallServiceType, } from "../Services/UserCallService"; +import UserNotificationRuleService from "../Services/UserNotificationRuleService"; import { ExpressRequest, ExpressResponse, @@ -9,8 +10,10 @@ import { OneUptimeRequest, } from "../Utils/Express"; import Response from "../Utils/Response"; +import logger from "../Utils/Logger"; import BaseAPI from "./BaseAPI"; import BadDataException from "../../Types/Exception/BadDataException"; +import ObjectID from "../../Types/ObjectID"; import UserCall from "../../Models/DatabaseModels/UserCall"; import UserSMS from "../../Models/DatabaseModels/UserSMS"; @@ -52,6 +55,7 @@ export default class UserCallAPI extends BaseAPI< }, select: { userId: true, + projectId: true, verificationCode: true, }, }); @@ -95,6 +99,21 @@ export default class UserCallAPI extends BaseAPI< }, }); + // Create default notification rules for this verified call number + try { + await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod( + { + projectId: new ObjectID(item.projectId!.toString()), + userId: new ObjectID(item.userId!.toString()), + notificationMethod: { + userCallId: item.id!, + }, + }, + ); + } catch (e) { + logger.error(e); + } + return Response.sendEmptySuccessResponse(req, res); } catch (err) { return next(err); diff --git a/Common/Server/API/UserEmailAPI.ts b/Common/Server/API/UserEmailAPI.ts index 74efcacde1..8a3e8e806e 100644 --- a/Common/Server/API/UserEmailAPI.ts +++ b/Common/Server/API/UserEmailAPI.ts @@ -2,6 +2,7 @@ import UserMiddleware from "../Middleware/UserAuthorization"; import UserEmailService, { Service as UserEmailServiceType, } from "../Services/UserEmailService"; +import UserNotificationRuleService from "../Services/UserNotificationRuleService"; import { ExpressRequest, ExpressResponse, @@ -9,8 +10,10 @@ import { OneUptimeRequest, } from "../Utils/Express"; import Response from "../Utils/Response"; +import logger from "../Utils/Logger"; import BaseAPI from "./BaseAPI"; import BadDataException from "../../Types/Exception/BadDataException"; +import ObjectID from "../../Types/ObjectID"; import UserEmail from "../../Models/DatabaseModels/UserEmail"; export default class UserEmailAPI extends BaseAPI< @@ -51,6 +54,7 @@ export default class UserEmailAPI extends BaseAPI< }, select: { userId: true, + projectId: true, verificationCode: true, }, }); @@ -94,6 +98,21 @@ export default class UserEmailAPI extends BaseAPI< }, }); + // Create default notification rules for this verified email + try { + await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod( + { + projectId: new ObjectID(item.projectId!.toString()), + userId: new ObjectID(item.userId!.toString()), + notificationMethod: { + userEmailId: item.id!, + }, + }, + ); + } catch (e) { + logger.error(e); + } + return Response.sendEmptySuccessResponse(req, res); } catch (err) { return next(err); diff --git a/Common/Server/API/UserPushAPI.ts b/Common/Server/API/UserPushAPI.ts index d2adbfad1c..5543f91686 100644 --- a/Common/Server/API/UserPushAPI.ts +++ b/Common/Server/API/UserPushAPI.ts @@ -2,8 +2,10 @@ import UserMiddleware from "../Middleware/UserAuthorization"; import UserPushService, { Service as UserPushServiceType, } from "../Services/UserPushService"; +import UserNotificationRuleService from "../Services/UserNotificationRuleService"; import PushNotificationService from "../Services/PushNotificationService"; import PushNotificationUtil from "../Utils/PushNotificationUtil"; +import logger from "../Utils/Logger"; import { ExpressRequest, ExpressResponse, @@ -117,6 +119,21 @@ export default class UserPushAPI extends BaseAPI< }, }); + // Create default notification rules for this registered push device + try { + await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod( + { + projectId: new ObjectID(req.body.projectId), + userId, + notificationMethod: { + userPushId: savedDevice.id!, + }, + }, + ); + } catch (e) { + logger.error(e); + } + return Response.sendJsonObjectResponse(req, res, { success: true, deviceId: savedDevice._id!.toString(), @@ -294,6 +311,7 @@ export default class UserPushAPI extends BaseAPI< }, select: { userId: true, + projectId: true, }, }); @@ -316,6 +334,21 @@ export default class UserPushAPI extends BaseAPI< await this.service.verifyDevice(device._id!.toString()); + // Create default notification rules for this verified push device + try { + await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod( + { + projectId: new ObjectID(device.projectId!.toString()), + userId, + notificationMethod: { + userPushId: device.id!, + }, + }, + ); + } catch (e) { + logger.error(e); + } + return Response.sendEmptySuccessResponse(req, res); } catch (error) { return next(error); diff --git a/Common/Server/API/UserSmsAPI.ts b/Common/Server/API/UserSmsAPI.ts index 3c698402fd..ffe588a7c2 100644 --- a/Common/Server/API/UserSmsAPI.ts +++ b/Common/Server/API/UserSmsAPI.ts @@ -2,6 +2,7 @@ import UserMiddleware from "../Middleware/UserAuthorization"; import UserSMSService, { Service as UserSMSServiceType, } from "../Services/UserSmsService"; +import UserNotificationRuleService from "../Services/UserNotificationRuleService"; import { ExpressRequest, ExpressResponse, @@ -9,8 +10,10 @@ import { OneUptimeRequest, } from "../Utils/Express"; import Response from "../Utils/Response"; +import logger from "../Utils/Logger"; import BaseAPI from "./BaseAPI"; import BadDataException from "../../Types/Exception/BadDataException"; +import ObjectID from "../../Types/ObjectID"; import UserSMS from "../../Models/DatabaseModels/UserSMS"; export default class UserSMSAPI extends BaseAPI { @@ -48,6 +51,7 @@ export default class UserSMSAPI extends BaseAPI { }, select: { userId: true, + projectId: true, verificationCode: true, }, }); @@ -91,6 +95,21 @@ export default class UserSMSAPI extends BaseAPI { }, }); + // Create default notification rules for this verified SMS + try { + await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod( + { + projectId: new ObjectID(item.projectId!.toString()), + userId: new ObjectID(item.userId!.toString()), + notificationMethod: { + userSmsId: item.id!, + }, + }, + ); + } catch (e) { + logger.error(e); + } + return Response.sendEmptySuccessResponse(req, res); } catch (err) { return next(err); diff --git a/Common/Server/API/UserWhatsAppAPI.ts b/Common/Server/API/UserWhatsAppAPI.ts index 1724c5dea9..b7679d7253 100644 --- a/Common/Server/API/UserWhatsAppAPI.ts +++ b/Common/Server/API/UserWhatsAppAPI.ts @@ -2,6 +2,7 @@ import UserMiddleware from "../Middleware/UserAuthorization"; import UserWhatsAppService, { Service as UserWhatsAppServiceType, } from "../Services/UserWhatsAppService"; +import UserNotificationRuleService from "../Services/UserNotificationRuleService"; import { ExpressRequest, ExpressResponse, @@ -9,8 +10,10 @@ import { OneUptimeRequest, } from "../Utils/Express"; import Response from "../Utils/Response"; +import logger from "../Utils/Logger"; import BaseAPI from "./BaseAPI"; import BadDataException from "../../Types/Exception/BadDataException"; +import ObjectID from "../../Types/ObjectID"; import UserWhatsApp from "../../Models/DatabaseModels/UserWhatsApp"; export default class UserWhatsAppAPI extends BaseAPI< @@ -50,6 +53,7 @@ export default class UserWhatsAppAPI extends BaseAPI< }, select: { userId: true, + projectId: true, verificationCode: true, isVerified: true, }, @@ -100,6 +104,21 @@ export default class UserWhatsAppAPI extends BaseAPI< }, }); + // Create default notification rules for this verified WhatsApp number + try { + await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod( + { + projectId: new ObjectID(item.projectId!.toString()), + userId: new ObjectID(item.userId!.toString()), + notificationMethod: { + userWhatsAppId: item.id!, + }, + }, + ); + } catch (e) { + logger.error(e); + } + return Response.sendEmptySuccessResponse(req, res); } catch (err) { return next(err); diff --git a/Common/Server/Services/UserNotificationRuleService.ts b/Common/Server/Services/UserNotificationRuleService.ts index d9a0bc6e1e..55bee3e74c 100644 --- a/Common/Server/Services/UserNotificationRuleService.ts +++ b/Common/Server/Services/UserNotificationRuleService.ts @@ -69,6 +69,14 @@ import PushNotificationMessage from "../../Types/PushNotification/PushNotificati import logger from "../Utils/Logger"; import CaptureSpan from "../Utils/Telemetry/CaptureSpan"; +export interface NotificationMethodDescriptor { + userEmailId?: ObjectID; + userSmsId?: ObjectID; + userCallId?: ObjectID; + userWhatsAppId?: ObjectID; + userPushId?: ObjectID; +} + export class Service extends DatabaseService { public constructor() { super(Model); @@ -2207,13 +2215,89 @@ export class Service extends DatabaseService { } @CaptureSpan() - public async addDefaultIncidentNotificationRuleForUser(data: { + public async addDefaultNotificationRulesForVerifiedMethod(data: { projectId: ObjectID; userId: ObjectID; - userEmail: UserEmail; + notificationMethod: NotificationMethodDescriptor; }): Promise { - const { projectId, userId, userEmail } = data; + const { projectId, userId, notificationMethod } = data; + await this.createIncidentOnCallRules(projectId, userId, notificationMethod); + await this.createAlertOnCallRules(projectId, userId, notificationMethod); + await this.createSingleRule( + projectId, + userId, + notificationMethod, + NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE, + ); + await this.createSingleRule( + projectId, + userId, + notificationMethod, + NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE, + ); + await this.createSingleRule( + projectId, + userId, + notificationMethod, + NotificationRuleType.WHEN_USER_GOES_ON_CALL, + ); + await this.createSingleRule( + projectId, + userId, + notificationMethod, + NotificationRuleType.WHEN_USER_GOES_OFF_CALL, + ); + } + + private applyNotificationMethod( + rule: Model, + descriptor: NotificationMethodDescriptor, + ): void { + if (descriptor.userEmailId) { + rule.userEmailId = descriptor.userEmailId; + } + if (descriptor.userSmsId) { + rule.userSmsId = descriptor.userSmsId; + } + if (descriptor.userCallId) { + rule.userCallId = descriptor.userCallId; + } + if (descriptor.userWhatsAppId) { + rule.userWhatsAppId = descriptor.userWhatsAppId; + } + if (descriptor.userPushId) { + rule.userPushId = descriptor.userPushId; + } + } + + private getNotificationMethodQuery( + descriptor: NotificationMethodDescriptor, + ): Record { + const query: Record = {}; + if (descriptor.userEmailId) { + query["userEmailId"] = descriptor.userEmailId; + } + if (descriptor.userSmsId) { + query["userSmsId"] = descriptor.userSmsId; + } + if (descriptor.userCallId) { + query["userCallId"] = descriptor.userCallId; + } + if (descriptor.userWhatsAppId) { + query["userWhatsAppId"] = descriptor.userWhatsAppId; + } + if (descriptor.userPushId) { + query["userPushId"] = descriptor.userPushId; + } + return query; + } + + private async createIncidentOnCallRules( + projectId: ObjectID, + userId: ObjectID, + notificationMethod: NotificationMethodDescriptor, + ): Promise { const incidentSeverities: Array = await IncidentSeverityService.findBy({ query: { @@ -2229,38 +2313,34 @@ export class Service extends DatabaseService { }, }); - // create for incident severities. for (const incidentSeverity of incidentSeverities) { - //check if this rule already exists. const existingRule: Model | null = await this.findOneBy({ query: { projectId, userId, - userEmailId: userEmail.id!, + ...this.getNotificationMethodQuery(notificationMethod), incidentSeverityId: incidentSeverity.id!, ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT, - }, + } as any, props: { isRoot: true, }, }); if (existingRule) { - continue; // skip this rule. + continue; } - const notificationRule: Model = new Model(); - - notificationRule.projectId = projectId; - notificationRule.userId = userId; - notificationRule.userEmailId = userEmail.id!; - notificationRule.incidentSeverityId = incidentSeverity.id!; - notificationRule.notifyAfterMinutes = 0; - notificationRule.ruleType = - NotificationRuleType.ON_CALL_EXECUTED_INCIDENT; + const rule: Model = new Model(); + rule.projectId = projectId; + rule.userId = userId; + this.applyNotificationMethod(rule, notificationMethod); + rule.incidentSeverityId = incidentSeverity.id!; + rule.notifyAfterMinutes = 0; + rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_INCIDENT; await this.create({ - data: notificationRule, + data: rule, props: { isRoot: true, }, @@ -2268,14 +2348,11 @@ export class Service extends DatabaseService { } } - @CaptureSpan() - public async addDefaultAlertNotificationRulesForUser(data: { - projectId: ObjectID; - userId: ObjectID; - userEmail: UserEmail; - }): Promise { - const { projectId, userId, userEmail } = data; - + private async createAlertOnCallRules( + projectId: ObjectID, + userId: ObjectID, + notificationMethod: NotificationMethodDescriptor, + ): Promise { const alertSeverities: Array = await AlertSeverityService.findBy({ query: { @@ -2291,37 +2368,34 @@ export class Service extends DatabaseService { }, }); - // create for Alert severities. for (const alertSeverity of alertSeverities) { - //check if this rule already exists. const existingRule: Model | null = await this.findOneBy({ query: { projectId, userId, - userEmailId: userEmail.id!, + ...this.getNotificationMethodQuery(notificationMethod), alertSeverityId: alertSeverity.id!, ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT, - }, + } as any, props: { isRoot: true, }, }); if (existingRule) { - continue; // skip this rule. + continue; } - const notificationRule: Model = new Model(); - - notificationRule.projectId = projectId; - notificationRule.userId = userId; - notificationRule.userEmailId = userEmail.id!; - notificationRule.alertSeverityId = alertSeverity.id!; - notificationRule.notifyAfterMinutes = 0; - notificationRule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_ALERT; + const rule: Model = new Model(); + rule.projectId = projectId; + rule.userId = userId; + this.applyNotificationMethod(rule, notificationMethod); + rule.alertSeverityId = alertSeverity.id!; + rule.notifyAfterMinutes = 0; + rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_ALERT; await this.create({ - data: notificationRule, + data: rule, props: { isRoot: true, }, @@ -2329,6 +2403,43 @@ export class Service extends DatabaseService { } } + private async createSingleRule( + projectId: ObjectID, + userId: ObjectID, + notificationMethod: NotificationMethodDescriptor, + ruleType: NotificationRuleType, + ): Promise { + const existingRule: Model | null = await this.findOneBy({ + query: { + projectId, + userId, + ...this.getNotificationMethodQuery(notificationMethod), + ruleType, + } as any, + props: { + isRoot: true, + }, + }); + + if (existingRule) { + return; + } + + const rule: Model = new Model(); + rule.projectId = projectId; + rule.userId = userId; + this.applyNotificationMethod(rule, notificationMethod); + rule.notifyAfterMinutes = 0; + rule.ruleType = ruleType; + + await this.create({ + data: rule, + props: { + isRoot: true, + }, + }); + } + @CaptureSpan() public async addDefaultNotificationRuleForUser( projectId: ObjectID, @@ -2361,82 +2472,13 @@ export class Service extends DatabaseService { }); } - // add default incident rules for user - await this.addDefaultIncidentNotificationRuleForUser({ + await this.addDefaultNotificationRulesForVerifiedMethod({ projectId, userId, - userEmail, - }); - - // add default alert rules for user, just like the incident - - await this.addDefaultAlertNotificationRulesForUser({ - projectId, - userId, - userEmail, - }); - - //check if this rule already exists. - const existingRuleOnCall: Model | null = await this.findOneBy({ - query: { - projectId, - userId, + notificationMethod: { userEmailId: userEmail.id!, - ruleType: NotificationRuleType.WHEN_USER_GOES_ON_CALL, - }, - props: { - isRoot: true, }, }); - - if (!existingRuleOnCall) { - // on and off call. - const onCallRule: Model = new Model(); - - onCallRule.projectId = projectId; - onCallRule.userId = userId; - onCallRule.userEmailId = userEmail.id!; - onCallRule.notifyAfterMinutes = 0; - onCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_ON_CALL; - - await this.create({ - data: onCallRule, - props: { - isRoot: true, - }, - }); - } - - //check if this rule already exists. - const existingRuleOffCall: Model | null = await this.findOneBy({ - query: { - projectId, - userId, - userEmailId: userEmail.id!, - ruleType: NotificationRuleType.WHEN_USER_GOES_OFF_CALL, - }, - props: { - isRoot: true, - }, - }); - - if (!existingRuleOffCall) { - // on and off call. - const offCallRule: Model = new Model(); - - offCallRule.projectId = projectId; - offCallRule.userId = userId; - offCallRule.userEmailId = userEmail.id!; - offCallRule.notifyAfterMinutes = 0; - offCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_OFF_CALL; - - await this.create({ - data: offCallRule, - props: { - isRoot: true, - }, - }); - } } } export default new Service(); From 0af41725b4136c8cf1e7b8175c551c2467e98ec4 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 13:25:17 +0000 Subject: [PATCH 34/44] fix: add missing comma in dependencies section of package.json --- Probe/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Probe/package.json b/Probe/package.json index a2ddcaac90..765f670ab6 100644 --- a/Probe/package.json +++ b/Probe/package.json @@ -30,7 +30,7 @@ "net-snmp": "^3.26.1", "ping": "^0.4.4", "playwright": "^1.58.0", - "ts-node": "^10.9.1" + "ts-node": "^10.9.1", "whois-json": "^2.0.4" }, "devDependencies": { From 7ccea023406787799370bd79fd3b848c22b7011d Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 13:37:22 +0000 Subject: [PATCH 35/44] fix secret in probe --- HelmChart/Public/oneuptime/templates/probe.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HelmChart/Public/oneuptime/templates/probe.yaml b/HelmChart/Public/oneuptime/templates/probe.yaml index f99631636e..8bd31ef9f1 100644 --- a/HelmChart/Public/oneuptime/templates/probe.yaml +++ b/HelmChart/Public/oneuptime/templates/probe.yaml @@ -131,7 +131,7 @@ spec: - name: NO_PROXY value: {{ $val.proxy.noProxy | squote }} {{- end }} - {{- include "oneuptime.env.runtime" (dict "Values" $.Values "Release" $.Release) | nindent 12 }} + {{- include "oneuptime.env.oneuptimeSecret" (dict "Values" $.Values "Release" $.Release) | nindent 12 }} ports: - containerPort: {{ if and $val.ports $val.ports.http }}{{ $val.ports.http }}{{ else }}3874{{ end }} protocol: TCP From 7dd6129dad68b0a141a81044c48358174e814c8f Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 13:42:14 +0000 Subject: [PATCH 36/44] feat: add environment variables for log level and node environment in isolated-vm deployment --- .../Public/oneuptime/templates/isolated-vm.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/HelmChart/Public/oneuptime/templates/isolated-vm.yaml b/HelmChart/Public/oneuptime/templates/isolated-vm.yaml index d930b41a77..9086f14b79 100644 --- a/HelmChart/Public/oneuptime/templates/isolated-vm.yaml +++ b/HelmChart/Public/oneuptime/templates/isolated-vm.yaml @@ -73,12 +73,23 @@ spec: {{- end }} imagePullPolicy: {{ $.Values.image.pullPolicy }} env: - {{- include "oneuptime.env.common" . | nindent 12 }} {{- include "oneuptime.env.oneuptimeSecret" . | nindent 12 }} - name: PORT value: {{ $.Values.isolatedVM.ports.http | quote }} + - name: LOG_LEVEL + value: {{ $.Values.logLevel }} + - name: NODE_ENV + value: {{ $.Values.nodeEnvironment }} - name: DISABLE_TELEMETRY value: {{ $.Values.isolatedVM.disableTelemetryCollection | quote }} + {{- if $.Values.openTelemetryExporter.endpoint }} + - name: OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT + value: {{ $.Values.openTelemetryExporter.endpoint }} + {{- end }} + {{- if $.Values.openTelemetryExporter.headers }} + - name: OPENTELEMETRY_EXPORTER_OTLP_HEADERS + value: {{ $.Values.openTelemetryExporter.headers }} + {{- end }} ports: - containerPort: {{ $.Values.isolatedVM.ports.http }} From d62816dd49f73431aa5cf90988c5e2c64b08fad3 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 13:50:28 +0000 Subject: [PATCH 37/44] feat: update probe services to include environment variables for telemetry and logging --- docker-compose.base.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docker-compose.base.yml b/docker-compose.base.yml index b227a62c0c..482a6cd757 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -395,21 +395,25 @@ services: options: max-size: "1000m" - probe-1: + probe-1: restart: always network_mode: host environment: - <<: *common-runtime-variables + ONEUPTIME_URL: ${GLOBAL_PROBE_1_ONEUPTIME_URL} + ONEUPTIME_SECRET: ${ONEUPTIME_SECRET} PROBE_NAME: ${GLOBAL_PROBE_1_NAME} PROBE_DESCRIPTION: ${GLOBAL_PROBE_1_DESCRIPTION} PROBE_MONITORING_WORKERS: ${GLOBAL_PROBE_1_MONITORING_WORKERS} PROBE_KEY: ${GLOBAL_PROBE_1_KEY} PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS: ${GLOBAL_PROBE_1_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS} PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS: ${GLOBAL_PROBE_1_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS} - ONEUPTIME_URL: ${GLOBAL_PROBE_1_ONEUPTIME_URL} PROBE_MONITOR_FETCH_LIMIT: ${GLOBAL_PROBE_1_MONITOR_FETCH_LIMIT} - DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE} PORT: ${GLOBAL_PROBE_1_PORT} + NODE_ENV: ${ENVIRONMENT} + LOG_LEVEL: ${LOG_LEVEL} + DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE} + OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT: ${OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT} + OPENTELEMETRY_EXPORTER_OTLP_HEADERS: ${OPENTELEMETRY_EXPORTER_OTLP_HEADERS} logging: driver: "local" options: @@ -419,17 +423,21 @@ services: restart: always network_mode: host environment: - <<: *common-runtime-variables + ONEUPTIME_URL: ${GLOBAL_PROBE_2_ONEUPTIME_URL} + ONEUPTIME_SECRET: ${ONEUPTIME_SECRET} PROBE_NAME: ${GLOBAL_PROBE_2_NAME} PROBE_DESCRIPTION: ${GLOBAL_PROBE_2_DESCRIPTION} PROBE_MONITORING_WORKERS: ${GLOBAL_PROBE_2_MONITORING_WORKERS} PROBE_KEY: ${GLOBAL_PROBE_2_KEY} - ONEUPTIME_URL: ${GLOBAL_PROBE_2_ONEUPTIME_URL} PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS: ${GLOBAL_PROBE_2_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS} PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS: ${GLOBAL_PROBE_2_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS} PROBE_MONITOR_FETCH_LIMIT: ${GLOBAL_PROBE_2_MONITOR_FETCH_LIMIT} - DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE} PORT: ${GLOBAL_PROBE_2_PORT} + NODE_ENV: ${ENVIRONMENT} + LOG_LEVEL: ${LOG_LEVEL} + DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE} + OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT: ${OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT} + OPENTELEMETRY_EXPORTER_OTLP_HEADERS: ${OPENTELEMETRY_EXPORTER_OTLP_HEADERS} logging: driver: "local" options: From 7f9ed4d43945574702a26b7c206e38cc344fe427 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 14:14:24 +0000 Subject: [PATCH 38/44] feat: refactor SyntheticMonitor to use child processes for script execution - Added isolated-vm dependency for secure script execution. - Replaced direct Playwright usage in SyntheticMonitor with a worker process. - Created SyntheticMonitorWorker to handle script execution in a sandboxed environment. - Implemented proxy configuration handling for worker processes. - Enhanced error handling and logging for script execution results. - Removed unnecessary browser session management from SyntheticMonitor. --- Common/Server/EnvironmentConfig.ts | 8 + Common/Server/Utils/VM/VMRunner.ts | 250 ++++++-- Common/package-lock.json | 327 ++++++++++- Common/package.json | 1 + .../Public/oneuptime/templates/_helpers.tpl | 12 + .../oneuptime/templates/probe-ingest.yaml | 1 + .../Public/oneuptime/templates/probe.yaml | 2 +- .../Public/oneuptime/templates/secrets.yaml | 12 + HelmChart/Public/oneuptime/values.yaml | 1 + Probe/Services/Register.ts | 12 +- .../Monitors/MonitorTypes/SyntheticMonitor.ts | 542 ++++++------------ .../MonitorTypes/SyntheticMonitorWorker.ts | 339 +++++++++++ ProbeIngest/API/Register.ts | 20 +- config.example.env | 1 + docker-compose.base.yml | 5 +- 15 files changed, 1105 insertions(+), 428 deletions(-) create mode 100644 Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts diff --git a/Common/Server/EnvironmentConfig.ts b/Common/Server/EnvironmentConfig.ts index ef5f62fd5b..6eccd75d1c 100644 --- a/Common/Server/EnvironmentConfig.ts +++ b/Common/Server/EnvironmentConfig.ts @@ -161,6 +161,14 @@ export const ClusterKey: ObjectID = new ObjectID( export const HasClusterKey: boolean = Boolean(process.env["ONEUPTIME_SECRET"]); +export const RegisterProbeKey: ObjectID = new ObjectID( + process.env["REGISTER_PROBE_KEY"] || "secret", +); + +export const HasRegisterProbeKey: boolean = Boolean( + process.env["REGISTER_PROBE_KEY"], +); + export const AppApiHostname: Hostname = Hostname.fromString( `${process.env["SERVER_APP_HOSTNAME"] || "localhost"}:${ process.env["APP_PORT"] || 80 diff --git a/Common/Server/Utils/VM/VMRunner.ts b/Common/Server/Utils/VM/VMRunner.ts index 7054620e1e..85c56daff6 100644 --- a/Common/Server/Utils/VM/VMRunner.ts +++ b/Common/Server/Utils/VM/VMRunner.ts @@ -1,12 +1,8 @@ -import Dictionary from "../../../Types/Dictionary"; -import GenericObject from "../../../Types/GenericObject"; import ReturnResult from "../../../Types/IsolatedVM/ReturnResult"; -import { JSONObject, JSONValue } from "../../../Types/JSON"; -import axios from "axios"; -import http from "http"; -import https from "https"; +import { JSONObject } from "../../../Types/JSON"; +import axios, { AxiosResponse } from "axios"; import crypto from "crypto"; -import vm, { Context } from "node:vm"; +import ivm from "isolated-vm"; import CaptureSpan from "../Telemetry/CaptureSpan"; export default class VMRunner { @@ -16,49 +12,231 @@ export default class VMRunner { options: { timeout?: number; args?: JSONObject | undefined; - context?: Dictionary | undefined; }; }): Promise { const { code, options } = data; + const timeout: number = options.timeout || 5000; const logMessages: string[] = []; - let sandbox: Context = { - console: { - log: (...args: JSONValue[]) => { + const isolate: ivm.Isolate = new ivm.Isolate({ memoryLimit: 128 }); + + try { + const context: ivm.Context = await isolate.createContext(); + const jail: ivm.Reference> = context.global; + + // Set up global object + await jail.set("global", jail.derefInto()); + + // console.log - fire-and-forget callback + await jail.set( + "_log", + new ivm.Callback((...args: string[]) => { logMessages.push(args.join(" ")); + }), + ); + + await context.eval(` + const console = { log: (...a) => _log(...a.map(v => { + try { return typeof v === 'object' ? JSON.stringify(v) : String(v); } + catch(_) { return String(v); } + }))}; + `); + + // args - deep copy into isolate + if (options.args) { + await jail.set( + "_args", + new ivm.ExternalCopy(options.args).copyInto(), + ); + await context.eval("const args = _args;"); + } else { + await context.eval("const args = {};"); + } + + // axios (get, post, put, delete) - bridged via applySyncPromise + const axiosRef: ivm.Reference< + ( + method: string, + url: string, + dataOrConfig?: string, + ) => Promise + > = new ivm.Reference( + async ( + method: string, + url: string, + dataOrConfig?: string, + ): Promise => { + const parsed: JSONObject | undefined = dataOrConfig + ? (JSON.parse(dataOrConfig) as JSONObject) + : undefined; + + let response: AxiosResponse; + + switch (method) { + case "get": + response = await axios.get(url, parsed); + break; + case "post": + response = await axios.post(url, parsed); + break; + case "put": + response = await axios.put(url, parsed); + break; + case "delete": + response = await axios.delete(url, parsed); + break; + default: + throw new Error(`Unsupported HTTP method: ${method}`); + } + + return JSON.stringify({ + status: response.status, + headers: response.headers, + data: response.data, + }); }, - }, - http: http, - https: https, - axios: axios, - crypto: crypto, - setTimeout: setTimeout, - clearTimeout: clearTimeout, - setInterval: setInterval, - ...options.context, - }; + ); - if (options.args) { - sandbox = { - ...sandbox, - args: options.args, - }; - } + await jail.set("_axiosRef", axiosRef); - vm.createContext(sandbox); // Contextify the object. + await context.eval(` + const axios = { + get: async (url, config) => { + const r = await _axiosRef.applySyncPromise(undefined, ['get', url, config ? JSON.stringify(config) : undefined]); + return JSON.parse(r); + }, + post: async (url, data) => { + const r = await _axiosRef.applySyncPromise(undefined, ['post', url, data ? JSON.stringify(data) : undefined]); + return JSON.parse(r); + }, + put: async (url, data) => { + const r = await _axiosRef.applySyncPromise(undefined, ['put', url, data ? JSON.stringify(data) : undefined]); + return JSON.parse(r); + }, + delete: async (url, config) => { + const r = await _axiosRef.applySyncPromise(undefined, ['delete', url, config ? JSON.stringify(config) : undefined]); + return JSON.parse(r); + }, + }; + `); - const script: string = `(async()=>{ + // crypto (createHash, createHmac, randomBytes) - bridged via applySync + const cryptoRef: ivm.Reference< + (op: string, ...args: string[]) => string + > = new ivm.Reference((op: string, ...args: string[]): string => { + switch (op) { + case "createHash": { + const [algorithm, inputData, encoding] = args; + return crypto + .createHash(algorithm!) + .update(inputData!) + .digest((encoding as crypto.BinaryToTextEncoding) || "hex"); + } + case "createHmac": { + const [algorithm, key, inputData, encoding] = args; + return crypto + .createHmac(algorithm!, key!) + .update(inputData!) + .digest((encoding as crypto.BinaryToTextEncoding) || "hex"); + } + case "randomBytes": { + const [size] = args; + return crypto.randomBytes(parseInt(size!)).toString("hex"); + } + default: + throw new Error(`Unsupported crypto operation: ${op}`); + } + }); + + await jail.set("_cryptoRef", cryptoRef); + + await context.eval(` + const crypto = { + createHash: (algorithm) => ({ + _alg: algorithm, _data: '', + update(d) { this._data = d; return this; }, + digest(enc) { return _cryptoRef.applySync(undefined, ['createHash', this._alg, this._data, enc || 'hex']); } + }), + createHmac: (algorithm, key) => ({ + _alg: algorithm, _key: key, _data: '', + update(d) { this._data = d; return this; }, + digest(enc) { return _cryptoRef.applySync(undefined, ['createHmac', this._alg, this._key, this._data, enc || 'hex']); } + }), + randomBytes: (size) => ({ + toString(enc) { return _cryptoRef.applySync(undefined, ['randomBytes', String(size)]); } + }), + }; + `); + + // setTimeout / sleep - bridged via applySyncPromise + const sleepRef: ivm.Reference<(ms: number) => Promise> = + new ivm.Reference((ms: number): Promise => { + return new Promise((resolve: () => void) => { + global.setTimeout(resolve, Math.min(ms, timeout)); + }); + }); + + await jail.set("_sleepRef", sleepRef); + + await context.eval(` + function setTimeout(fn, ms) { + _sleepRef.applySyncPromise(undefined, [ms || 0]); + if (typeof fn === 'function') fn(); + } + async function sleep(ms) { + await _sleepRef.applySyncPromise(undefined, [ms || 0]); + } + `); + + // Wrap user code in async IIFE and evaluate + const wrappedCode: string = `(async () => { ${code} })()`; - const returnVal: any = await vm.runInContext(script, sandbox, { - timeout: options.timeout || 5000, - }); // run the script + // Run with overall timeout covering both CPU and I/O wait + const resultPromise: Promise = context.eval(wrappedCode, { + promise: true, + timeout: timeout, + }); - return { - returnValue: returnVal, - logMessages, - }; + const overallTimeout: Promise = new Promise( + ( + _resolve: (value: never) => void, + reject: (reason: Error) => void, + ) => { + global.setTimeout(() => { + reject(new Error("Script execution timed out")); + }, timeout + 5000); // 5s grace period beyond isolate timeout + }, + ); + + let returnValue: unknown; + + const result: unknown = await Promise.race([ + resultPromise, + overallTimeout, + ]); + + // If the result is an ivm.Reference or ExternalCopy, extract the value + if (result && typeof result === "object" && "copy" in result) { + try { + returnValue = (result as ivm.Reference).copy(); + } catch { + returnValue = undefined; + } + } else { + returnValue = result; + } + + return { + returnValue, + logMessages, + }; + } finally { + if (!isolate.isDisposed) { + isolate.dispose(); + } + } } } diff --git a/Common/package-lock.json b/Common/package-lock.json index 595fd63448..15afcc8baa 100644 --- a/Common/package-lock.json +++ b/Common/package-lock.json @@ -62,6 +62,7 @@ "formik": "^2.4.6", "history": "^5.3.0", "ioredis": "^5.3.2", + "isolated-vm": "^6.0.2", "json2csv": "^5.0.7", "json5": "^2.2.3", "jsonwebtoken": "^9.0.0", @@ -6414,6 +6415,55 @@ "node": ">=6.0.0" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bn.js": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", @@ -7102,6 +7152,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -8174,6 +8230,21 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -8214,6 +8285,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deepmerge": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", @@ -8373,7 +8453,6 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -8628,6 +8707,15 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/engine.io": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", @@ -9005,6 +9093,15 @@ "node": ">= 0.8.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/expect": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", @@ -9466,6 +9563,12 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "11.3.2", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", @@ -9609,6 +9712,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -10115,6 +10224,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", @@ -10650,6 +10765,19 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isolated-vm": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-6.0.2.tgz", + "integrity": "sha512-Qw6AJuagG/VJuh2AIcSWmQPsAArti/L+lKhjXU+lyhYkbt3J57XZr+ZjgfTnOr4NJcY1r3f8f0eePS7MRGp+pg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -13760,6 +13888,18 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -13818,6 +13958,12 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/mlly": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", @@ -13922,6 +14068,12 @@ "node": ">= 10.16.0" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -13948,6 +14100,18 @@ "tslib": "^2.0.3" } }, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -14175,7 +14339,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -14762,6 +14925,32 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -14946,6 +15135,16 @@ "punycode": "^2.3.1" } }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -15166,6 +15365,30 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -16555,6 +16778,51 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -17522,6 +17790,48 @@ "url": "https://github.com/sponsors/dcastil" } }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tar-stream": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", @@ -17821,6 +18131,18 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/twilio": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/twilio/-/twilio-4.23.0.tgz", @@ -18891,7 +19213,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { diff --git a/Common/package.json b/Common/package.json index 1d723eda8e..459ced9c7d 100644 --- a/Common/package.json +++ b/Common/package.json @@ -101,6 +101,7 @@ "formik": "^2.4.6", "history": "^5.3.0", "ioredis": "^5.3.2", + "isolated-vm": "^6.0.2", "json2csv": "^5.0.7", "json5": "^2.2.3", "jsonwebtoken": "^9.0.0", diff --git a/HelmChart/Public/oneuptime/templates/_helpers.tpl b/HelmChart/Public/oneuptime/templates/_helpers.tpl index e6c71e49e0..dbd4a39676 100644 --- a/HelmChart/Public/oneuptime/templates/_helpers.tpl +++ b/HelmChart/Public/oneuptime/templates/_helpers.tpl @@ -198,6 +198,18 @@ Usage: {{- end }} {{- end }} +{{- define "oneuptime.env.registerProbeKey" }} +- name: REGISTER_PROBE_KEY + {{- if $.Values.registerProbeKey }} + value: {{ $.Values.registerProbeKey }} + {{- else }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-%s" $.Release.Name "secrets" }} + key: register-probe-key + {{- end }} +{{- end }} + {{- define "oneuptime.env.runtime" }} - name: VAPID_PRIVATE_KEY diff --git a/HelmChart/Public/oneuptime/templates/probe-ingest.yaml b/HelmChart/Public/oneuptime/templates/probe-ingest.yaml index 24d3eb3c09..46001257c3 100644 --- a/HelmChart/Public/oneuptime/templates/probe-ingest.yaml +++ b/HelmChart/Public/oneuptime/templates/probe-ingest.yaml @@ -112,6 +112,7 @@ spec: value: {{ $.Values.probeIngest.disableTelemetryCollection | quote }} - name: PROBE_INGEST_CONCURRENCY value: {{ $.Values.probeIngest.concurrency | squote }} + {{- include "oneuptime.env.registerProbeKey" (dict "Values" $.Values "Release" $.Release) | nindent 12 }} ports: - containerPort: {{ $.Values.probeIngest.ports.http }} protocol: TCP diff --git a/HelmChart/Public/oneuptime/templates/probe.yaml b/HelmChart/Public/oneuptime/templates/probe.yaml index 8bd31ef9f1..3c5de0fb93 100644 --- a/HelmChart/Public/oneuptime/templates/probe.yaml +++ b/HelmChart/Public/oneuptime/templates/probe.yaml @@ -131,7 +131,7 @@ spec: - name: NO_PROXY value: {{ $val.proxy.noProxy | squote }} {{- end }} - {{- include "oneuptime.env.oneuptimeSecret" (dict "Values" $.Values "Release" $.Release) | nindent 12 }} + {{- include "oneuptime.env.registerProbeKey" (dict "Values" $.Values "Release" $.Release) | nindent 12 }} ports: - containerPort: {{ if and $val.ports $val.ports.http }}{{ $val.ports.http }}{{ else }}3874{{ end }} protocol: TCP diff --git a/HelmChart/Public/oneuptime/templates/secrets.yaml b/HelmChart/Public/oneuptime/templates/secrets.yaml index 38465a9db7..6cf517a168 100644 --- a/HelmChart/Public/oneuptime/templates/secrets.yaml +++ b/HelmChart/Public/oneuptime/templates/secrets.yaml @@ -17,6 +17,13 @@ stringData: {{- else }} oneuptime-secret: {{ index (lookup "v1" "Secret" $.Release.Namespace (printf "%s-secrets" $.Release.Name)).data "oneuptime-secret" | b64dec }} {{- end }} + {{- if .Values.registerProbeKey }} + register-probe-key: {{ .Values.registerProbeKey | quote }} + {{- else if (index (lookup "v1" "Secret" $.Release.Namespace (printf "%s-secrets" $.Release.Name)).data "register-probe-key") }} + register-probe-key: {{ index (lookup "v1" "Secret" $.Release.Namespace (printf "%s-secrets" $.Release.Name)).data "register-probe-key" | b64dec }} + {{- else }} + register-probe-key: {{ randAlphaNum 32 | quote }} + {{- end }} {{- if .Values.encryptionSecret }} encryption-secret: {{ .Values.encryptionSecret | quote }} {{- else }} @@ -48,6 +55,11 @@ stringData: {{- else }} oneuptime-secret: {{ randAlphaNum 32 | quote }} {{- end }} + {{- if .Values.registerProbeKey }} + register-probe-key: {{ .Values.registerProbeKey | quote }} + {{- else }} + register-probe-key: {{ randAlphaNum 32 | quote }} + {{- end }} {{- if .Values.encryptionSecret }} encryption-secret: {{ .Values.encryptionSecret | quote }} {{- else }} diff --git a/HelmChart/Public/oneuptime/values.yaml b/HelmChart/Public/oneuptime/values.yaml index 26de1ad81b..296804e6d3 100644 --- a/HelmChart/Public/oneuptime/values.yaml +++ b/HelmChart/Public/oneuptime/values.yaml @@ -32,6 +32,7 @@ image: # Important: You do need to set this to a long random values if you're using OneUptime in production. # Please set this to string. oneuptimeSecret: +registerProbeKey: encryptionSecret: # External Secrets diff --git a/Probe/Services/Register.ts b/Probe/Services/Register.ts index fa25b40fbd..56de930094 100644 --- a/Probe/Services/Register.ts +++ b/Probe/Services/Register.ts @@ -15,10 +15,12 @@ import { JSONObject } from "Common/Types/JSON"; import ProbeStatusReport from "Common/Types/Probe/ProbeStatusReport"; import Sleep from "Common/Types/Sleep"; import API from "Common/Utils/API"; -import { HasClusterKey } from "Common/Server/EnvironmentConfig"; +import { + HasRegisterProbeKey, + RegisterProbeKey, +} from "Common/Server/EnvironmentConfig"; import LocalCache from "Common/Server/Infrastructure/LocalCache"; import logger from "Common/Server/Utils/Logger"; -import ClusterKeyAuthorization from "Common/Server/Middleware/ClusterKeyAuthorization"; import ProxyConfig from "../Utils/ProxyConfig"; export default class Register { @@ -117,7 +119,7 @@ export default class Register { } private static async _registerProbe(): Promise { - if (HasClusterKey) { + if (HasRegisterProbeKey) { const probeRegistrationUrl: URL = URL.fromString( PROBE_INGEST_URL.toString(), ).addRoute("/register"); @@ -131,7 +133,7 @@ export default class Register { probeKey: PROBE_KEY, probeName: PROBE_NAME, probeDescription: PROBE_DESCRIPTION, - clusterKey: ClusterKeyAuthorization.getClusterKey(), + registerProbeKey: RegisterProbeKey.toString(), }, options: { ...ProxyConfig.getRequestProxyAgents(probeRegistrationUrl), @@ -149,7 +151,7 @@ export default class Register { } else { // validate probe. if (!PROBE_ID) { - logger.error("PROBE_ID or ONEUPTIME_SECRET should be set"); + logger.error("PROBE_ID or REGISTER_PROBE_KEY should be set"); return process.exit(); } diff --git a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts index a85629fc50..67f790debf 100644 --- a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts @@ -1,16 +1,12 @@ import { PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS } from "../../../Config"; import ProxyConfig from "../../ProxyConfig"; -import BadDataException from "Common/Types/Exception/BadDataException"; -import ReturnResult from "Common/Types/IsolatedVM/ReturnResult"; import BrowserType from "Common/Types/Monitor/SyntheticMonitors/BrowserType"; import ScreenSizeType from "Common/Types/Monitor/SyntheticMonitors/ScreenSizeType"; import SyntheticMonitorResponse from "Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse"; import ObjectID from "Common/Types/ObjectID"; import logger from "Common/Server/Utils/Logger"; -import VMRunner from "Common/Server/Utils/VM/VMRunner"; -import { Browser, BrowserContext, Page, chromium, firefox } from "playwright"; -import LocalFile from "Common/Server/Utils/LocalFile"; -import os from "os"; +import { ChildProcess, fork } from "child_process"; +import path from "path"; export interface SyntheticMonitorOptions { monitorId?: ObjectID | undefined; @@ -20,24 +16,24 @@ export interface SyntheticMonitorOptions { retryCountOnError?: number | undefined; } -interface BrowserLaunchOptions { - executablePath?: string; +interface WorkerConfig { + script: string; + browserType: BrowserType; + screenSizeType: ScreenSizeType; + timeout: number; proxy?: { server: string; - username?: string; - password?: string; - bypass?: string; - }; - args?: string[]; - headless?: boolean; - devtools?: boolean; - timeout?: number; + username?: string | undefined; + password?: string | undefined; + } | undefined; } -interface BrowserSession { - browser: Browser; - context: BrowserContext; - page: Page; +interface WorkerResult { + logMessages: string[]; + scriptError?: string | undefined; + result?: unknown | undefined; + screenshots: Record; + executionTimeInMS: number; } export default class SyntheticMonitor { @@ -111,13 +107,70 @@ export default class SyntheticMonitor { return result; } + private static getSanitizedEnv(): Record { + // Only pass safe environment variables to the worker process. + // Explicitly exclude all secrets (DATABASE_PASSWORD, REDIS_PASSWORD, + // CLICKHOUSE_PASSWORD, ONEUPTIME_SECRET, ENCRYPTION_SECRET, BILLING_PRIVATE_KEY, etc.) + const safeKeys: string[] = [ + "PATH", + "HOME", + "NODE_ENV", + "PLAYWRIGHT_BROWSERS_PATH", + "HTTP_PROXY_URL", + "http_proxy", + "HTTPS_PROXY_URL", + "https_proxy", + "NO_PROXY", + "no_proxy", + ]; + + const env: Record = {}; + + for (const key of safeKeys) { + if (process.env[key]) { + env[key] = process.env[key]!; + } + } + + return env; + } + + private static getProxyConfig(): WorkerConfig["proxy"] | undefined { + if (!ProxyConfig.isProxyConfigured()) { + return undefined; + } + + const httpsProxyUrl: string | null = ProxyConfig.getHttpsProxyUrl(); + const httpProxyUrl: string | null = ProxyConfig.getHttpProxyUrl(); + const proxyUrl: string | null = httpsProxyUrl || httpProxyUrl; + + if (!proxyUrl) { + return undefined; + } + + const proxyConfig: WorkerConfig["proxy"] = { + server: proxyUrl, + }; + + try { + const parsedUrl: globalThis.URL = new URL(proxyUrl); + if (parsedUrl.username && parsedUrl.password) { + proxyConfig.username = parsedUrl.username; + proxyConfig.password = parsedUrl.password; + } + } catch (error) { + logger.warn(`Failed to parse proxy URL for authentication: ${error}`); + } + + return proxyConfig; + } + private static async executeByBrowserAndScreenSize(options: { script: string; browserType: BrowserType; screenSizeType: ScreenSizeType; }): Promise { if (!options) { - // this should never happen options = { script: "", browserType: BrowserType.Chromium, @@ -135,385 +188,116 @@ export default class SyntheticMonitor { screenSizeType: options.screenSizeType, }; - let browserSession: BrowserSession | null = null; + const timeout: number = PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS; + + const workerConfig: WorkerConfig = { + script: options.script, + browserType: options.browserType, + screenSizeType: options.screenSizeType, + timeout: timeout, + proxy: this.getProxyConfig(), + }; try { - let result: ReturnResult | null = null; - - const startTime: [number, number] = process.hrtime(); - - browserSession = await SyntheticMonitor.getPageByBrowserType({ - browserType: options.browserType, - screenSizeType: options.screenSizeType, - }); - - if (!browserSession) { - throw new BadDataException( - "Could not create Playwright browser session", - ); - } - - result = await VMRunner.runCodeInSandbox({ - code: options.script, - options: { - timeout: PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS, - args: {}, - context: { - browser: browserSession.browser, - page: browserSession.page, - screenSizeType: options.screenSizeType, - browserType: options.browserType, - }, - }, - }); - - const endTime: [number, number] = process.hrtime(startTime); - - const executionTimeInMS: number = Math.ceil( - (endTime[0] * 1000000000 + endTime[1]) / 1000000, + const workerResult: WorkerResult = await this.forkWorker( + workerConfig, + timeout, ); - scriptResult.executionTimeInMS = executionTimeInMS; - - scriptResult.logMessages = result.logMessages; - - if (result.returnValue?.screenshots) { - if (!scriptResult.screenshots) { - scriptResult.screenshots = {}; - } - - for (const screenshotName in result.returnValue.screenshots) { - if (!result.returnValue.screenshots[screenshotName]) { - continue; - } - - // check if this is of type Buffer. If it is not, continue. - - if ( - !(result.returnValue.screenshots[screenshotName] instanceof Buffer) - ) { - continue; - } - - const screenshotBuffer: Buffer = result.returnValue.screenshots[ - screenshotName - ] as Buffer; - scriptResult.screenshots[screenshotName] = - screenshotBuffer.toString("base64"); // convert screenshots to base 64 - } - } - - scriptResult.result = result?.returnValue?.data; + scriptResult.logMessages = workerResult.logMessages; + scriptResult.scriptError = workerResult.scriptError; + scriptResult.result = workerResult.result as typeof scriptResult.result; + scriptResult.screenshots = workerResult.screenshots; + scriptResult.executionTimeInMS = workerResult.executionTimeInMS; } catch (err: unknown) { logger.error(err); scriptResult.scriptError = (err as Error)?.message || (err as Error).toString(); - } finally { - // Always dispose browser session to prevent zombie processes - await SyntheticMonitor.disposeBrowserSession(browserSession); } return scriptResult; } - private static getViewportHeightAndWidth(options: { - screenSizeType: ScreenSizeType; - }): { - height: number; - width: number; - } { - let viewPortHeight: number = 0; - let viewPortWidth: number = 0; - - switch (options.screenSizeType) { - case ScreenSizeType.Desktop: - viewPortHeight = 1080; - viewPortWidth = 1920; - break; - case ScreenSizeType.Mobile: - viewPortHeight = 640; - viewPortWidth = 360; - break; - case ScreenSizeType.Tablet: - viewPortHeight = 768; - viewPortWidth = 1024; - break; - default: - viewPortHeight = 1080; - viewPortWidth = 1920; - break; - } - - return { height: viewPortHeight, width: viewPortWidth }; - } - - private static getPlaywrightBrowsersPath(): string { - return ( - process.env["PLAYWRIGHT_BROWSERS_PATH"] || - `${os.homedir()}/.cache/ms-playwright` - ); - } - - public static async getChromeExecutablePath(): Promise { - const browsersPath: string = this.getPlaywrightBrowsersPath(); - - const doesDirectoryExist: boolean = - await LocalFile.doesDirectoryExist(browsersPath); - if (!doesDirectoryExist) { - throw new BadDataException("Chrome executable path not found."); - } - - // get list of files in the directory - const directories: string[] = - await LocalFile.getListOfDirectories(browsersPath); - - if (directories.length === 0) { - throw new BadDataException("Chrome executable path not found."); - } - - const chromeInstallationName: string | undefined = directories.find( - (directory: string) => { - return directory.includes("chromium"); - }, - ); - - if (!chromeInstallationName) { - throw new BadDataException("Chrome executable path not found."); - } - - const chromeExecutableCandidates: Array = [ - `${browsersPath}/${chromeInstallationName}/chrome-linux/chrome`, - `${browsersPath}/${chromeInstallationName}/chrome-linux64/chrome`, - `${browsersPath}/${chromeInstallationName}/chrome64/chrome`, - `${browsersPath}/${chromeInstallationName}/chrome/chrome`, - ]; - - for (const executablePath of chromeExecutableCandidates) { - if (await LocalFile.doesFileExist(executablePath)) { - return executablePath; - } - } - - throw new BadDataException("Chrome executable path not found."); - } - - public static async getFirefoxExecutablePath(): Promise { - const browsersPath: string = this.getPlaywrightBrowsersPath(); - - const doesDirectoryExist: boolean = - await LocalFile.doesDirectoryExist(browsersPath); - if (!doesDirectoryExist) { - throw new BadDataException("Firefox executable path not found."); - } - - // get list of files in the directory - const directories: string[] = - await LocalFile.getListOfDirectories(browsersPath); - - if (directories.length === 0) { - throw new BadDataException("Firefox executable path not found."); - } - - const firefoxInstallationName: string | undefined = directories.find( - (directory: string) => { - return directory.includes("firefox"); - }, - ); - - if (!firefoxInstallationName) { - throw new BadDataException("Firefox executable path not found."); - } - - const firefoxExecutableCandidates: Array = [ - `${browsersPath}/${firefoxInstallationName}/firefox/firefox`, - `${browsersPath}/${firefoxInstallationName}/firefox-linux64/firefox`, - `${browsersPath}/${firefoxInstallationName}/firefox64/firefox`, - `${browsersPath}/${firefoxInstallationName}/firefox-64/firefox`, - ]; - - for (const executablePath of firefoxExecutableCandidates) { - if (await LocalFile.doesFileExist(executablePath)) { - return executablePath; - } - } - - throw new BadDataException("Firefox executable path not found."); - } - - private static async getPageByBrowserType(data: { - browserType: BrowserType; - screenSizeType: ScreenSizeType; - }): Promise { - const viewport: { - height: number; - width: number; - } = SyntheticMonitor.getViewportHeightAndWidth({ - screenSizeType: data.screenSizeType, - }); - - // Prepare browser launch options with proxy support - const baseOptions: BrowserLaunchOptions = {}; - - // Configure proxy if available - if (ProxyConfig.isProxyConfigured()) { - const httpsProxyUrl: string | null = ProxyConfig.getHttpsProxyUrl(); - const httpProxyUrl: string | null = ProxyConfig.getHttpProxyUrl(); - - // Prefer HTTPS proxy, fall back to HTTP proxy - const proxyUrl: string | null = httpsProxyUrl || httpProxyUrl; - - if (proxyUrl) { - baseOptions.proxy = { - server: proxyUrl, - }; - - // Extract username and password if present in proxy URL - try { - const parsedUrl: globalThis.URL = new URL(proxyUrl); - if (parsedUrl.username && parsedUrl.password) { - baseOptions.proxy.username = parsedUrl.username; - baseOptions.proxy.password = parsedUrl.password; - } - } catch (error) { - logger.warn(`Failed to parse proxy URL for authentication: ${error}`); - } - - logger.debug( - `Synthetic Monitor using proxy: ${proxyUrl} (HTTPS: ${Boolean(httpsProxyUrl)}, HTTP: ${Boolean(httpProxyUrl)})`, + private static async forkWorker( + config: WorkerConfig, + timeout: number, + ): Promise { + return new Promise( + ( + resolve: (value: WorkerResult) => void, + reject: (reason: Error) => void, + ) => { + // The worker file path. At runtime the compiled JS will be at the same + // relative location under the build output directory. + const workerPath: string = path.resolve( + __dirname, + "SyntheticMonitorWorker", ); - } - } - if (data.browserType === BrowserType.Chromium) { - const browser: Browser = await chromium.launch({ - executablePath: await this.getChromeExecutablePath(), - ...baseOptions, - }); - - const context: BrowserContext = await browser.newContext({ - viewport: { - width: viewport.width, - height: viewport.height, - }, - }); - - const page: Page = await context.newPage(); - - return { - browser, - context, - page, - }; - } - - if (data.browserType === BrowserType.Firefox) { - const browser: Browser = await firefox.launch({ - executablePath: await this.getFirefoxExecutablePath(), - ...baseOptions, - }); - - let context: BrowserContext | null = null; - - try { - context = await browser.newContext({ - viewport: { - width: viewport.width, - height: viewport.height, - }, + const child: ChildProcess = fork(workerPath, [], { + env: this.getSanitizedEnv(), + timeout: timeout + 30000, // fork-level timeout: script timeout + 30s for browser startup/shutdown + stdio: ["pipe", "pipe", "pipe", "ipc"], }); - const page: Page = await context.newPage(); + let resolved: boolean = false; - return { - browser, - context, - page, - }; - } catch (error) { - await SyntheticMonitor.safeCloseBrowserContext(context); - await SyntheticMonitor.safeCloseBrowser(browser); - throw error; - } - } + // Explicit kill timer as final safety net + const killTimer: ReturnType = global.setTimeout( + () => { + if (!resolved) { + resolved = true; + child.kill("SIGKILL"); + reject( + new Error( + "Synthetic monitor worker killed after timeout", + ), + ); + } + }, + timeout + 60000, // kill after script timeout + 60s + ); - throw new BadDataException("Invalid Browser Type."); - } + child.on("message", (result: WorkerResult) => { + if (!resolved) { + resolved = true; + global.clearTimeout(killTimer); + resolve(result); + } + }); - private static async disposeBrowserSession( - session: BrowserSession | null, - ): Promise { - if (!session) { - return; - } + child.on("error", (err: Error) => { + if (!resolved) { + resolved = true; + global.clearTimeout(killTimer); + reject(err); + } + }); - await SyntheticMonitor.safeClosePage(session.page); - await SyntheticMonitor.safeCloseBrowserContexts({ - browser: session.browser, - }); - await SyntheticMonitor.safeCloseBrowser(session.browser); - } + child.on("exit", (exitCode: number | null) => { + if (!resolved) { + resolved = true; + global.clearTimeout(killTimer); + if (exitCode !== 0) { + reject( + new Error( + `Synthetic monitor worker exited with code ${exitCode}`, + ), + ); + } else { + // Worker exited cleanly but didn't send a message — shouldn't happen + reject( + new Error( + "Synthetic monitor worker exited without sending results", + ), + ); + } + } + }); - private static async safeClosePage(page?: Page | null): Promise { - if (!page) { - return; - } - - try { - if (!page.isClosed()) { - await page.close(); - } - } catch (error) { - logger.warn( - `Failed to close Playwright page: ${(error as Error)?.message || error}`, - ); - } - } - - private static async safeCloseBrowserContext( - context?: BrowserContext | null, - ): Promise { - if (!context) { - return; - } - - try { - await context.close(); - } catch (error) { - logger.warn( - `Failed to close Playwright browser context: ${(error as Error)?.message || error}`, - ); - } - } - - private static async safeCloseBrowser( - browser?: Browser | null, - ): Promise { - if (!browser) { - return; - } - - try { - if (browser.isConnected()) { - await browser.close(); - } - } catch (error) { - logger.warn( - `Failed to close Playwright browser: ${(error as Error)?.message || error}`, - ); - } - } - - private static async safeCloseBrowserContexts(data: { - browser: Browser; - }): Promise { - if (!data.browser || !data.browser.contexts) { - return; - } - - const contexts: Array = data.browser.contexts(); - - for (const context of contexts) { - await SyntheticMonitor.safeCloseBrowserContext(context); - } + // Send config to worker via IPC + child.send(config); + }, + ); } } diff --git a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts new file mode 100644 index 0000000000..7e61eb7dae --- /dev/null +++ b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts @@ -0,0 +1,339 @@ +// This script is executed via child_process.fork() with a sanitized environment +// It launches Playwright, runs user code with node:vm (safe because env is stripped), +// and sends results back via IPC. + +import BrowserType from "Common/Types/Monitor/SyntheticMonitors/BrowserType"; +import ScreenSizeType from "Common/Types/Monitor/SyntheticMonitors/ScreenSizeType"; +import vm, { Context } from "node:vm"; +import { Browser, BrowserContext, Page, chromium, firefox } from "playwright"; +import LocalFile from "Common/Server/Utils/LocalFile"; +import os from "os"; + +interface WorkerConfig { + script: string; + browserType: BrowserType; + screenSizeType: ScreenSizeType; + timeout: number; + proxy?: { + server: string; + username?: string | undefined; + password?: string | undefined; + } | undefined; +} + +interface WorkerResult { + logMessages: string[]; + scriptError?: string | undefined; + result?: unknown | undefined; + screenshots: Record; + executionTimeInMS: number; +} + +interface ProxyOptions { + server: string; + username?: string | undefined; + password?: string | undefined; +} + +function getViewportHeightAndWidth(screenSizeType: ScreenSizeType): { + height: number; + width: number; +} { + switch (screenSizeType) { + case ScreenSizeType.Desktop: + return { height: 1080, width: 1920 }; + case ScreenSizeType.Mobile: + return { height: 640, width: 360 }; + case ScreenSizeType.Tablet: + return { height: 768, width: 1024 }; + default: + return { height: 1080, width: 1920 }; + } +} + +function getPlaywrightBrowsersPath(): string { + return ( + process.env["PLAYWRIGHT_BROWSERS_PATH"] || + `${os.homedir()}/.cache/ms-playwright` + ); +} + +async function getChromeExecutablePath(): Promise { + const browsersPath: string = getPlaywrightBrowsersPath(); + + const doesDirectoryExist: boolean = + await LocalFile.doesDirectoryExist(browsersPath); + if (!doesDirectoryExist) { + throw new Error("Chrome executable path not found."); + } + + const directories: string[] = + await LocalFile.getListOfDirectories(browsersPath); + + if (directories.length === 0) { + throw new Error("Chrome executable path not found."); + } + + const chromeInstallationName: string | undefined = directories.find( + (directory: string) => { + return directory.includes("chromium"); + }, + ); + + if (!chromeInstallationName) { + throw new Error("Chrome executable path not found."); + } + + const candidates: Array = [ + `${browsersPath}/${chromeInstallationName}/chrome-linux/chrome`, + `${browsersPath}/${chromeInstallationName}/chrome-linux64/chrome`, + `${browsersPath}/${chromeInstallationName}/chrome64/chrome`, + `${browsersPath}/${chromeInstallationName}/chrome/chrome`, + ]; + + for (const executablePath of candidates) { + if (await LocalFile.doesFileExist(executablePath)) { + return executablePath; + } + } + + throw new Error("Chrome executable path not found."); +} + +async function getFirefoxExecutablePath(): Promise { + const browsersPath: string = getPlaywrightBrowsersPath(); + + const doesDirectoryExist: boolean = + await LocalFile.doesDirectoryExist(browsersPath); + if (!doesDirectoryExist) { + throw new Error("Firefox executable path not found."); + } + + const directories: string[] = + await LocalFile.getListOfDirectories(browsersPath); + + if (directories.length === 0) { + throw new Error("Firefox executable path not found."); + } + + const firefoxInstallationName: string | undefined = directories.find( + (directory: string) => { + return directory.includes("firefox"); + }, + ); + + if (!firefoxInstallationName) { + throw new Error("Firefox executable path not found."); + } + + const candidates: Array = [ + `${browsersPath}/${firefoxInstallationName}/firefox/firefox`, + `${browsersPath}/${firefoxInstallationName}/firefox-linux64/firefox`, + `${browsersPath}/${firefoxInstallationName}/firefox64/firefox`, + `${browsersPath}/${firefoxInstallationName}/firefox-64/firefox`, + ]; + + for (const executablePath of candidates) { + if (await LocalFile.doesFileExist(executablePath)) { + return executablePath; + } + } + + throw new Error("Firefox executable path not found."); +} + +async function launchBrowser( + config: WorkerConfig, +): Promise<{ browser: Browser; context: BrowserContext; page: Page }> { + const viewport: { height: number; width: number } = + getViewportHeightAndWidth(config.screenSizeType); + + let proxyOptions: ProxyOptions | undefined; + + if (config.proxy) { + proxyOptions = { + server: config.proxy.server, + }; + + if (config.proxy.username && config.proxy.password) { + proxyOptions.username = config.proxy.username; + proxyOptions.password = config.proxy.password; + } + } + + let browser: Browser; + + if (config.browserType === BrowserType.Chromium) { + const launchOptions: Record = { + executablePath: await getChromeExecutablePath(), + }; + + if (proxyOptions) { + launchOptions["proxy"] = proxyOptions; + } + + browser = await chromium.launch(launchOptions); + } else if (config.browserType === BrowserType.Firefox) { + const launchOptions: Record = { + executablePath: await getFirefoxExecutablePath(), + }; + + if (proxyOptions) { + launchOptions["proxy"] = proxyOptions; + } + + browser = await firefox.launch(launchOptions); + } else { + throw new Error("Invalid Browser Type."); + } + + const context: BrowserContext = await browser.newContext({ + viewport: { + width: viewport.width, + height: viewport.height, + }, + }); + + const page: Page = await context.newPage(); + + return { browser, context, page }; +} + +async function run(config: WorkerConfig): Promise { + const workerResult: WorkerResult = { + logMessages: [], + scriptError: undefined, + result: undefined, + screenshots: {}, + executionTimeInMS: 0, + }; + + let browser: Browser | null = null; + + try { + const startTime: [number, number] = process.hrtime(); + + const session: { browser: Browser; context: BrowserContext; page: Page } = + await launchBrowser(config); + + browser = session.browser; + + const logMessages: string[] = []; + + const sandbox: Context = { + console: { + log: (...args: unknown[]) => { + logMessages.push( + args + .map((v: unknown) => { + return typeof v === "object" ? JSON.stringify(v) : String(v); + }) + .join(" "), + ); + }, + }, + browser: session.browser, + page: session.page, + screenSizeType: config.screenSizeType, + browserType: config.browserType, + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + }; + + vm.createContext(sandbox); + + const script: string = `(async()=>{ + ${config.script} + })()`; + + const returnVal: unknown = await vm.runInContext(script, sandbox, { + timeout: config.timeout, + }); + + const endTime: [number, number] = process.hrtime(startTime); + const executionTimeInMS: number = Math.ceil( + (endTime[0] * 1000000000 + endTime[1]) / 1000000, + ); + + workerResult.executionTimeInMS = executionTimeInMS; + workerResult.logMessages = logMessages; + + // Convert screenshots from Buffer to base64 + const returnObj: Record = + returnVal && typeof returnVal === "object" + ? (returnVal as Record) + : {}; + + if (returnObj["screenshots"]) { + const screenshots: Record = returnObj[ + "screenshots" + ] as Record; + + for (const screenshotName in screenshots) { + if (!screenshots[screenshotName]) { + continue; + } + + if (!(screenshots[screenshotName] instanceof Buffer)) { + continue; + } + + const screenshotBuffer: Buffer = screenshots[ + screenshotName + ] as Buffer; + workerResult.screenshots[screenshotName] = + screenshotBuffer.toString("base64"); + } + } + + workerResult.result = returnObj["data"]; + } catch (err: unknown) { + workerResult.scriptError = + (err as Error)?.message || String(err); + } finally { + // Close browser + if (browser) { + try { + const contexts: Array = browser.contexts(); + for (const ctx of contexts) { + try { + await ctx.close(); + } catch (_e: unknown) { + // ignore + } + } + if (browser.isConnected()) { + await browser.close(); + } + } catch (_e: unknown) { + // ignore cleanup errors + } + } + } + + return workerResult; +} + +// Entry point: receive config via IPC message +process.on("message", (config: WorkerConfig) => { + run(config) + .then((result: WorkerResult) => { + if (process.send) { + process.send(result); + } + process.exit(0); + }) + .catch((err: unknown) => { + if (process.send) { + process.send({ + logMessages: [], + scriptError: (err as Error)?.message || String(err), + result: undefined, + screenshots: {}, + executionTimeInMS: 0, + } as WorkerResult); + } + process.exit(1); + }); +}); diff --git a/ProbeIngest/API/Register.ts b/ProbeIngest/API/Register.ts index 22c1c6eabd..4b67e334fa 100644 --- a/ProbeIngest/API/Register.ts +++ b/ProbeIngest/API/Register.ts @@ -1,7 +1,7 @@ import OneUptimeDate from "Common/Types/Date"; import BadDataException from "Common/Types/Exception/BadDataException"; import { JSONObject } from "Common/Types/JSON"; -import ClusterKeyAuthorization from "Common/Server/Middleware/ClusterKeyAuthorization"; +import { RegisterProbeKey } from "Common/Server/EnvironmentConfig"; import ProbeService from "Common/Server/Services/ProbeService"; import Express, { ExpressRequest, @@ -19,7 +19,6 @@ const router: ExpressRouter = Express.getRouter(); // Register Global Probe. Custom Probe can be registered via dashboard. router.post( "/register", - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, async ( req: ExpressRequest, res: ExpressResponse, @@ -28,6 +27,23 @@ router.post( try { const data: JSONObject = req.body; + const registerProbeKey: string | undefined = data[ + "registerProbeKey" + ] as string; + + if ( + !registerProbeKey || + registerProbeKey !== RegisterProbeKey.toString() + ) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Invalid or missing registerProbeKey. Please check REGISTER_PROBE_KEY environment variable.", + ), + ); + } + if (!data["probeKey"]) { return Response.sendErrorResponse( req, diff --git a/config.example.env b/config.example.env index b7bee145e7..d8c6a1d0a9 100644 --- a/config.example.env +++ b/config.example.env @@ -22,6 +22,7 @@ CAPTCHA_SECRET_KEY= # Secrets - PLEASE CHANGE THESE. Please change these to something random. All of these can be different values. ONEUPTIME_SECRET=please-change-this-to-random-value +REGISTER_PROBE_KEY=please-change-this-to-random-value DATABASE_PASSWORD=please-change-this-to-random-value CLICKHOUSE_PASSWORD=please-change-this-to-random-value REDIS_PASSWORD=please-change-this-to-random-value diff --git a/docker-compose.base.yml b/docker-compose.base.yml index 482a6cd757..b7212c9cb7 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -400,7 +400,7 @@ services: network_mode: host environment: ONEUPTIME_URL: ${GLOBAL_PROBE_1_ONEUPTIME_URL} - ONEUPTIME_SECRET: ${ONEUPTIME_SECRET} + REGISTER_PROBE_KEY: ${REGISTER_PROBE_KEY} PROBE_NAME: ${GLOBAL_PROBE_1_NAME} PROBE_DESCRIPTION: ${GLOBAL_PROBE_1_DESCRIPTION} PROBE_MONITORING_WORKERS: ${GLOBAL_PROBE_1_MONITORING_WORKERS} @@ -424,7 +424,7 @@ services: network_mode: host environment: ONEUPTIME_URL: ${GLOBAL_PROBE_2_ONEUPTIME_URL} - ONEUPTIME_SECRET: ${ONEUPTIME_SECRET} + REGISTER_PROBE_KEY: ${REGISTER_PROBE_KEY} PROBE_NAME: ${GLOBAL_PROBE_2_NAME} PROBE_DESCRIPTION: ${GLOBAL_PROBE_2_DESCRIPTION} PROBE_MONITORING_WORKERS: ${GLOBAL_PROBE_2_MONITORING_WORKERS} @@ -515,6 +515,7 @@ services: PORT: ${PROBE_INGEST_PORT} DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE_INGEST} PROBE_INGEST_CONCURRENCY: ${PROBE_INGEST_CONCURRENCY} + REGISTER_PROBE_KEY: ${REGISTER_PROBE_KEY} logging: driver: "local" options: From 54754ff5ae2c40016cdd2725516c13a6b0503780 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 14:15:25 +0000 Subject: [PATCH 39/44] feat: add registerProbeKey to values schema --- HelmChart/Public/oneuptime/values.schema.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/HelmChart/Public/oneuptime/values.schema.json b/HelmChart/Public/oneuptime/values.schema.json index fcc40fb6e7..1bfd4a042e 100644 --- a/HelmChart/Public/oneuptime/values.schema.json +++ b/HelmChart/Public/oneuptime/values.schema.json @@ -35,6 +35,9 @@ "oneuptimeSecret": { "type": ["string", "null"] }, + "registerProbeKey": { + "type": ["string", "null"] + }, "encryptionSecret": { "type": ["string", "null"] }, From 509a010261a44d76ea5a703e62f788b1819b9a57 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 14:22:39 +0000 Subject: [PATCH 40/44] feat: enhance sandbox execution by wrapping user code in async IIFE and handling non-cloneable return values --- Common/Server/Utils/VM/VMRunner.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Common/Server/Utils/VM/VMRunner.ts b/Common/Server/Utils/VM/VMRunner.ts index 85c56daff6..94a6b7b0e0 100644 --- a/Common/Server/Utils/VM/VMRunner.ts +++ b/Common/Server/Utils/VM/VMRunner.ts @@ -189,9 +189,16 @@ export default class VMRunner { } `); - // Wrap user code in async IIFE and evaluate + // Wrap user code in async IIFE. JSON.stringify the return value inside + // the isolate so only a plain string crosses the boundary — this avoids + // "A non-transferable value was passed" errors when user code returns + // objects containing functions, class instances, or other non-cloneable types. const wrappedCode: string = `(async () => { - ${code} + const __result = await (async () => { + ${code} + })(); + try { return JSON.stringify(__result); } + catch(_) { return undefined; } })()`; // Run with overall timeout covering both CPU and I/O wait @@ -211,19 +218,19 @@ export default class VMRunner { }, ); - let returnValue: unknown; - const result: unknown = await Promise.race([ resultPromise, overallTimeout, ]); - // If the result is an ivm.Reference or ExternalCopy, extract the value - if (result && typeof result === "object" && "copy" in result) { + // Parse the JSON string returned from inside the isolate + let returnValue: unknown; + + if (typeof result === "string") { try { - returnValue = (result as ivm.Reference).copy(); + returnValue = JSON.parse(result); } catch { - returnValue = undefined; + returnValue = result; } } else { returnValue = result; From d89be8ed57791d1d5d1534b23bebf3c8cda330a8 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 15:00:25 +0000 Subject: [PATCH 41/44] feat: enhance error handling and process exit flow in SyntheticMonitorWorker --- .../Monitors/MonitorTypes/SyntheticMonitor.ts | 20 +++++++++--- .../MonitorTypes/SyntheticMonitorWorker.ts | 31 +++++++++++++------ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts index 67f790debf..30d9355b3d 100644 --- a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts @@ -227,8 +227,6 @@ export default class SyntheticMonitor { resolve: (value: WorkerResult) => void, reject: (reason: Error) => void, ) => { - // The worker file path. At runtime the compiled JS will be at the same - // relative location under the build output directory. const workerPath: string = path.resolve( __dirname, "SyntheticMonitorWorker", @@ -241,6 +239,14 @@ export default class SyntheticMonitor { }); let resolved: boolean = false; + let stderrOutput: string = ""; + + // Capture child stderr for debugging worker crashes + if (child.stderr) { + child.stderr.on("data", (data: Buffer) => { + stderrOutput += data.toString(); + }); + } // Explicit kill timer as final safety net const killTimer: ReturnType = global.setTimeout( @@ -278,17 +284,21 @@ export default class SyntheticMonitor { if (!resolved) { resolved = true; global.clearTimeout(killTimer); + + const stderrInfo: string = stderrOutput.trim() + ? `: ${stderrOutput.trim().substring(0, 500)}` + : ""; + if (exitCode !== 0) { reject( new Error( - `Synthetic monitor worker exited with code ${exitCode}`, + `Synthetic monitor worker exited with code ${exitCode}${stderrInfo}`, ), ); } else { - // Worker exited cleanly but didn't send a message — shouldn't happen reject( new Error( - "Synthetic monitor worker exited without sending results", + `Synthetic monitor worker exited without sending results${stderrInfo}`, ), ); } diff --git a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts index 7e61eb7dae..96bb11fc11 100644 --- a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts +++ b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts @@ -320,20 +320,31 @@ process.on("message", (config: WorkerConfig) => { run(config) .then((result: WorkerResult) => { if (process.send) { - process.send(result); + // Wait for the IPC message to be flushed before exiting. + // process.send() is async — calling process.exit() immediately + // can kill the process before the message is delivered. + process.send(result, () => { + process.exit(0); + }); + } else { + process.exit(0); } - process.exit(0); }) .catch((err: unknown) => { + const errorResult: WorkerResult = { + logMessages: [], + scriptError: (err as Error)?.message || String(err), + result: undefined, + screenshots: {}, + executionTimeInMS: 0, + }; + if (process.send) { - process.send({ - logMessages: [], - scriptError: (err as Error)?.message || String(err), - result: undefined, - screenshots: {}, - executionTimeInMS: 0, - } as WorkerResult); + process.send(errorResult, () => { + process.exit(1); + }); + } else { + process.exit(1); } - process.exit(1); }); }); From a31f9d9d28d581a1c798092c26fe828e766fc0b8 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 15:02:24 +0000 Subject: [PATCH 42/44] refactor: clean up code formatting and improve readability across multiple files --- App/FeatureSet/Notification/API/PushRelay.ts | 17 ++++--- .../Services/PushNotificationService.ts | 3 +- Common/Server/Utils/VM/VMRunner.ts | 26 ++++------- MobileApp/src/notifications/setup.ts | 7 ++- .../Monitors/MonitorTypes/SyntheticMonitor.ts | 24 +++++----- .../MonitorTypes/SyntheticMonitorWorker.ts | 44 ++++++++++--------- 6 files changed, 60 insertions(+), 61 deletions(-) diff --git a/App/FeatureSet/Notification/API/PushRelay.ts b/App/FeatureSet/Notification/API/PushRelay.ts index adcd5a1bf0..7cb8d443c1 100644 --- a/App/FeatureSet/Notification/API/PushRelay.ts +++ b/App/FeatureSet/Notification/API/PushRelay.ts @@ -33,14 +33,17 @@ function isRateLimited(ip: string): boolean { } // Clean up stale rate limit entries every 5 minutes -setInterval(() => { - const now: number = Date.now(); - for (const [ip, entry] of rateLimitMap.entries()) { - if (now > entry.resetTime) { - rateLimitMap.delete(ip); +setInterval( + () => { + const now: number = Date.now(); + for (const [ip, entry] of rateLimitMap.entries()) { + if (now > entry.resetTime) { + rateLimitMap.delete(ip); + } } - } -}, 5 * 60 * 1000); + }, + 5 * 60 * 1000, +); router.post( "/send", diff --git a/Common/Server/Services/PushNotificationService.ts b/Common/Server/Services/PushNotificationService.ts index b7c03ee71e..0f278a0304 100644 --- a/Common/Server/Services/PushNotificationService.ts +++ b/Common/Server/Services/PushNotificationService.ts @@ -497,8 +497,7 @@ export default class PushNotificationService { body: data.body || "", data: data.data || {}, sound: (data.sound as "default" | null) || "default", - priority: - (data.priority as "default" | "normal" | "high") || "high", + priority: (data.priority as "default" | "normal" | "high") || "high", channelId: data.channelId || "default", }; diff --git a/Common/Server/Utils/VM/VMRunner.ts b/Common/Server/Utils/VM/VMRunner.ts index 94a6b7b0e0..8140abfcff 100644 --- a/Common/Server/Utils/VM/VMRunner.ts +++ b/Common/Server/Utils/VM/VMRunner.ts @@ -45,10 +45,7 @@ export default class VMRunner { // args - deep copy into isolate if (options.args) { - await jail.set( - "_args", - new ivm.ExternalCopy(options.args).copyInto(), - ); + await jail.set("_args", new ivm.ExternalCopy(options.args).copyInto()); await context.eval("const args = _args;"); } else { await context.eval("const args = {};"); @@ -56,11 +53,7 @@ export default class VMRunner { // axios (get, post, put, delete) - bridged via applySyncPromise const axiosRef: ivm.Reference< - ( - method: string, - url: string, - dataOrConfig?: string, - ) => Promise + (method: string, url: string, dataOrConfig?: string) => Promise > = new ivm.Reference( async ( method: string, @@ -189,10 +182,12 @@ export default class VMRunner { } `); - // Wrap user code in async IIFE. JSON.stringify the return value inside - // the isolate so only a plain string crosses the boundary — this avoids - // "A non-transferable value was passed" errors when user code returns - // objects containing functions, class instances, or other non-cloneable types. + /* + * Wrap user code in async IIFE. JSON.stringify the return value inside + * the isolate so only a plain string crosses the boundary — this avoids + * "A non-transferable value was passed" errors when user code returns + * objects containing functions, class instances, or other non-cloneable types. + */ const wrappedCode: string = `(async () => { const __result = await (async () => { ${code} @@ -208,10 +203,7 @@ export default class VMRunner { }); const overallTimeout: Promise = new Promise( - ( - _resolve: (value: never) => void, - reject: (reason: Error) => void, - ) => { + (_resolve: (value: never) => void, reject: (reason: Error) => void) => { global.setTimeout(() => { reject(new Error("Script execution timed out")); }, timeout + 5000); // 5s grace period beyond isolate timeout diff --git a/MobileApp/src/notifications/setup.ts b/MobileApp/src/notifications/setup.ts index b5a2e5b65a..004498db93 100644 --- a/MobileApp/src/notifications/setup.ts +++ b/MobileApp/src/notifications/setup.ts @@ -114,10 +114,9 @@ export async function requestPermissionsAndGetToken(): Promise { } try { - const tokenData: ExpoPushToken = - await Notifications.getExpoPushTokenAsync({ - projectId, - }); + const tokenData: ExpoPushToken = await Notifications.getExpoPushTokenAsync({ + projectId, + }); return tokenData.data; } catch (error: unknown) { diff --git a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts index 30d9355b3d..0cc9f5a801 100644 --- a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts @@ -21,11 +21,13 @@ interface WorkerConfig { browserType: BrowserType; screenSizeType: ScreenSizeType; timeout: number; - proxy?: { - server: string; - username?: string | undefined; - password?: string | undefined; - } | undefined; + proxy?: + | { + server: string; + username?: string | undefined; + password?: string | undefined; + } + | undefined; } interface WorkerResult { @@ -108,9 +110,11 @@ export default class SyntheticMonitor { } private static getSanitizedEnv(): Record { - // Only pass safe environment variables to the worker process. - // Explicitly exclude all secrets (DATABASE_PASSWORD, REDIS_PASSWORD, - // CLICKHOUSE_PASSWORD, ONEUPTIME_SECRET, ENCRYPTION_SECRET, BILLING_PRIVATE_KEY, etc.) + /* + * Only pass safe environment variables to the worker process. + * Explicitly exclude all secrets (DATABASE_PASSWORD, REDIS_PASSWORD, + * CLICKHOUSE_PASSWORD, ONEUPTIME_SECRET, ENCRYPTION_SECRET, BILLING_PRIVATE_KEY, etc.) + */ const safeKeys: string[] = [ "PATH", "HOME", @@ -255,9 +259,7 @@ export default class SyntheticMonitor { resolved = true; child.kill("SIGKILL"); reject( - new Error( - "Synthetic monitor worker killed after timeout", - ), + new Error("Synthetic monitor worker killed after timeout"), ); } }, diff --git a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts index 96bb11fc11..a4d3e339be 100644 --- a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts +++ b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts @@ -1,6 +1,8 @@ -// This script is executed via child_process.fork() with a sanitized environment -// It launches Playwright, runs user code with node:vm (safe because env is stripped), -// and sends results back via IPC. +/* + * This script is executed via child_process.fork() with a sanitized environment + * It launches Playwright, runs user code with node:vm (safe because env is stripped), + * and sends results back via IPC. + */ import BrowserType from "Common/Types/Monitor/SyntheticMonitors/BrowserType"; import ScreenSizeType from "Common/Types/Monitor/SyntheticMonitors/ScreenSizeType"; @@ -14,11 +16,13 @@ interface WorkerConfig { browserType: BrowserType; screenSizeType: ScreenSizeType; timeout: number; - proxy?: { - server: string; - username?: string | undefined; - password?: string | undefined; - } | undefined; + proxy?: + | { + server: string; + username?: string | undefined; + password?: string | undefined; + } + | undefined; } interface WorkerResult { @@ -145,8 +149,9 @@ async function getFirefoxExecutablePath(): Promise { async function launchBrowser( config: WorkerConfig, ): Promise<{ browser: Browser; context: BrowserContext; page: Page }> { - const viewport: { height: number; width: number } = - getViewportHeightAndWidth(config.screenSizeType); + const viewport: { height: number; width: number } = getViewportHeightAndWidth( + config.screenSizeType, + ); let proxyOptions: ProxyOptions | undefined; @@ -279,9 +284,7 @@ async function run(config: WorkerConfig): Promise { continue; } - const screenshotBuffer: Buffer = screenshots[ - screenshotName - ] as Buffer; + const screenshotBuffer: Buffer = screenshots[screenshotName] as Buffer; workerResult.screenshots[screenshotName] = screenshotBuffer.toString("base64"); } @@ -289,8 +292,7 @@ async function run(config: WorkerConfig): Promise { workerResult.result = returnObj["data"]; } catch (err: unknown) { - workerResult.scriptError = - (err as Error)?.message || String(err); + workerResult.scriptError = (err as Error)?.message || String(err); } finally { // Close browser if (browser) { @@ -299,14 +301,14 @@ async function run(config: WorkerConfig): Promise { for (const ctx of contexts) { try { await ctx.close(); - } catch (_e: unknown) { + } catch { // ignore } } if (browser.isConnected()) { await browser.close(); } - } catch (_e: unknown) { + } catch { // ignore cleanup errors } } @@ -320,9 +322,11 @@ process.on("message", (config: WorkerConfig) => { run(config) .then((result: WorkerResult) => { if (process.send) { - // Wait for the IPC message to be flushed before exiting. - // process.send() is async — calling process.exit() immediately - // can kill the process before the message is delivered. + /* + * Wait for the IPC message to be flushed before exiting. + * process.send() is async — calling process.exit() immediately + * can kill the process before the message is delivered. + */ process.send(result, () => { process.exit(0); }); From 87501d8d1b56f0c66e8f24fe67e4e33410c00f32 Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 15:08:30 +0000 Subject: [PATCH 43/44] feat: implement centralized logging utility and replace console statements in push notification handling --- MobileApp/src/api/pushDevice.ts | 7 ++++--- MobileApp/src/hooks/usePushNotifications.ts | 9 +++++---- MobileApp/src/notifications/setup.ts | 9 +++++---- MobileApp/src/utils/logger.ts | 19 +++++++++++++++++++ 4 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 MobileApp/src/utils/logger.ts diff --git a/MobileApp/src/api/pushDevice.ts b/MobileApp/src/api/pushDevice.ts index 9ff0f269e7..ab443d96f2 100644 --- a/MobileApp/src/api/pushDevice.ts +++ b/MobileApp/src/api/pushDevice.ts @@ -1,6 +1,7 @@ import { Platform } from "react-native"; import * as Device from "expo-device"; import apiClient from "./client"; +import logger from "../utils/logger"; export async function registerPushDevice(params: { deviceToken: string; @@ -20,7 +21,7 @@ export async function registerPushDevice(params: { deviceName: Device.modelName || "Unknown Device", projectId: params.projectId, }); - console.info( + logger.info( `[PushNotifications] Device registered successfully for project ${params.projectId}`, ); } catch (error: unknown) { @@ -33,14 +34,14 @@ export async function registerPushDevice(params: { // Treat "already registered" as success if (status === 400 && message.includes("already registered")) { - console.info( + logger.info( `[PushNotifications] Device already registered for project ${params.projectId}`, ); return; } // Log and re-throw other errors - console.error( + logger.error( `[PushNotifications] Registration failed (status=${status}): ${message}`, ); throw error; diff --git a/MobileApp/src/hooks/usePushNotifications.ts b/MobileApp/src/hooks/usePushNotifications.ts index 1e4a46f728..09949dd11e 100644 --- a/MobileApp/src/hooks/usePushNotifications.ts +++ b/MobileApp/src/hooks/usePushNotifications.ts @@ -15,6 +15,7 @@ import { registerPushDevice } from "../api/pushDevice"; import { useAuth } from "./useAuth"; import { useProject } from "./useProject"; import { PUSH_TOKEN_KEY } from "./pushTokenUtils"; +import logger from "../utils/logger"; const RETRY_DELAY_MS: number = 5000; const MAX_RETRIES: number = 3; @@ -58,7 +59,7 @@ export function usePushNotifications(navigationRef: unknown): void { if (!token && !cancelled) { attempt++; if (attempt < MAX_RETRIES) { - console.warn( + logger.warn( `[PushNotifications] Push token not available, retrying in ${RETRY_DELAY_MS}ms (attempt ${attempt}/${MAX_RETRIES})`, ); await new Promise((resolve: () => void): void => { @@ -70,7 +71,7 @@ export function usePushNotifications(navigationRef: unknown): void { if (!token || cancelled) { if (!token) { - console.warn( + logger.warn( "[PushNotifications] Could not obtain push token after all retries — device will not be registered", ); } @@ -90,7 +91,7 @@ export function usePushNotifications(navigationRef: unknown): void { projectId: project._id, }); } catch (error: unknown) { - console.warn( + logger.warn( `[PushNotifications] Failed to register device for project ${project._id}:`, error, ); @@ -99,7 +100,7 @@ export function usePushNotifications(navigationRef: unknown): void { }; register().catch((error: unknown): void => { - console.error( + logger.error( "[PushNotifications] Unexpected error during push registration:", error, ); diff --git a/MobileApp/src/notifications/setup.ts b/MobileApp/src/notifications/setup.ts index 004498db93..fb67d4ea9c 100644 --- a/MobileApp/src/notifications/setup.ts +++ b/MobileApp/src/notifications/setup.ts @@ -4,6 +4,7 @@ import * as Device from "expo-device"; import Constants from "expo-constants"; import { Platform } from "react-native"; import { PermissionStatus } from "expo-modules-core"; +import logger from "../utils/logger"; // Show notifications when app is in foreground Notifications.setNotificationHandler({ @@ -80,7 +81,7 @@ export async function setupNotificationCategories(): Promise { export async function requestPermissionsAndGetToken(): Promise { if (!Device.isDevice) { - console.warn( + logger.warn( "[PushNotifications] Not a physical device — skipping push token registration", ); return null; @@ -95,7 +96,7 @@ export async function requestPermissionsAndGetToken(): Promise { } if (finalStatus !== "granted") { - console.warn( + logger.warn( "[PushNotifications] Push notification permission not granted:", finalStatus, ); @@ -107,7 +108,7 @@ export async function requestPermissionsAndGetToken(): Promise { Constants.easConfig?.projectId; if (!projectId) { - console.warn( + logger.warn( "[PushNotifications] EAS project ID not found — cannot register for push notifications", ); return null; @@ -120,7 +121,7 @@ export async function requestPermissionsAndGetToken(): Promise { return tokenData.data; } catch (error: unknown) { - console.error("[PushNotifications] Failed to get push token:", error); + logger.error("[PushNotifications] Failed to get push token:", error); return null; } } diff --git a/MobileApp/src/utils/logger.ts b/MobileApp/src/utils/logger.ts new file mode 100644 index 0000000000..1a973956f1 --- /dev/null +++ b/MobileApp/src/utils/logger.ts @@ -0,0 +1,19 @@ +/* eslint-disable no-console */ + +const logger: { + info: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; +} = { + info: (...args: unknown[]): void => { + console.info(...args); + }, + warn: (...args: unknown[]): void => { + console.warn(...args); + }, + error: (...args: unknown[]): void => { + console.error(...args); + }, +}; + +export default logger; From 70a8c9974cf3b700ff6847a1fdaa2dbd3e8d536b Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Wed, 18 Feb 2026 15:17:52 +0000 Subject: [PATCH 44/44] feat: update base image to Node.js 24.9-alpine3.21 in Dockerfile --- IsolatedVM/Dockerfile.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IsolatedVM/Dockerfile.tpl b/IsolatedVM/Dockerfile.tpl index 1c9bd720da..5ba8e5c893 100644 --- a/IsolatedVM/Dockerfile.tpl +++ b/IsolatedVM/Dockerfile.tpl @@ -3,7 +3,7 @@ # # Pull base image nodejs image. -FROM public.ecr.aws/docker/library/node:21.6-alpine3.18 +FROM public.ecr.aws/docker/library/node:24.9-alpine3.21 RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global RUN npm config set fetch-retries 5