about summary refs log tree commit diff
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-03-06 05:55:34 +0000
committerGitHub <noreply@github.com>2024-03-06 05:55:34 +0000
commiteb298d2e60a0ddf26ebaf8f27373418bbf7769e3 (patch)
tree223bdfdeae951ccbda10c2a47eda0794191c4df0
parent26fc0cf66d0cde33e8105495785a1ce4248fb9f7 (diff)
downloadvoidsky-eb298d2e60a0ddf26ebaf8f27373418bbf7769e3.tar.zst
Initial feature gating and A/B testing integration (#3122)
* Add statsig dependency

* Add SDK provider

* Move to separate file, add tier and hashing

* Disable local storage for now

* Add initial gate testing fixture

* Fork for web just in case

* More WIP

* wip

* Rm test gate

* Add shim on native

* Clarify
-rw-r--r--package.json2
-rw-r--r--src/App.native.tsx33
-rw-r--r--src/App.web.tsx33
-rw-r--r--src/lib/statsig/statsig.tsx11
-rw-r--r--src/lib/statsig/statsig.web.tsx51
-rw-r--r--yarn.lock162
6 files changed, 259 insertions, 33 deletions
diff --git a/package.json b/package.json
index cd8cc7651..59ee33193 100644
--- a/package.json
+++ b/package.json
@@ -179,6 +179,8 @@
     "react-responsive": "^9.0.2",
     "rn-fetch-blob": "^0.12.0",
     "sentry-expo": "~7.0.1",
+    "statsig-react": "^1.36.0",
+    "statsig-react-native-expo": "^4.6.1",
     "tippy.js": "^6.3.7",
     "tlds": "^1.234.0",
     "use-deep-compare": "^1.1.0",
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 4da3f85f0..eff8ab099 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -43,6 +43,7 @@ import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unre
 import * as persisted from '#/state/persisted'
 import {Splash} from '#/Splash'
 import {Provider as PortalProvider} from '#/components/Portal'
+import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useIntentHandler} from 'lib/hooks/useIntentHandler'
@@ -77,21 +78,23 @@ function InnerApp() {
           <React.Fragment
             // Resets the entire tree below when it changes:
             key={currentAccount?.did}>
-            <LoggedOutViewProvider>
-              <SelectedFeedProvider>
-                <UnreadNotifsProvider>
-                  <ThemeProvider theme={theme}>
-                    {/* All components should be within this provider */}
-                    <RootSiblingParent>
-                      <GestureHandlerRootView style={s.h100pct}>
-                        <TestCtrls />
-                        <Shell />
-                      </GestureHandlerRootView>
-                    </RootSiblingParent>
-                  </ThemeProvider>
-                </UnreadNotifsProvider>
-              </SelectedFeedProvider>
-            </LoggedOutViewProvider>
+            <StatsigProvider>
+              <LoggedOutViewProvider>
+                <SelectedFeedProvider>
+                  <UnreadNotifsProvider>
+                    <ThemeProvider theme={theme}>
+                      {/* All components should be within this provider */}
+                      <RootSiblingParent>
+                        <GestureHandlerRootView style={s.h100pct}>
+                          <TestCtrls />
+                          <Shell />
+                        </GestureHandlerRootView>
+                      </RootSiblingParent>
+                    </ThemeProvider>
+                  </UnreadNotifsProvider>
+                </SelectedFeedProvider>
+              </LoggedOutViewProvider>
+            </StatsigProvider>
           </React.Fragment>
         </Splash>
       </Alf>
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 6ac32a011..eb2e42593 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -32,6 +32,7 @@ import {
 import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
 import * as persisted from '#/state/persisted'
 import {Provider as PortalProvider} from '#/components/Portal'
+import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
 import {useIntentHandler} from 'lib/hooks/useIntentHandler'
 
 function InnerApp() {
@@ -54,21 +55,23 @@ function InnerApp() {
       <React.Fragment
         // Resets the entire tree below when it changes:
         key={currentAccount?.did}>
-        <LoggedOutViewProvider>
-          <SelectedFeedProvider>
-            <UnreadNotifsProvider>
-              <ThemeProvider theme={theme}>
-                {/* All components should be within this provider */}
-                <RootSiblingParent>
-                  <SafeAreaProvider>
-                    <Shell />
-                  </SafeAreaProvider>
-                </RootSiblingParent>
-                <ToastContainer />
-              </ThemeProvider>
-            </UnreadNotifsProvider>
-          </SelectedFeedProvider>
-        </LoggedOutViewProvider>
+        <StatsigProvider>
+          <LoggedOutViewProvider>
+            <SelectedFeedProvider>
+              <UnreadNotifsProvider>
+                <ThemeProvider theme={theme}>
+                  {/* All components should be within this provider */}
+                  <RootSiblingParent>
+                    <SafeAreaProvider>
+                      <Shell />
+                    </SafeAreaProvider>
+                  </RootSiblingParent>
+                  <ToastContainer />
+                </ThemeProvider>
+              </UnreadNotifsProvider>
+            </SelectedFeedProvider>
+          </LoggedOutViewProvider>
+        </StatsigProvider>
       </React.Fragment>
     </Alf>
   )
diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx
new file mode 100644
index 000000000..88a57c3fc
--- /dev/null
+++ b/src/lib/statsig/statsig.tsx
@@ -0,0 +1,11 @@
+import React from 'react'
+
+export function useGate(_gateName: string) {
+  // Not enabled for native yet.
+  return false
+}
+
+export function Provider({children}: {children: React.ReactNode}) {
+  // Not enabled for native yet.
+  return children
+}
diff --git a/src/lib/statsig/statsig.web.tsx b/src/lib/statsig/statsig.web.tsx
new file mode 100644
index 000000000..6508131c4
--- /dev/null
+++ b/src/lib/statsig/statsig.web.tsx
@@ -0,0 +1,51 @@
+import React from 'react'
+import {StatsigProvider, useGate as useStatsigGate} from 'statsig-react'
+import {useSession} from '../../state/session'
+import {sha256} from 'js-sha256'
+
+const statsigOptions = {
+  environment: {
+    tier: process.env.NODE_ENV === 'development' ? 'development' : 'production',
+  },
+  // Don't block on waiting for network. The fetched config will kick in on next load.
+  // This ensures the UI is always consistent and doesn't update mid-session.
+  // Note this makes cold load (no local storage) and private mode return `false` for all gates.
+  initTimeoutMs: 1,
+}
+
+export function useGate(gateName: string) {
+  const {isLoading, value} = useStatsigGate(gateName)
+  if (isLoading) {
+    // This should not happen because of waitForInitialization={true}.
+    console.error('Did not expected isLoading to ever be true.')
+  }
+  return value
+}
+
+function toStatsigUser(did: string | undefined) {
+  let userID: string | undefined
+  if (did) {
+    userID = sha256(did)
+  }
+  return {userID}
+}
+
+export function Provider({children}: {children: React.ReactNode}) {
+  const {currentAccount} = useSession()
+  const currentStatsigUser = React.useMemo(
+    () => toStatsigUser(currentAccount?.did),
+    [currentAccount?.did],
+  )
+  return (
+    <StatsigProvider
+      sdkKey="client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV"
+      mountKey={currentStatsigUser.userID}
+      user={currentStatsigUser}
+      // This isn't really blocking due to short initTimeoutMs above.
+      // However, it ensures `isLoading` is always `false`.
+      waitForInitialization={true}
+      options={statsigOptions}>
+      {children}
+    </StatsigProvider>
+  )
+}
diff --git a/yarn.lock b/yarn.lock
index c3ddb8ee0..3add1af64 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3115,6 +3115,27 @@
     xcode "^3.0.1"
     xml2js "0.6.0"
 
+"@expo/config-plugins@~5.0.3":
+  version "5.0.4"
+  resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-5.0.4.tgz#216fea6558fe66615af1370de55193f4181cb23e"
+  integrity sha512-vzUcVpqOMs3h+hyRdhGwk+eGIOhXa5xYdd92yO17RMNHav3v/+ekMbs7XA2c3lepMO8Yd4/5hqmRw9ZTL6jGzg==
+  dependencies:
+    "@expo/config-types" "^47.0.0"
+    "@expo/json-file" "8.2.36"
+    "@expo/plist" "0.0.18"
+    "@expo/sdk-runtime-versions" "^1.0.0"
+    "@react-native/normalize-color" "^2.0.0"
+    chalk "^4.1.2"
+    debug "^4.3.1"
+    find-up "~5.0.0"
+    getenv "^1.0.0"
+    glob "7.1.6"
+    resolve-from "^5.0.0"
+    semver "^7.3.5"
+    slash "^3.0.0"
+    xcode "^3.0.1"
+    xml2js "0.4.23"
+
 "@expo/config-plugins@~7.8.2":
   version "7.8.2"
   resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.8.2.tgz#c00ce93c4d6c2cb9e345ed9cd56ceeea05ab8ddb"
@@ -3138,6 +3159,11 @@
     xcode "^3.0.1"
     xml2js "0.6.0"
 
+"@expo/config-types@^47.0.0":
+  version "47.0.0"
+  resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-47.0.0.tgz#99eeabe0bba7a776e0f252b78beb0c574692c38d"
+  integrity sha512-r0pWfuhkv7KIcXMUiNACJmJKKwlTBGMw9VZHNdppS8/0Nve8HZMTkNRFQzTHW1uH3pBj8jEXpyw/2vSWDHex9g==
+
 "@expo/config-types@^50.0.0", "@expo/config-types@^50.0.0-alpha.1":
   version "50.0.0"
   resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-50.0.0.tgz#b534d3ec997ec60f8af24f6ad56244c8afc71a0b"
@@ -3160,6 +3186,23 @@
     slugify "^1.3.4"
     sucrase "^3.20.0"
 
+"@expo/config@~7.0.0":
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/@expo/config/-/config-7.0.3.tgz#c9c634e76186de25e296485e51418f1e52966e6e"
+  integrity sha512-joVtB5o+NF40Tmsdp65UzryRtbnCuMbXkVO4wJnNJO4aaK0EYLdHCYSewORVqNcDfGN0LphQr8VTG2npbd9CJA==
+  dependencies:
+    "@babel/code-frame" "~7.10.4"
+    "@expo/config-plugins" "~5.0.3"
+    "@expo/config-types" "^47.0.0"
+    "@expo/json-file" "8.2.36"
+    getenv "^1.0.0"
+    glob "7.1.6"
+    require-from-string "^2.0.2"
+    resolve-from "^5.0.0"
+    semver "7.3.2"
+    slugify "^1.3.4"
+    sucrase "^3.20.0"
+
 "@expo/config@~8.5.0":
   version "8.5.0"
   resolved "https://registry.yarnpkg.com/@expo/config/-/config-8.5.0.tgz#c618e016c3272335e33fec02fb7fd67e4dcc3342"
@@ -3259,6 +3302,15 @@
     semver "7.3.2"
     tempy "0.3.0"
 
+"@expo/json-file@8.2.36":
+  version "8.2.36"
+  resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.36.tgz#62a505cb7f30a34d097386476794680a3f7385ff"
+  integrity sha512-tOZfTiIFA5KmMpdW9KF7bc6CFiGjb0xnbieJhTGlHrLL+ps2G0OkqmuZ3pFEXBOMnJYUVpnSy++52LFxvpa5ZQ==
+  dependencies:
+    "@babel/code-frame" "~7.10.4"
+    json5 "^1.0.1"
+    write-file-atomic "^2.3.0"
+
 "@expo/json-file@^8.2.37":
   version "8.2.37"
   resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.37.tgz#9c02d3b42134907c69cc0a027b18671b69344049"
@@ -3328,6 +3380,15 @@
     split "^1.0.1"
     sudo-prompt "9.1.1"
 
+"@expo/plist@0.0.18":
+  version "0.0.18"
+  resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.0.18.tgz#9abcde78df703a88f6d9fa1a557ee2f045d178b0"
+  integrity sha512-+48gRqUiz65R21CZ/IXa7RNBXgAI/uPSdvJqoN9x1hfL44DNbUoWHgHiEXTx7XelcATpDwNTz6sHLfy0iNqf+w==
+  dependencies:
+    "@xmldom/xmldom" "~0.7.0"
+    base64-js "^1.2.3"
+    xmlbuilder "^14.0.0"
+
 "@expo/plist@^0.1.0":
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.1.0.tgz#eabc95f951d14e10c87fd0443ee01d567371f058"
@@ -4750,6 +4811,13 @@
   dependencies:
     merge-options "^3.0.4"
 
+"@react-native-async-storage/async-storage@^1.15.2":
+  version "1.22.0"
+  resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.22.0.tgz#202a9afd15a5b829c39b709d0ca3942612441efc"
+  integrity sha512-b5KD010iiZnot86RbAaHpLuHwmPW2qA3SSN/OSZhd1kBoINEQEVBuv+uFtcaTxAhX27bT0wd13GOb2IOSDUXSA==
+  dependencies:
+    merge-options "^3.0.4"
+
 "@react-native-camera-roll/camera-roll@^5.2.2":
   version "5.7.2"
   resolved "https://registry.yarnpkg.com/@react-native-camera-roll/camera-roll/-/camera-roll-5.7.2.tgz#db11525ae26c8a61630c424aebd323a7c784a921"
@@ -8124,7 +8192,7 @@
   resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
   integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
 
-"@xmldom/xmldom@~0.7.7":
+"@xmldom/xmldom@~0.7.0", "@xmldom/xmldom@~0.7.7":
   version "0.7.13"
   resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3"
   integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==
@@ -11737,6 +11805,14 @@ expo-camera@~14.0.1:
   dependencies:
     invariant "^2.2.4"
 
+expo-constants@^13.0.2:
+  version "13.2.4"
+  resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-13.2.4.tgz#eab4a553f074b2c60ad7a158d3b82e3484a94606"
+  integrity sha512-Zobau8EuTk2GgafwkfGnWM6CmSLB7X8qnQXVuXe0nd3v92hfQUmRWGhJwH88uxXj3LrfqctM6PaJ8taG1vxfBw==
+  dependencies:
+    "@expo/config" "~7.0.0"
+    uuid "^3.3.2"
+
 expo-constants@~15.4.0:
   version "15.4.1"
   resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-15.4.1.tgz#f76f347cf687b6630e1e3b9a385a4e42771671a4"
@@ -11786,6 +11862,13 @@ expo-dev-menu@4.5.3:
     expo-dev-menu-interface "1.7.2"
     semver "^7.5.3"
 
+expo-device@~4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/expo-device/-/expo-device-4.1.1.tgz#5de94144113ffb7fa0f37fa3d70e452113954c10"
+  integrity sha512-It0SGtKcvzQSf+Co6zdPdB63zZvG2/rDolB1lqswMNKj03Y7KVU41s5tcQCqNczj7tmeN3CJy7A8YhYGKdb7gA==
+  dependencies:
+    ua-parser-js "^0.7.19"
+
 expo-device@~5.9.2:
   version "5.9.2"
   resolved "https://registry.yarnpkg.com/expo-device/-/expo-device-5.9.2.tgz#697e96f52d213a141b6f265f1e274e9d5e98c92c"
@@ -14978,6 +15061,16 @@ js-queue@2.0.2:
   dependencies:
     easy-stack "^1.0.1"
 
+js-sha256@^0.10.1:
+  version "0.10.1"
+  resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.10.1.tgz#b40104ba1368e823fdd5f41b66b104b15a0da60d"
+  integrity sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==
+
+js-sha256@^0.11.0:
+  version "0.11.0"
+  resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.11.0.tgz#256a921d9292f7fe98905face82e367abaca9576"
+  integrity sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==
+
 js-sha256@^0.9.0:
   version "0.9.0"
   resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
@@ -15162,7 +15255,7 @@ json-stable-stringify-without-jsonify@^1.0.1:
   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
   integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
 
-json5@^1.0.2:
+json5@^1.0.1, json5@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
   integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
@@ -18503,6 +18596,13 @@ react-native-gesture-handler@~2.14.0:
     lodash "^4.17.21"
     prop-types "^15.7.2"
 
+react-native-get-random-values@^1.6.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.10.0.tgz#c2c5f12a4ef8b1175145347b4a4b9f9a40d9ffc8"
+  integrity sha512-gZ1zbXhbb8+Jy9qYTV8c4Nf45/VB4g1jmXuavY5rPfUn7x3ok9Vl3FTl0dnE92Z4FFtfbUNNwtSfcmomdtWg+A==
+  dependencies:
+    fast-base64-decode "^1.0.0"
+
 react-native-get-random-values@~1.8.0:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.8.0.tgz#1cb4bd4bd3966a356e59697b8f372999fe97cb16"
@@ -19976,6 +20076,49 @@ standard-as-callback@^2.1.0:
   resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
   integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
 
+statsig-js@4.45.1:
+  version "4.45.1"
+  resolved "https://registry.yarnpkg.com/statsig-js/-/statsig-js-4.45.1.tgz#b1f5b9c52adc4a8aece376fb011416c89227932f"
+  integrity sha512-h94RzFQsJCQCNwQXpZ9OBXcvCxDnkXF6OrCekd81ySvY2l4JSowpxMWX3Iw6IDFzfTfKdER9JQzFLhMSQbT+YQ==
+  dependencies:
+    js-sha256 "^0.10.1"
+    uuid "^8.3.2"
+
+statsig-js@4.49.0:
+  version "4.49.0"
+  resolved "https://registry.yarnpkg.com/statsig-js/-/statsig-js-4.49.0.tgz#8470a9ac218a93d36f4b7b306ff9377e48064740"
+  integrity sha512-N4drx6fzI168Q4NndFY3IJbSDqpWSBWvS290H/RnT/g3Et58SvtXzG5qqgzmqy4CwcmwH+IL8K15pL7hPnfvUQ==
+  dependencies:
+    js-sha256 "^0.11.0"
+    uuid "^8.3.2"
+
+statsig-react-native-expo@^4.6.1:
+  version "4.6.1"
+  resolved "https://registry.yarnpkg.com/statsig-react-native-expo/-/statsig-react-native-expo-4.6.1.tgz#0bdf49fee7112f7f28bff2405f4ba0c1727bb3d6"
+  integrity sha512-rB60c+WSrQPmjW9j75d+acUtwSOe38PE2KTDHiOv1Mf+0TCcFtGYlJmKCibWvbeXR7ZAyjjGeroh23bCSEZauQ==
+  dependencies:
+    "@react-native-async-storage/async-storage" "^1.15.2"
+    expo-constants "^13.0.2"
+    expo-device "~4.1.1"
+    js-sha256 "^0.9.0"
+    react-native-get-random-values "^1.6.0"
+    statsig-react "^1.21.1"
+    uuid "^8.3.2"
+
+statsig-react@^1.21.1:
+  version "1.35.0"
+  resolved "https://registry.yarnpkg.com/statsig-react/-/statsig-react-1.35.0.tgz#ad5730b83f564c640623e954fcbcbe848e939946"
+  integrity sha512-KLN7dhq6FvAl25Z0QN6IINFBgM3yn0GMafoE698tYZqRf911xvevFaR7qUXiTz3W9vmFYrmFRouqVMfCv7DW0A==
+  dependencies:
+    statsig-js "4.45.1"
+
+statsig-react@^1.36.0:
+  version "1.36.0"
+  resolved "https://registry.yarnpkg.com/statsig-react/-/statsig-react-1.36.0.tgz#c2171268a6c76eee534849ec9556b836baba04b6"
+  integrity sha512-QcTHla3ypfn2RvrnHGNlqWbiC2W/ZjcMM5LT6ExNV4skH7Xhspto3dMS3JVzBhOb74OEDZK4DbxQj9Wdz6XW0w==
+  dependencies:
+    statsig-js "4.49.0"
+
 statuses@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
@@ -20920,6 +21063,11 @@ typescript@^5.3.3:
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
   integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
 
+ua-parser-js@^0.7.19:
+  version "0.7.37"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.37.tgz#e464e66dac2d33a7a1251d7d7a99d6157ec27832"
+  integrity sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==
+
 ua-parser-js@^0.7.33:
   version "0.7.35"
   resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.35.tgz#8bda4827be4f0b1dda91699a29499575a1f1d307"
@@ -21201,7 +21349,7 @@ utils-merge@1.0.1:
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
   integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
-uuid@^3.0.1:
+uuid@^3.0.1, uuid@^3.3.2:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
   integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
@@ -21953,6 +22101,14 @@ xml-name-validator@^4.0.0:
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
   integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
 
+xml2js@0.4.23:
+  version "0.4.23"
+  resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
+  integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
+  dependencies:
+    sax ">=0.6.0"
+    xmlbuilder "~11.0.0"
+
 xml2js@0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.0.tgz#07afc447a97d2bd6507a1f76eeadddb09f7a8282"