diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index b40192464..f538f5b87 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -34,6 +34,7 @@ jobs: name: ${{ needs.set_environment.outputs.my_env }}-release steps: - name: Generate Heroku Config + id: heroku-config run: | cat < ~/.netrc machine api.heroku.com @@ -44,6 +45,16 @@ jobs: password ${{ secrets.HEROKU_API_TOKEN }} EOF - name: release modules + id: release-modules run: | heroku config:set MODULES_REPO_BRANCH=$GITHUB_REF_NAME -a ${{ secrets.HEROKU_APP }} - heroku run 'python manage.py update_crowdbotics_components --quiet --no-input --no-log-file --public' --size=standard-2x -a ${{ secrets.HEROKU_APP }} \ No newline at end of file + heroku run 'python manage.py update_crowdbotics_components --quiet --no-input --no-log-file --public' --size=standard-2x -a ${{ secrets.HEROKU_APP }} + + - uses: act10ns/slack@v2 + if: ${{ needs.set_environment.outputs.my_env }} == 'production' + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + \ No newline at end of file diff --git a/.github/workflows/create-github-release.yml b/.github/workflows/create-github-release.yml index c36f1d19b..db4df92f4 100644 --- a/.github/workflows/create-github-release.yml +++ b/.github/workflows/create-github-release.yml @@ -40,3 +40,4 @@ jobs: GIT_TOKEN: ${{ secrets.GIT_TOKEN }} JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }} release_branch: ${{ github.event.client_payload.release_branch }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/index.js b/index.js index 6c19f270e..0ab813e9c 100755 --- a/index.js +++ b/index.js @@ -28,7 +28,13 @@ import { info } from "./scripts/info.js"; import { removeModules } from "./scripts/remove.js"; import { commitModules } from "./scripts/commit-module.js"; import { upgradeScaffold } from "./scripts/upgrade.js"; -import { valid, invalid, isNameValid, section } from "./utils.js"; +import { + valid, + invalid, + isNameValid, + section, + isUserEnvironment +} from "./utils.js"; import { createModule } from "./scripts/create.js"; import { login } from "./scripts/login.js"; import { configFile } from "./scripts/utils/configFile.js"; @@ -36,7 +42,12 @@ import { sendFeedback } from "./scripts/feedback.js"; import { logout } from "./scripts/logout.js"; import { modulesArchive, modulesGet, modulesList } from "./scripts/modules.js"; import { publish } from "./scripts/publish.js"; -import { Amplitude } from "./scripts/amplitude/wrapper.js"; +import { preExecuteChecks } from "./scripts/utils/environment.js"; +import { analytics } from "./scripts/analytics/wrapper.js"; +import { HAS_ASKED_OPT_IN_NAME } from "./scripts/analytics/config.js"; +import { EVENT } from "./scripts/analytics/constants.js"; +import { askOptIn } from "./scripts/analytics/scripts.js"; +import { sentryMonitoring } from "./scripts/utils/sentry.js"; const pkg = JSON.parse( fs.readFileSync(new URL("package.json", import.meta.url), "utf8") @@ -62,7 +73,15 @@ Visit our official documentation for more information and try again: https://doc } }; -function dispatcher() { +async function dispatcher() { + const useDefaults = process.env.npm_config_yes; + + // check config if they have been asked opted in or out of amplitude + const hasAskedOptIn = configFile.get(HAS_ASKED_OPT_IN_NAME) || false; + if (!hasAskedOptIn && isUserEnvironment && !useDefaults) { + await askOptIn(); + } + const command = process.argv[2]; if (!command) { @@ -73,11 +92,24 @@ function dispatcher() { invalid(`command doesn't exist: ${command}`); } - return commands[command](); + sentryMonitoring.registerCommandName(command); + + await commands[command](); + + if (!analytics.event.name && !useDefaults) { + analytics.sendEvent({ + name: EVENT.OTHER, + properties: { + command, + fullCommand: process.argv.slice(2, process.argv.length).join(" ") + } + }); + } } const commands = { demo: () => { + preExecuteChecks(); createDemo( path.join(gitRoot(), "demo"), path.join(sourceDir, "cookiecutter.yaml") @@ -107,6 +139,7 @@ const commands = { } }, add: () => { + preExecuteChecks(); const args = arg({ "--source": String, "--project": String @@ -129,6 +162,7 @@ const commands = { removeModules(modules, args["--source"], args["--project"], gitRoot()); }, create: () => { + preExecuteChecks(true); const args = arg({ "--name": String, "--type": String, @@ -147,8 +181,8 @@ const commands = { ); } - Amplitude.sendEvent({ - name: "Create Module", + analytics.sendEvent({ + name: EVENT.CREATE_MODULE, properties: { Name: args["--name"] } }); @@ -205,7 +239,7 @@ demo`; const args = arg({ "--version": String }); - Amplitude.sendEvent({ name: "Upgrade Scaffold" }); + analytics.sendEvent({ name: EVENT.UPGRADE }); upgradeScaffold(args["--version"]); }, login: () => { @@ -254,7 +288,7 @@ demo`; } }, - modules: () => { + modules: async () => { const args = arg({ "--search": String, "--visibility": String, @@ -275,8 +309,8 @@ demo`; switch (action) { case "list": - Amplitude.sendEvent({ name: "List Modules" }); - modulesList({ + analytics.sendEvent({ name: EVENT.LIST_MODULES }); + await modulesList({ search: args["--search"], status: args["--status"], visibility: args["--visibility"], @@ -292,12 +326,7 @@ demo`; ); } - Amplitude.sendEvent({ - name: "View Module Details", - properties: { "Module Id": id } - }); - - modulesGet(id); + await modulesGet(id); break; case "archive": @@ -308,12 +337,7 @@ demo`; ); } - Amplitude.sendEvent({ - name: args["--unarchive"] ? "Unarchive Module" : "Archive Module", - properties: { "Module Id": id } - }); - - modulesArchive(id, !!args["--unarchive"]); + await modulesArchive(id, !!args["--unarchive"]); break; case "help": @@ -335,8 +359,8 @@ demo`; } }, publish: () => { - Amplitude.sendEvent({ - name: "Publish Modules" + analytics.sendEvent({ + name: EVENT.PUBLISH_MODULES }); publish(); }, @@ -359,7 +383,7 @@ demo`; Please contact Support for help using Crowdbotics or to report errors, bugs, and other issues. - https://crowdbotics-slack-dev.crowdbotics.com/dashboard/user/support + https://app.crowdbotics.com/dashboard/user/support `); break; diff --git a/package.json b/package.json index e4a1b41f8..46c3cd13b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "type": "module", "engines": { - "node": ">16.0" + "node": ">18.0" }, "scripts": { "demo": "npx . demo", @@ -29,7 +29,6 @@ }, "homepage": "https://github.com/crowdbotics/modules#readme", "dependencies": { - "@amplitude/analytics-node": "^1.3.5", "@babel/cli": "^7.17.10", "@babel/core": "^7.18.5", "@babel/generator": "^7.14.9", @@ -37,6 +36,8 @@ "@babel/preset-env": "^7.18.2", "@babel/template": "^7.16.7", "@babel/types": "^7.18.8", + "@segment/analytics-node": "^2.0.0", + "@sentry/node": "^7.103.0", "ajv": "^8.11.0", "ajv-formats": "^2.1.1", "arg": "^5.0.2", diff --git a/scripts/amplitude/config.js b/scripts/amplitude/config.js deleted file mode 100644 index 65be2c572..000000000 --- a/scripts/amplitude/config.js +++ /dev/null @@ -1,8 +0,0 @@ -import { configFile } from "../utils/configFile.js"; -import { HOST_CONFIG_NAME } from "../utils/constants.js"; -import { DEVELOPMENT_AMPLITUDE_KEY, PRODUCTION_AMPLITUDE_KEY } from "./constants.js"; - -export const HAS_ASKED_OPT_IN_NAME = "has-asked-opt-in"; -export const OPT_IN_NAME = "opt-in"; - -export const AMPLITUDE_API_KEY = configFile.get(HOST_CONFIG_NAME) === "https://app.crowdbotics.com/" ? PRODUCTION_AMPLITUDE_KEY : DEVELOPMENT_AMPLITUDE_KEY; diff --git a/scripts/amplitude/constants.js b/scripts/amplitude/constants.js deleted file mode 100644 index 141a6d4bf..000000000 --- a/scripts/amplitude/constants.js +++ /dev/null @@ -1,7 +0,0 @@ -export const PRODUCTION_AMPLITUDE_KEY = "b48a6aaef8d0ac8df1f2a78196473f06"; -export const DEVELOPMENT_AMPLITUDE_KEY = "8fa9ae919aaf5bbfb85f8275b734b11c"; - -export const CUSTOMER_TYPE = { - SMB: "SMB", - ENT: "ENT" -}; diff --git a/scripts/amplitude/wrapper.js b/scripts/amplitude/wrapper.js deleted file mode 100644 index 829d9e738..000000000 --- a/scripts/amplitude/wrapper.js +++ /dev/null @@ -1,71 +0,0 @@ -import { configFile } from "../utils/configFile.js"; -import { AMPLITUDE_API_KEY, OPT_IN_NAME } from "./config.js"; -import { init, track, Identify, identify } from "@amplitude/analytics-node"; -import { currentUser } from "../utils/user.js"; - -class AmplitudeWrapper { - get userType() { - // TODO: Implement once we have the data available in the UserSerializer - return undefined; - } - - get optedIn() { - return configFile.get(OPT_IN_NAME); - } - - get appProps() { - return {}; - } - - get userProps() { - const org = currentUser.get("organization"); - return { - "User Type": this.userType, - "Org ID": org?.id, - Source: "CLI" - }; - } - - async init({ token = AMPLITUDE_API_KEY, options = {} } = {}) { - if (!this.optedIn || !token) return; - - try { - await init(token, { ...options, includeUtm: true }).promise; - } catch { - // Ignore errors during initialization - } - } - - async loadAndIdentify(user) { - await currentUser.setUser(user); - if (!currentUser.get("email")) return; - - const identifyEvent = new Identify(); - identifyEvent.set("Django Id", currentUser.get("id")); - identify(identifyEvent, { user_id: currentUser.get("email") }); - } - - async sendEvent({ name, properties = {}, user }) { - if (!this.optedIn) return; - - try { - await this.init(); - await this.loadAndIdentify(user); - - const userEmail = currentUser.get("email"); - if (userEmail) { - const updatedProps = { - ...properties, - ...this.appProps, - ...this.userProps - }; - - await track(name, updatedProps, { user_id: userEmail }).promise; - } - } catch (error) { - console.warn("Error handling analytics - skipping"); - } - } -} - -export const Amplitude = new AmplitudeWrapper(); diff --git a/scripts/analytics/config.js b/scripts/analytics/config.js new file mode 100644 index 000000000..92366851c --- /dev/null +++ b/scripts/analytics/config.js @@ -0,0 +1,14 @@ +import { configFile } from "../utils/configFile.js"; +import { HOST_CONFIG_NAME } from "../utils/constants.js"; +import { + DEVELOPMENT_SEGMENT_KEY, + PRODUCTION_SEGMENT_KEY +} from "./constants.js"; + +export const HAS_ASKED_OPT_IN_NAME = "has-asked-opt-in"; +export const OPT_IN_NAME = "opt-in"; + +export const SEGMENT_API_KEY = + configFile.get(HOST_CONFIG_NAME) === "https://app.crowdbotics.com/" + ? PRODUCTION_SEGMENT_KEY + : DEVELOPMENT_SEGMENT_KEY; diff --git a/scripts/analytics/constants.js b/scripts/analytics/constants.js new file mode 100644 index 000000000..e44f29d40 --- /dev/null +++ b/scripts/analytics/constants.js @@ -0,0 +1,18 @@ +export const PRODUCTION_SEGMENT_KEY = "J7ScSXeIOb8KrCJT8HHcMr7yDRDdCsUw"; +export const DEVELOPMENT_SEGMENT_KEY = "WrYiRf1pEB74S4RhSDKC0rQ3db3uVHiE"; + +export const CUSTOMER_TYPE = { + SMB: "SMB", + ENT: "ENT" +}; + +export const EVENT = { + OTHER: "Other CLI Commands", + UPGRADE: "Upgrade Scaffold", + LIST_MODULES: "List Modules", + CREATE_MODULE: "Create Module", + PUBLISH_MODULES: "Publish Modules", + VIEW_MODULE: "View Module Details", + ARCHIVE_MODULE: "Archive Module", + UNARCHIVE_MODULE: "Unarchive Module" +}; diff --git a/scripts/amplitude/scripts.js b/scripts/analytics/scripts.js similarity index 100% rename from scripts/amplitude/scripts.js rename to scripts/analytics/scripts.js diff --git a/scripts/analytics/wrapper.js b/scripts/analytics/wrapper.js new file mode 100644 index 000000000..94131e803 --- /dev/null +++ b/scripts/analytics/wrapper.js @@ -0,0 +1,89 @@ +import { configFile } from "../utils/configFile.js"; +import { SEGMENT_API_KEY, OPT_IN_NAME } from "./config.js"; +import { currentUser } from "../utils/user.js"; +import { Analytics } from "@segment/analytics-node"; + +class AnalyticsWrapper { + constructor() { + this.analytics = null; + this.event = { name: "", properties: {} }; + } + + get userType() { + // TODO: Implement once we have the data available in the UserSerializer + return undefined; + } + + get optedIn() { + return configFile.get(OPT_IN_NAME); + } + + get appProps() { + return {}; + } + + get userProps() { + const org = currentUser.get("organization"); + return { + "User Type": this.userType, + "Org ID": org?.id, + Source: "CLI" + }; + } + + init({ token = SEGMENT_API_KEY, options = {} } = {}) { + if (!this.optedIn || !token) return; + + try { + this.analytics = new Analytics({ + writeKey: token + }); + } catch { + // Ignore errors during initialization - TODO: log to sentry + } + } + + async loadAndIdentify(user) { + await currentUser.setUser(user); + if (!currentUser.get("email")) return; + + this.analytics.identify({ + userId: currentUser.get("email"), + traits: { + name: currentUser.first_name, + email: currentUser.get("email"), + "Django Id": currentUser.get("id") + } + }); + } + + async sendEvent(event) { + this.event = event; + const { name, properties = {}, user } = this.event; + if (!this.optedIn) return; + + try { + this.init(); + await this.loadAndIdentify(user); + + const userEmail = currentUser.get("email"); + + const updatedProps = { + ...properties, + ...this.appProps, + ...this.userProps + }; + + this.analytics.track({ + userId: userEmail, + anonymousId: !userEmail ? `${new Date().valueOf()}` : undefined, + event: name, + properties: updatedProps + }); + } catch (error) { + console.warn("Error handling analytics - skipping"); + } + } +} + +export const analytics = new AnalyticsWrapper(); diff --git a/scripts/login.js b/scripts/login.js index 2e8b27864..fee9c6e84 100644 --- a/scripts/login.js +++ b/scripts/login.js @@ -1,6 +1,4 @@ import { valid, invalid, section } from "../utils.js"; -import { HAS_ASKED_OPT_IN_NAME } from "./amplitude/config.js"; -import { askOptIn } from "./amplitude/scripts.js"; import { apiClient } from "./utils/apiClient.js"; import { performLogin } from "./utils/auth.js"; import { configFile } from "./utils/configFile.js"; @@ -17,12 +15,6 @@ export const login = async () => { valid("Login Successful!"); - // check config if they have been asked opted in or out of amplitude - const hasAskedOptIn = configFile.get(HAS_ASKED_OPT_IN_NAME) || false; - if (!hasAskedOptIn) { - await askOptIn(); - } - // set user properties to file try { const userResponse = await apiClient.get({ diff --git a/scripts/logout.js b/scripts/logout.js index 76ceb01f3..a04a7deed 100644 --- a/scripts/logout.js +++ b/scripts/logout.js @@ -1,6 +1,7 @@ import { invalid, section, valid } from "../utils.js"; import { REQUIRED_USER_PROPS } from "./login.js"; import { performLogout } from "./utils/auth.js"; +import { TOKEN_CONFIG_NAME } from "./utils/constants.js"; import { configFile } from "./utils/configFile.js"; export const logout = async () => { @@ -13,6 +14,7 @@ export const logout = async () => { REQUIRED_USER_PROPS.forEach((key) => { configFile.set(key, ""); }); + configFile.set(TOKEN_CONFIG_NAME, ""); configFile.save(); } catch (e) { return invalid( diff --git a/scripts/modules.js b/scripts/modules.js index 7e1f1e0bc..fd2e7ccdf 100644 --- a/scripts/modules.js +++ b/scripts/modules.js @@ -7,6 +7,8 @@ import Table from "cli-table"; import ora from "ora"; import { formatUrlPath } from "./utils/url.js"; import inquirer from "inquirer"; +import { analytics } from "./analytics/wrapper.js"; +import { EVENT } from "./analytics/constants.js"; const MODULES_PAGE_LIMIT = 50; @@ -120,6 +122,11 @@ export const modulesGet = async (id) => { const module = await moduleResponse.json(); + analytics.sendEvent({ + name: EVENT.VIEW_MODULE, + properties: { "Module Id": id, Name: module.title } + }); + section(`Name: \n${module.title}`); section(`Description: \n${module.description}`); section(`ID: \n${module.id}`); @@ -158,6 +165,11 @@ export const modulesArchive = async (id, unarchive = false) => { const module = await moduleResponse.json(); + analytics.sendEvent({ + name: EVENT[`${verb.toUpperCase()}_MODULE`], + properties: { "Module Id": id, Name: module.title } + }); + // Verify that the user understands which organization the modules are being published to. const { ok } = await inquirer.prompt({ message: `Are you sure you want to ${verb} module: ${module.title}?`, diff --git a/scripts/utils/apiClient.js b/scripts/utils/apiClient.js index 8c7e55a60..b73f6601f 100644 --- a/scripts/utils/apiClient.js +++ b/scripts/utils/apiClient.js @@ -30,7 +30,14 @@ class ApiClient { return `Token ${token}`; } - async _request({ path, body, method, params, anonymous }) { + async _request({ + path, + body, + method, + params, + anonymous, + shouldFailOnUnauthorized = true + }) { const host = configFile.get(HOST_CONFIG_NAME) || DEFAULT_HOST; let url = `${formatUrlPath(host)}/api/${formatUrlPath(path)}/`; @@ -48,7 +55,7 @@ class ApiClient { method: method }); - if (response.status === 401) { + if (shouldFailOnUnauthorized && response.status === 401) { // Flush newline before printing error in case console is in loading state. console.log(""); invalid( diff --git a/scripts/utils/environment.js b/scripts/utils/environment.js index d8900933e..1acf7b32b 100644 --- a/scripts/utils/environment.js +++ b/scripts/utils/environment.js @@ -1,4 +1,7 @@ -import { execSync } from "child_process"; +import { execSync, spawnSync } from "node:child_process"; +import { invalid, section } from "../../utils.js"; + +const userdir = process.cwd(); export const execOptions = { encoding: "utf8", stdio: "inherit" }; @@ -9,3 +12,38 @@ export const execOptions = { encoding: "utf8", stdio: "inherit" }; export function configurePython(options = execOptions) { execSync("pipenv --python 3.8.17", options); } + +export function preExecuteChecks(cookiecutterCheck = false) { + // Check if Python 3.8.x is installed + + try { + section("Checking Python version"); + + const pythonVersionRegex = /Python 3\.[0-9]*/; + + const pythonCheck = spawnSync("python", ["--version"], { + cwd: userdir, + shell: true, + encoding: "utf8" + }); + const userPythonVersion = pythonCheck.stdout || pythonCheck.stderr; + if (!pythonVersionRegex.test(userPythonVersion)) { + invalid(`Found Python version: ${userPythonVersion}. Please install 3.x and try again.`); + } + } catch (error) { + invalid("Error detecting python version, please check install and try again."); + } + + if (cookiecutterCheck) { + // Check if Cookiecutter is installed + try { + execSync("cookiecutter --version", { + cwd: userdir, + shell: true, + encoding: "utf8" + }); + } catch (error) { + invalid("Cookiecutter is not installed. Please install Cookiecutter before running this command."); + } + } +} diff --git a/scripts/utils/sentry.js b/scripts/utils/sentry.js new file mode 100644 index 000000000..954df5c44 --- /dev/null +++ b/scripts/utils/sentry.js @@ -0,0 +1,25 @@ +import * as Sentry from "@sentry/node"; + +class SentryMonitoring { + constructor() { + try { + Sentry.init({ + dsn: "https://9a87cf40be32750e4fc2a1b3246349e6@o263664.ingest.sentry.io/4506825798647808", + // Don't log the name of our users computers. Feels a bit much? + serverName: "-" + }); + } catch { + // Do nothing, but prevent any sentry errors from blocking commands from executing + } + } + + registerCommandName(commandName) { + try { + Sentry.setTag("command", commandName); + } catch { + // Do nothing, but prevent any sentry errors from blocking commands from executing + } + } +} + +export const sentryMonitoring = new SentryMonitoring(); diff --git a/scripts/utils/user.js b/scripts/utils/user.js index 11c6adff1..291654a69 100644 --- a/scripts/utils/user.js +++ b/scripts/utils/user.js @@ -1,4 +1,6 @@ import { apiClient } from "./apiClient.js"; +import { TOKEN_CONFIG_NAME } from "./constants.js"; +import { configFile } from "./configFile.js"; /** * A more generic approach to cache the current user information for a session. @@ -21,19 +23,20 @@ class User { } async load() { - try { - const response = await apiClient.get({ - path: "/v2/user/" - }); - if (!response.ok) return {}; + if (configFile.get(TOKEN_CONFIG_NAME)) { + try { + const response = await apiClient.get({ + path: "/v2/user/", + shouldFailOnUnauthorized: false + }); + if (!response.ok) return {}; - const userBody = await response.json(); - - this.user = userBody; - } catch { - this.user = {}; + return await response.json(); + } catch { + return {}; + } } - return this.user; + return {}; } } diff --git a/utils.js b/utils.js index d452237f8..113f968cd 100644 --- a/utils.js +++ b/utils.js @@ -26,3 +26,5 @@ export const isNameValid = (name) => { const pattern = /^[a-zA-Z][a-zA-Z0-9_-]*$/; return pattern.test(name); }; + +export const isUserEnvironment = !process?.env?.CI && !process?.env?.CIRCLE_JOB; diff --git a/yarn.lock b/yarn.lock index 1a25d8d98..ba9f03648 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,28 +2,6 @@ # yarn lockfile v1 -"@amplitude/analytics-core@^1.2.5": - version "1.2.5" - resolved "https://registry.yarnpkg.com/@amplitude/analytics-core/-/analytics-core-1.2.5.tgz#f5fc39d93920f3e5447956b9a7525b924b5c0791" - integrity sha512-V7CVlHVN+1diKiOpdp2bCPZ0mbS4CmUYF+v+eXDwVfJL3M/t3sVcT1apXnmVYGYi14cGu9hQOD11rD6qKbUOsw== - dependencies: - "@amplitude/analytics-types" "^1.3.4" - tslib "^2.4.1" - -"@amplitude/analytics-node@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@amplitude/analytics-node/-/analytics-node-1.3.5.tgz#70f5f434cfd2d29f533284074eada97745a96ad7" - integrity sha512-PVdCs3duu4OQNIQ3ENCKdm6wnoMAOdpSZD3tpWbEQVKh08FlxWLrR8eQN48nvhs+i6yEwGGVr1SfRDPpsMog1w== - dependencies: - "@amplitude/analytics-core" "^1.2.5" - "@amplitude/analytics-types" "^1.3.4" - tslib "^2.4.1" - -"@amplitude/analytics-types@^1.3.4": - version "1.3.4" - resolved "https://registry.yarnpkg.com/@amplitude/analytics-types/-/analytics-types-1.3.4.tgz#9142a893313cc28a9c7a7a3e3360c33636fc9b7d" - integrity sha512-tR70gzqFkEzX9QpxvWYMfLCledT7vMhgd3d4/bkp3nnGXTOORaVUOCcSgOyxyuFdSx84T61aP/eZPKIcZcaP+A== - "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -1260,6 +1238,18 @@ resolved "https://registry.yarnpkg.com/@ljharb/through/-/through-2.3.9.tgz#85f221eb82f9d555e180e87d6e50fb154af85408" integrity sha512-yN599ZBuMPPK4tdoToLlvgJB4CLK8fGl7ntfy0Wn7U6ttNvHYurd81bfUiK/6sMkiIwm65R6ck4L6+Y3DfVbNQ== +"@lukeed/csprng@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" + integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== + +"@lukeed/uuid@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@lukeed/uuid/-/uuid-2.0.1.tgz#4f6c34259ee0982a455e1797d56ac27bb040fd74" + integrity sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w== + dependencies: + "@lukeed/csprng" "^1.1.0" + "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" @@ -1553,6 +1543,75 @@ resolved "https://registry.yarnpkg.com/@react-native/polyfills/-/polyfills-2.0.0.tgz#4c40b74655c83982c8cf47530ee7dc13d957b6aa" integrity sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ== +"@segment/analytics-core@1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@segment/analytics-core/-/analytics-core-1.4.1.tgz#af0e5ec31f3d1978a56dee4683af4c34b207effb" + integrity sha512-kV0Pf33HnthuBOVdYNani21kYyj118Fn+9757bxqoksiXoZlYvBsFq6giNdCsKcTIE1eAMqNDq3xE1VQ0cfsHA== + dependencies: + "@lukeed/uuid" "^2.0.0" + "@segment/analytics-generic-utils" "1.1.1" + dset "^3.1.2" + tslib "^2.4.1" + +"@segment/analytics-generic-utils@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@segment/analytics-generic-utils/-/analytics-generic-utils-1.1.1.tgz#8603a38cc6bce5b0b0d2edfd97518489551ae789" + integrity sha512-THTIzBPHnvu1HYJU3fARdJ3qIkukO3zDXsmDm+kAeUks5R9CBXOQ6rPChiASVzSmwAIIo5uFIXXnCraojlq/Gw== + dependencies: + tslib "^2.4.1" + +"@segment/analytics-node@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@segment/analytics-node/-/analytics-node-2.0.0.tgz#297acf76dc787e373d4236fceb38a67f110e1a7c" + integrity sha512-DU25dmsTsMEQ/A1OxVr2vHpGUyUZ2wukH3dH9bYRgvQOlhnnRHBP9hB3smNLfygC0IU2CKhT1rP6CGtVfdf4Sg== + dependencies: + "@lukeed/uuid" "^2.0.0" + "@segment/analytics-core" "1.4.1" + "@segment/analytics-generic-utils" "1.1.1" + buffer "^6.0.3" + jose "^5.1.0" + node-fetch "^2.6.7" + tslib "^2.4.1" + +"@sentry-internal/tracing@7.103.0": + version "7.103.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.103.0.tgz#b18ef65f610099ee2fc74f91f9ccfdb0353580c4" + integrity sha512-sZ/Wao8HOvGaBs7WlOdflMpHGAFkOBWL6hBiirHaOy5d+IDm7n7et5U6zhvcfiyYBO4nY36gy1Tg5mw+aNO0Vw== + dependencies: + "@sentry/core" "7.103.0" + "@sentry/types" "7.103.0" + "@sentry/utils" "7.103.0" + +"@sentry/core@7.103.0": + version "7.103.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.103.0.tgz#8f626362c96f9aa4b4a52042c431d16372491dc1" + integrity sha512-LCI+PIDoF/RLqN41fNXum3ilmS6ukni6L7t38vSdibbe2G0804EbPLtOIpv2PkS8E6CFuRW5zOb+8OwEAAtZWw== + dependencies: + "@sentry/types" "7.103.0" + "@sentry/utils" "7.103.0" + +"@sentry/node@^7.103.0": + version "7.103.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.103.0.tgz#9cf488086717c2920c43568432d14232b6783a9e" + integrity sha512-/bS/WNeO+PEd0r3o3LN0XGJV+l7hLNy1dTcn61VRgWGVs8SqMBb3uAvXAibZ9zGTCkaX/Ky3JumMcOOoxmNCtg== + dependencies: + "@sentry-internal/tracing" "7.103.0" + "@sentry/core" "7.103.0" + "@sentry/types" "7.103.0" + "@sentry/utils" "7.103.0" + +"@sentry/types@7.103.0": + version "7.103.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.103.0.tgz#f413f922216c97ec86bae39f9d527669d8afedbd" + integrity sha512-NCvKyx8d2AGBQKPARrJemZmZ16DiMo688OEikZg4BbvFNDUzK5Egm2BH0vfLDhbNkU19o3maJowrYo42m8r9Zw== + +"@sentry/utils@7.103.0": + version "7.103.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.103.0.tgz#803b76e2adfdcec0d4ab6369cc8990dde19b55f4" + integrity sha512-phkUJt3F0UOkVq+M4GfdAh2ewI3ASrNiJddx9aO7GnT0aDwwVBHZltnqt95qgAB8W+BipTSt1dAh8yUbbq1Ceg== + dependencies: + "@sentry/types" "7.103.0" + "@sideway/address@^4.1.3": version "4.1.4" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" @@ -2631,6 +2690,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dset@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.3.tgz#c194147f159841148e8e34ca41f638556d9542d2" + integrity sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -4081,6 +4145,11 @@ joi@^17.2.1: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" +jose@^5.1.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/jose/-/jose-5.2.2.tgz#b91170e9ba6dbe609b0c0a86568f9a1fbe4335c0" + integrity sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -5101,6 +5170,13 @@ node-fetch@^2.2.0, node-fetch@^2.6.0: dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b"