diff --git a/common/calculate.ts b/common/calculate.ts index e4c9ed07..5edf1211 100644 --- a/common/calculate.ts +++ b/common/calculate.ts @@ -1,4 +1,4 @@ -import { maxBy, sortBy, sum, sumBy } from 'lodash' +import { maxBy, partition, sortBy, sum, sumBy } from 'lodash' import { Bet, LimitBet } from './bet' import { calculateCpmmSale, @@ -255,3 +255,43 @@ export function getTopAnswer( ) return top?.answer } + +export function getLargestPosition(contract: Contract, userBets: Bet[]) { + let yesFloorShares = 0, + yesShares = 0, + noShares = 0, + noFloorShares = 0 + + if (userBets.length === 0) { + return null + } + if (contract.outcomeType === 'FREE_RESPONSE') { + const answerCounts: { [outcome: string]: number } = {} + for (const bet of userBets) { + if (bet.outcome) { + if (!answerCounts[bet.outcome]) { + answerCounts[bet.outcome] = bet.amount + } else { + answerCounts[bet.outcome] += bet.amount + } + } + } + const majorityAnswer = + maxBy(Object.keys(answerCounts), (outcome) => answerCounts[outcome]) ?? '' + return { + prob: undefined, + shares: answerCounts[majorityAnswer] || 0, + outcome: majorityAnswer, + } + } + + const [yesBets, noBets] = partition(userBets, (bet) => bet.outcome === 'YES') + yesShares = sumBy(yesBets, (bet) => bet.shares) + noShares = sumBy(noBets, (bet) => bet.shares) + yesFloorShares = Math.floor(yesShares) + noFloorShares = Math.floor(noShares) + + const shares = yesFloorShares || noFloorShares + const outcome = yesFloorShares > noFloorShares ? 'YES' : 'NO' + return { shares, outcome } +} diff --git a/common/comment.ts b/common/comment.ts index 7ecbb6d4..cdb62fd3 100644 --- a/common/comment.ts +++ b/common/comment.ts @@ -33,6 +33,11 @@ export type OnContract = { // denormalized from bet betAmount?: number betOutcome?: string + + // denormalized based on betting history + commenterPositionProb?: number // binary only + commenterPositionShares?: number + commenterPositionOutcome?: string } export type OnGroup = { diff --git a/common/contract.ts b/common/contract.ts index 0d2a38ca..2f71bab7 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -148,7 +148,7 @@ export const OUTCOME_TYPES = [ 'NUMERIC', ] as const -export const MAX_QUESTION_LENGTH = 480 +export const MAX_QUESTION_LENGTH = 240 export const MAX_DESCRIPTION_LENGTH = 16000 export const MAX_TAG_LENGTH = 60 diff --git a/common/economy.ts b/common/economy.ts index a412d4de..7ec52b30 100644 --- a/common/economy.ts +++ b/common/economy.ts @@ -7,7 +7,7 @@ export const FIXED_ANTE = econ?.FIXED_ANTE ?? 100 export const STARTING_BALANCE = econ?.STARTING_BALANCE ?? 1000 // for sus users, i.e. multiple sign ups for same person export const SUS_STARTING_BALANCE = econ?.SUS_STARTING_BALANCE ?? 10 -export const REFERRAL_AMOUNT = econ?.REFERRAL_AMOUNT ?? 500 +export const REFERRAL_AMOUNT = econ?.REFERRAL_AMOUNT ?? 250 export const UNIQUE_BETTOR_BONUS_AMOUNT = econ?.UNIQUE_BETTOR_BONUS_AMOUNT ?? 10 export const BETTING_STREAK_BONUS_AMOUNT = diff --git a/common/envs/dev.ts b/common/envs/dev.ts index 719de36e..96ec4dc2 100644 --- a/common/envs/dev.ts +++ b/common/envs/dev.ts @@ -16,4 +16,6 @@ export const DEV_CONFIG: EnvConfig = { cloudRunId: 'w3txbmd3ba', cloudRunRegion: 'uc', amplitudeApiKey: 'fd8cbfd964b9a205b8678a39faae71b3', + // this is Phil's deployment + twitchBotEndpoint: 'https://king-prawn-app-5btyw.ondigitalocean.app', } diff --git a/common/envs/prod.ts b/common/envs/prod.ts index a9d1ffc3..3014f4e3 100644 --- a/common/envs/prod.ts +++ b/common/envs/prod.ts @@ -2,6 +2,7 @@ export type EnvConfig = { domain: string firebaseConfig: FirebaseConfig amplitudeApiKey?: string + twitchBotEndpoint?: string // IDs for v2 cloud functions -- find these by deploying a cloud function and // examining the URL, https://[name]-[cloudRunId]-[cloudRunRegion].a.run.app @@ -66,6 +67,7 @@ export const PROD_CONFIG: EnvConfig = { appId: '1:128925704902:web:f61f86944d8ffa2a642dc7', measurementId: 'G-SSFK1Q138D', }, + twitchBotEndpoint: 'https://twitch-bot-nggbo3neva-uc.a.run.app', cloudRunId: 'nggbo3neva', cloudRunRegion: 'uc', adminEmails: [ @@ -82,9 +84,9 @@ export const PROD_CONFIG: EnvConfig = { visibility: 'PUBLIC', moneyMoniker: 'M$', - bettor: 'predictor', - pastBet: 'prediction', - presentBet: 'predict', + bettor: 'trader', + pastBet: 'trade', + presentBet: 'trade', navbarLogoPath: '', faviconPath: '/favicon.ico', newQuestionPlaceholders: [ diff --git a/common/notification.ts b/common/notification.ts index 804ec68e..b42df541 100644 --- a/common/notification.ts +++ b/common/notification.ts @@ -247,6 +247,8 @@ export type BetFillData = { creatorOutcome: string probability: number fillAmount: number + limitOrderTotal?: number + limitOrderRemaining?: number } export type ContractResolutionData = { diff --git a/common/stats.ts b/common/stats.ts index 152a6eae..8ddf3466 100644 --- a/common/stats.ts +++ b/common/stats.ts @@ -1,20 +1,22 @@ export type Stats = { startDate: number dailyActiveUsers: number[] + dailyActiveUsersWeeklyAvg: number[] weeklyActiveUsers: number[] monthlyActiveUsers: number[] + d1: number[] + d1WeeklyAvg: number[] + nd1: number[] + nd1WeeklyAvg: number[] + nw1: number[] dailyBetCounts: number[] dailyContractCounts: number[] dailyCommentCounts: number[] dailySignups: number[] weekOnWeekRetention: number[] monthlyRetention: number[] - weeklyActivationRate: number[] - topTenthActions: { - daily: number[] - weekly: number[] - monthly: number[] - } + dailyActivationRate: number[] + dailyActivationRateWeeklyAvg: number[] manaBet: { daily: number[] weekly: number[] diff --git a/common/user-notification-preferences.ts b/common/user-notification-preferences.ts index f585f373..3fc0fb2f 100644 --- a/common/user-notification-preferences.ts +++ b/common/user-notification-preferences.ts @@ -60,14 +60,6 @@ export const getDefaultNotificationPreferences = ( privateUser?: PrivateUser, noEmails?: boolean ) => { - const { - unsubscribedFromCommentEmails, - unsubscribedFromAnswerEmails, - unsubscribedFromResolutionEmails, - unsubscribedFromWeeklyTrendingEmails, - unsubscribedFromGenericEmails, - } = privateUser || {} - const constructPref = (browserIf: boolean, emailIf: boolean) => { const browser = browserIf ? 'browser' : undefined const email = noEmails ? undefined : emailIf ? 'email' : undefined @@ -75,84 +67,48 @@ export const getDefaultNotificationPreferences = ( } return { // Watched Markets - all_comments_on_watched_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), - all_answers_on_watched_markets: constructPref( - true, - !unsubscribedFromAnswerEmails - ), + all_comments_on_watched_markets: constructPref(true, false), + all_answers_on_watched_markets: constructPref(true, false), // Comments - tips_on_your_comments: constructPref(true, !unsubscribedFromCommentEmails), - comments_by_followed_users_on_watched_markets: constructPref(true, false), - all_replies_to_my_comments_on_watched_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), - all_replies_to_my_answers_on_watched_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), + tips_on_your_comments: constructPref(true, true), + comments_by_followed_users_on_watched_markets: constructPref(true, true), + all_replies_to_my_comments_on_watched_markets: constructPref(true, true), + all_replies_to_my_answers_on_watched_markets: constructPref(true, true), all_comments_on_contracts_with_shares_in_on_watched_markets: constructPref( true, - !unsubscribedFromCommentEmails + false ), // Answers - answers_by_followed_users_on_watched_markets: constructPref( - true, - !unsubscribedFromAnswerEmails - ), - answers_by_market_creator_on_watched_markets: constructPref( - true, - !unsubscribedFromAnswerEmails - ), + answers_by_followed_users_on_watched_markets: constructPref(true, true), + answers_by_market_creator_on_watched_markets: constructPref(true, true), all_answers_on_contracts_with_shares_in_on_watched_markets: constructPref( true, - !unsubscribedFromAnswerEmails + true ), // On users' markets - your_contract_closed: constructPref( - true, - !unsubscribedFromResolutionEmails - ), // High priority - all_comments_on_my_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), - all_answers_on_my_markets: constructPref( - true, - !unsubscribedFromAnswerEmails - ), + your_contract_closed: constructPref(true, true), // High priority + all_comments_on_my_markets: constructPref(true, true), + all_answers_on_my_markets: constructPref(true, true), subsidized_your_market: constructPref(true, true), // Market updates - resolutions_on_watched_markets: constructPref( - true, - !unsubscribedFromResolutionEmails - ), + resolutions_on_watched_markets: constructPref(true, false), market_updates_on_watched_markets: constructPref(true, false), market_updates_on_watched_markets_with_shares_in: constructPref( true, false ), - resolutions_on_watched_markets_with_shares_in: constructPref( - true, - !unsubscribedFromResolutionEmails - ), + resolutions_on_watched_markets_with_shares_in: constructPref(true, true), //Balance Changes loan_income: constructPref(true, false), betting_streaks: constructPref(true, false), referral_bonuses: constructPref(true, true), unique_bettors_on_your_contract: constructPref(true, false), - tipped_comments_on_watched_markets: constructPref( - true, - !unsubscribedFromCommentEmails - ), + tipped_comments_on_watched_markets: constructPref(true, true), tips_on_your_markets: constructPref(true, true), limit_order_fills: constructPref(true, false), @@ -160,17 +116,11 @@ export const getDefaultNotificationPreferences = ( tagged_user: constructPref(true, true), on_new_follow: constructPref(true, true), contract_from_followed_user: constructPref(true, true), - trending_markets: constructPref( - false, - !unsubscribedFromWeeklyTrendingEmails - ), + trending_markets: constructPref(false, true), profit_loss_updates: constructPref(false, true), probability_updates_on_watched_markets: constructPref(true, false), - thank_you_for_purchases: constructPref( - false, - !unsubscribedFromGenericEmails - ), - onboarding_flow: constructPref(false, !unsubscribedFromGenericEmails), + thank_you_for_purchases: constructPref(false, false), + onboarding_flow: constructPref(false, false), } as notification_preferences } diff --git a/common/user.ts b/common/user.ts index b490ab0c..0372d99b 100644 --- a/common/user.ts +++ b/common/user.ts @@ -56,11 +56,6 @@ export type PrivateUser = { username: string // denormalized from User email?: string - unsubscribedFromResolutionEmails?: boolean - unsubscribedFromCommentEmails?: boolean - unsubscribedFromAnswerEmails?: boolean - unsubscribedFromGenericEmails?: boolean - unsubscribedFromWeeklyTrendingEmails?: boolean weeklyTrendingEmailSent?: boolean manaBonusEmailSent?: boolean initialDeviceToken?: string @@ -71,6 +66,7 @@ export type PrivateUser = { twitchName: string controlToken: string botEnabled?: boolean + needsRelinking?: boolean } } @@ -85,9 +81,11 @@ export type PortfolioMetrics = { export const MANIFOLD_USERNAME = 'ManifoldMarkets' export const MANIFOLD_AVATAR_URL = 'https://manifold.markets/logo-bg-white.png' -export const BETTOR = ENV_CONFIG.bettor ?? 'bettor' // aka predictor -export const BETTORS = ENV_CONFIG.bettor + 's' ?? 'bettors' -export const PRESENT_BET = ENV_CONFIG.presentBet ?? 'bet' // aka predict -export const PRESENT_BETS = ENV_CONFIG.presentBet + 's' ?? 'bets' -export const PAST_BET = ENV_CONFIG.pastBet ?? 'bet' // aka prediction -export const PAST_BETS = ENV_CONFIG.pastBet + 's' ?? 'bets' // aka predictions +// TODO: remove. Hardcoding the strings would be better. +// Different views require different language. +export const BETTOR = ENV_CONFIG.bettor ?? 'trader' +export const BETTORS = ENV_CONFIG.bettor + 's' ?? 'traders' +export const PRESENT_BET = ENV_CONFIG.presentBet ?? 'trade' +export const PRESENT_BETS = ENV_CONFIG.presentBet + 's' ?? 'trades' +export const PAST_BET = ENV_CONFIG.pastBet ?? 'trade' +export const PAST_BETS = ENV_CONFIG.pastBet + 's' ?? 'trades' diff --git a/common/util/random.ts b/common/util/random.ts index c26b361b..93a574ab 100644 --- a/common/util/random.ts +++ b/common/util/random.ts @@ -46,3 +46,10 @@ export const shuffle = (array: unknown[], rand: () => number) => { ;[array[i], array[swapIndex]] = [array[swapIndex], array[i]] } } + +export function chooseRandomSubset(items: T[], count: number) { + const fiveMinutes = 5 * 60 * 1000 + const seed = Math.round(Date.now() / fiveMinutes).toString() + shuffle(items, createRNG(seed)) + return items.slice(0, count) +} diff --git a/firestore.indexes.json b/firestore.indexes.json index bcee41d5..2b7ef839 100644 --- a/firestore.indexes.json +++ b/firestore.indexes.json @@ -100,6 +100,20 @@ } ] }, + { + "collectionGroup": "comments", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "userId", + "order": "ASCENDING" + }, + { + "fieldPath": "createdTime", + "order": "ASCENDING" + } + ] + }, { "collectionGroup": "comments", "queryScope": "COLLECTION_GROUP", diff --git a/firestore.rules b/firestore.rules index 08214b10..26649fa6 100644 --- a/firestore.rules +++ b/firestore.rules @@ -27,7 +27,7 @@ service cloud.firestore { allow read; allow update: if userId == request.auth.uid && request.resource.data.diff(resource.data).affectedKeys() - .hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories', 'lastPingTime','shouldShowWelcome', 'hasSeenContractFollowModal']); + .hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories', 'lastPingTime','shouldShowWelcome', 'hasSeenContractFollowModal', 'homeSections']); // User referral rules allow update: if userId == request.auth.uid && request.resource.data.diff(resource.data).affectedKeys() @@ -78,7 +78,7 @@ service cloud.firestore { allow read: if userId == request.auth.uid || isAdmin(); allow update: if (userId == request.auth.uid || isAdmin()) && request.resource.data.diff(resource.data).affectedKeys() - .hasOnly(['apiKey', 'unsubscribedFromResolutionEmails', 'unsubscribedFromCommentEmails', 'unsubscribedFromAnswerEmails', 'unsubscribedFromWeeklyTrendingEmails', 'notificationPreferences', 'twitchInfo']); + .hasOnly(['apiKey', 'notificationPreferences', 'twitchInfo']); } match /private-users/{userId}/views/{viewId} { @@ -171,33 +171,32 @@ service cloud.firestore { allow read; } - match /groups/{groupId} { - allow read; - allow update: if (request.auth.uid == resource.data.creatorId || isAdmin()) - && request.resource.data.diff(resource.data) - .affectedKeys() - .hasOnly(['name', 'about', 'anyoneCanJoin', 'aboutPostId' ]); - allow delete: if request.auth.uid == resource.data.creatorId; + match /groups/{groupId} { + allow read; + allow update: if (request.auth.uid == resource.data.creatorId || isAdmin()) + && request.resource.data.diff(resource.data) + .affectedKeys() + .hasOnly(['name', 'about', 'anyoneCanJoin', 'aboutPostId' ]); + allow delete: if request.auth.uid == resource.data.creatorId; - match /groupContracts/{contractId} { - allow write: if isGroupMember() || request.auth.uid == get(/databases/$(database)/documents/groups/$(groupId)).data.creatorId - } + match /groupContracts/{contractId} { + allow write: if isGroupMember() || request.auth.uid == get(/databases/$(database)/documents/groups/$(groupId)).data.creatorId + } - match /groupMembers/{memberId}{ - allow create: if request.auth.uid == get(/databases/$(database)/documents/groups/$(groupId)).data.creatorId || (request.auth.uid == request.resource.data.userId && get(/databases/$(database)/documents/groups/$(groupId)).data.anyoneCanJoin); - allow delete: if request.auth.uid == resource.data.userId; - } + match /groupMembers/{memberId}{ + allow create: if request.auth.uid == get(/databases/$(database)/documents/groups/$(groupId)).data.creatorId || (request.auth.uid == request.resource.data.userId && get(/databases/$(database)/documents/groups/$(groupId)).data.anyoneCanJoin); + allow delete: if request.auth.uid == resource.data.userId; + } - function isGroupMember() { - return exists(/databases/$(database)/documents/groups/$(groupId)/groupMembers/$(request.auth.uid)); - } + function isGroupMember() { + return exists(/databases/$(database)/documents/groups/$(groupId)/groupMembers/$(request.auth.uid)); + } - match /comments/{commentId} { - allow read; - allow create: if request.auth != null && commentMatchesUser(request.auth.uid, request.resource.data) && isGroupMember(); - } - - } + match /comments/{commentId} { + allow read; + allow create: if request.auth != null && commentMatchesUser(request.auth.uid, request.resource.data) && isGroupMember(); + } + } match /posts/{postId} { allow read; diff --git a/functions/README.md b/functions/README.md index 97a7a33b..02477588 100644 --- a/functions/README.md +++ b/functions/README.md @@ -65,5 +65,6 @@ Adapted from https://firebase.google.com/docs/functions/get-started Secrets are strings that shouldn't be checked into Git (eg API keys, passwords). We store these using [Google Secret Manager](https://console.cloud.google.com/security/secret-manager), which provides them as environment variables to functions that require them. Some useful workflows: -- Set a secret: `$ firebase functions:secrets:set stripe.test_secret="THE-API-KEY"` +- Set a secret: `$ firebase functions:secrets:set STRIPE_APIKEY` + - Then, enter the secret in the prompt. - Read a secret: `$ firebase functions:secrets:access STRIPE_APIKEY` diff --git a/functions/package.json b/functions/package.json index d5a578de..ba59f090 100644 --- a/functions/package.json +++ b/functions/package.json @@ -39,6 +39,7 @@ "lodash": "4.17.21", "mailgun-js": "0.22.0", "module-alias": "2.2.2", + "node-fetch": "2", "react-masonry-css": "1.0.16", "stripe": "8.194.0", "zod": "3.17.2" @@ -46,6 +47,7 @@ "devDependencies": { "@types/mailgun-js": "0.22.12", "@types/module-alias": "2.0.1", + "@types/node-fetch": "2.6.2", "firebase-functions-test": "0.3.3" }, "private": true diff --git a/functions/src/create-notification.ts b/functions/src/create-notification.ts index ebd3f26c..038e0142 100644 --- a/functions/src/create-notification.ts +++ b/functions/src/create-notification.ts @@ -10,7 +10,7 @@ import { User } from '../../common/user' import { Contract } from '../../common/contract' import { getPrivateUser, getValues } from './utils' import { Comment } from '../../common/comment' -import { groupBy, uniq } from 'lodash' +import { groupBy, sum, uniq } from 'lodash' import { Bet, LimitBet } from '../../common/bet' import { Answer } from '../../common/answer' import { getContractBetMetrics } from '../../common/calculate' @@ -416,8 +416,9 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async ( ) } - //TODO: store all possible reasons why the user might be getting the notification and choose the most lenient that they - // have enabled so they will unsubscribe from the least important notifications + //TODO: store all possible reasons why the user might be getting the notification + // and choose the most lenient that they have enabled so they will unsubscribe + // from the least important notifications await notifyRepliedUser() await notifyTaggedUsers() await notifyContractCreator() @@ -479,7 +480,7 @@ export const createBetFillNotification = async ( fromUser: User, toUser: User, bet: Bet, - userBet: LimitBet, + limitBet: LimitBet, contract: Contract, idempotencyKey: string ) => { @@ -491,8 +492,10 @@ export const createBetFillNotification = async ( ) if (!sendToBrowser) return - const fill = userBet.fills.find((fill) => fill.matchedBetId === bet.id) + const fill = limitBet.fills.find((fill) => fill.matchedBetId === bet.id) const fillAmount = fill?.amount ?? 0 + const remainingAmount = + limitBet.orderAmount - sum(limitBet.fills.map((f) => f.amount)) const notificationRef = firestore .collection(`/users/${toUser.id}/notifications`) @@ -503,7 +506,7 @@ export const createBetFillNotification = async ( reason: 'bet_fill', createdTime: Date.now(), isSeen: false, - sourceId: userBet.id, + sourceId: limitBet.id, sourceType: 'bet', sourceUpdateType: 'updated', sourceUserName: fromUser.name, @@ -516,9 +519,11 @@ export const createBetFillNotification = async ( sourceContractId: contract.id, data: { betOutcome: bet.outcome, - creatorOutcome: userBet.outcome, + creatorOutcome: limitBet.outcome, fillAmount, - probability: userBet.limitProb, + probability: limitBet.limitProb, + limitOrderTotal: limitBet.orderAmount, + limitOrderRemaining: remainingAmount, } as BetFillData, } return await notificationRef.set(removeUndefinedProps(notification)) diff --git a/functions/src/email-templates/new-unique-bettor.html b/functions/src/email-templates/new-unique-bettor.html index 51026121..ffd507f5 100644 --- a/functions/src/email-templates/new-unique-bettor.html +++ b/functions/src/email-templates/new-unique-bettor.html @@ -9,7 +9,7 @@ - New unique predictors on your market + New unique traders on your market +Asset 2 + + + + + + + + + + + diff --git a/yarn.lock b/yarn.lock index be83129b..89d43cba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3398,6 +3398,14 @@ resolved "https://registry.yarnpkg.com/@types/module-alias/-/module-alias-2.0.1.tgz#e5893236ce922152d57c5f3f978f764f4deeb45f" integrity sha512-DN/CCT1HQG6HquBNJdLkvV+4v5l/oEuwOHUPLxI+Eub0NED+lk0YUfba04WGH90EINiUrNgClkNnwGmbICeWMQ== +"@types/node-fetch@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" + integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=8.1.0": version "17.0.35" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.35.tgz#635b7586086d51fb40de0a2ec9d1014a5283ba4a" @@ -4777,7 +4785,7 @@ combine-promises@^1.1.0: resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71" integrity sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg== -combined-stream@^1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -6536,6 +6544,15 @@ form-data@^2.3.3, form-data@^2.5.0: combined-stream "^1.0.6" mime-types "^2.1.12" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + formdata-polyfill@^4.0.10: version "4.0.10" resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" @@ -8666,7 +8683,7 @@ node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7: +node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==