From f3366432b2848b973ac81c7ca7bb667eff42bf2a Mon Sep 17 00:00:00 2001 From: Antoine Jaussoin Date: Wed, 8 Feb 2023 18:48:59 +0000 Subject: [PATCH] Migration to Google Analytics 4 (#471) --- .env.example | 4 +- .github/workflows/alpha.yml | 2 +- README.md | 4 + backend/package.json | 4 +- backend/src/common/types.ts | 6 + backend/src/config.ts | 2 + backend/src/db/actions/users.ts | 10 + backend/src/index.ts | 9 - backend/src/stripe/router.ts | 18 +- backend/src/track/GA.md | 1 + backend/src/track/track.ts | 97 ++++++ backend/src/types.ts | 2 + backend/yarn.lock | 286 +++++++++++++++++- docs/package.json | 2 +- frontend/package.json | 4 +- frontend/src/auth/modal/AnonAuth.tsx | 2 + frontend/src/auth/modal/account/Register.tsx | 9 +- frontend/src/common/types.ts | 6 + frontend/src/components/Icon/Icon.tsx | 4 +- frontend/src/ga.md | 1 + frontend/src/track.ts | 17 +- frontend/src/translations/languages.ts | 45 +-- frontend/src/views/Reset.tsx | 7 +- frontend/src/views/admin/NewAccountModal.tsx | 7 +- .../sections/template/ColumnEditor.tsx | 4 +- .../src/views/subscribe/SubscribePage.tsx | 6 + frontend/yarn.lock | 8 +- integration/package.json | 2 +- package.json | 2 +- 29 files changed, 487 insertions(+), 84 deletions(-) create mode 100644 backend/src/track/GA.md create mode 100644 backend/src/track/track.ts create mode 100644 frontend/src/ga.md diff --git a/.env.example b/.env.example index 0a43722b1..99089d636 100644 --- a/.env.example +++ b/.env.example @@ -56,4 +56,6 @@ RATE_LIMIT_WINDOW=900000 RATE_LIMIT_MAX=500 RATE_LIMIT_WS_POINTS=600 RATE_LIMIT_WS_DURATION=60 -WS_MAX_BUFFER_SIZE=10000 \ No newline at end of file +WS_MAX_BUFFER_SIZE=10000 +GA4_SECRET= +GA4_MEASUREMENT_ID=G-XXXXXXXXXX \ No newline at end of file diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml index 416ad046e..354623ee0 100644 --- a/.github/workflows/alpha.yml +++ b/.github/workflows/alpha.yml @@ -2,7 +2,7 @@ name: 'Alpha Build' on: push: - branches: [v4190/release] + branches: [v4192/ga4] jobs: build: diff --git a/README.md b/README.md index 8b95d5bfd..7502b7cd5 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ This will run a demo version, which you can turn into a fully licenced version b ## Versions History +### Version 4.19.2 + +- Migration to Google Analytics V4 + ### Version 4.19.1 - Hotfix: Issue with password account creation diff --git a/backend/package.json b/backend/package.json index 67e5374c2..b0d2c005c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "@retrospected/backend", - "version": "4.19.1", + "version": "4.19.2", "license": "GNU GPLv3", "private": true, "type": "module", @@ -42,6 +42,7 @@ "@types/passport-local": "1.0.34", "@types/passport-microsoft": "0.0.0", "@types/passport-twitter": "1.0.37", + "@types/request": "^2.48.8", "@types/shortid": "0.0.29", "@types/socket.io-redis": "3.0.0", "@types/uuid": "9.0.0", @@ -83,6 +84,7 @@ "prettier": "2.8.1", "rate-limiter-flexible": "2.4.1", "redis": "3.1.2", + "request": "^2.88.2", "rimraf": "3.0.2", "scripty": "^2.1.1", "shortid": "2.2.16", diff --git a/backend/src/common/types.ts b/backend/src/common/types.ts index 797c83b1f..198f07819 100644 --- a/backend/src/common/types.ts +++ b/backend/src/common/types.ts @@ -215,6 +215,12 @@ export type TrackingEvent = | 'trial/modal/open' | 'ack/error' | 'ack/refresh' + | 'register/password' + | 'register/oauth' + | 'register/anonymous' + | 'subscribe/initial' + | 'subscribe/launch-stripe' + | 'subscribe/purchased' | 'language/change/'; export type ColumnDefinitionType = diff --git a/backend/src/config.ts b/backend/src/config.ts index 62bcafadb..6c2ef9e1c 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -122,6 +122,8 @@ const config: BackendConfig = { 'MAIL_ALLOW_SELF_SIGNED_CERTS', false ), + GA4_SECRET: defaults('GA4_SECRET', ''), + GA4_MEASUREMENT_ID: defaults('GA4_MEASUREMENT_ID', ''), }; export default config; diff --git a/backend/src/db/actions/users.ts b/backend/src/db/actions/users.ts index 466257413..3aa59d5c1 100644 --- a/backend/src/db/actions/users.ts +++ b/backend/src/db/actions/users.ts @@ -121,6 +121,16 @@ export async function getUserByUsername( }); } +export async function getUserByEmail( + email: string +): Promise { + return await transaction(async (manager) => { + const userRepository = manager.withRepository(UserRepository); + const user = await userRepository.findOne({ where: { email } }); + return user ? user : null; + }); +} + export async function updateIdentity( identityId: string, updatedIdentity: Partial diff --git a/backend/src/index.ts b/backend/src/index.ts index a0b02f60e..e3895323e 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -615,15 +615,6 @@ db().then(() => { } }); - // app.get('/api/test', async (req, res) => { - // await sendSelfHostWelcome( - // 'antoine@jaussoin.com', - // 'Antoine J', - // 'BLAH-BLAH-BLAH' - // ); - // res.send('done'); - // }); - setupSentryErrorHandler(app); }); diff --git a/backend/src/stripe/router.ts b/backend/src/stripe/router.ts index fbcc0377f..f69702bad 100644 --- a/backend/src/stripe/router.ts +++ b/backend/src/stripe/router.ts @@ -13,7 +13,7 @@ import { SubscriptionDeletedPayload, } from './types.js'; import { plans, getProduct } from './products.js'; -import { updateUser } from '../db/actions/users.js'; +import { getUserByEmail, updateUser } from '../db/actions/users.js'; import { registerLicence } from '../db/actions/licences.js'; import { getIdentityFromRequest } from '../utils.js'; import isValidDomain from '../security/is-valid-domain.js'; @@ -25,6 +25,7 @@ import { startTrial, getActiveSubscriptionWhereUserIsAdmin, } from '../db/actions/subscriptions.js'; +import { trackPurchase } from './../track/track.js'; const stripe = new Stripe(config.STRIPE_SECRET, { apiVersion: '2022-11-15', @@ -137,6 +138,7 @@ function stripeRouter(): Router { const customerEmail = session.customer_details?.email || null; const stripeCustomerId = session.customer! as string; const stripeSessionId = session.id; + const user = customerEmail ? await getUserByEmail(customerEmail) : null; const customer = (await stripe.customers.retrieve( stripeCustomerId @@ -144,6 +146,20 @@ function stripeRouter(): Router { const customerName = customer.name; if (subEvent.data.object.payment_status === 'paid') { + if (session.line_items && session.line_items.data[0]) { + const lineItem = session.line_items.data[0]; + trackPurchase( + stripeCustomerId, + user ? user.id : stripeCustomerId, + session.id, + (lineItem.price?.product as string | null) || lineItem.id, + lineItem.description, + lineItem.quantity || 0, + lineItem.currency, + lineItem.amount_total / 100 + ); + } + if ( product && product.product === config.STRIPE_SELF_HOSTED_PRODUCT diff --git a/backend/src/track/GA.md b/backend/src/track/GA.md new file mode 100644 index 000000000..83478a8a1 --- /dev/null +++ b/backend/src/track/GA.md @@ -0,0 +1 @@ +https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag#payload_post_body diff --git a/backend/src/track/track.ts b/backend/src/track/track.ts new file mode 100644 index 000000000..098bc8221 --- /dev/null +++ b/backend/src/track/track.ts @@ -0,0 +1,97 @@ +import config from './../config.js'; +import request from 'request'; +import chalkTemplate from 'chalk-template'; + +export function trackPurchase( + /** + * Uniquely identifies a user instance of a web client + */ + clientId: string, + /** + * A unique identifier for a user, cross device and platform + */ + userId: string, + transactionId: string, + productId: string, + productName: string, + quantity: number, + currency: string, + value: number +) { + const measurementId = config.GA4_MEASUREMENT_ID; + const secret = config.GA4_SECRET; + + if (!measurementId || !secret) { + console.log(chalkTemplate`{red GA4 not configured}`); + return; + } + + type EventPayload = { + /** + * Uniquely identifies a user instance of a web client + */ + client_id: string; + /** + * A unique identifier for a user, cross device and platform + */ + user_id?: string; + events: PurchaseEvent[]; + }; + + type PurchaseEvent = { + name: string; + params: PurchaseEventParams; + }; + + type PurchaseEventParams = { + currency?: string; + value?: number; + transaction_id: string; + items: Item[]; + }; + + type Item = { + item_id: string; + item_name: string; + quantity: number; + }; + + const payload: EventPayload = { + client_id: clientId, + user_id: userId, + events: [ + { + name: 'purchase', + params: { + transaction_id: transactionId, + currency: currency, + value: value, + items: [ + { item_id: productId, item_name: productName, quantity: quantity }, + ], + }, + }, + ], + }; + + console.log('Sending to GA: ', JSON.stringify(payload, null, 2)); + + request.post( + { + url: + 'https://www.google-analytics.com/mp/collect?api_secret=' + + secret + + '&measurement_id=' + + measurementId, + body: JSON.stringify(payload), + }, + function (err, res) { + console.log('Res: ', res.body); + if (err) { + console.error(err); + } else { + console.log('GA4 event sent successfully'); + } + } + ); +} diff --git a/backend/src/types.ts b/backend/src/types.ts index 9c8fba74c..a879731c2 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -61,6 +61,8 @@ export interface BackendConfig { MAIL_PASSWORD: string; MAIL_SENDER: string; MAIL_ALLOW_SELF_SIGNED_CERTS: boolean; + GA4_MEASUREMENT_ID: string; + GA4_SECRET: string; } export type LicenceMetadata = { diff --git a/backend/yarn.lock b/backend/yarn.lock index 21443fade..8cee6d9fa 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -814,6 +814,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/caseless@*": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" + integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== + "@types/connect-redis@0.0.19": version "0.0.19" resolved "https://registry.yarnpkg.com/@types/connect-redis/-/connect-redis-0.0.19.tgz#0895bbb1f2e3b5d2695158ec3f3ce367d1fca455" @@ -1094,6 +1099,16 @@ dependencies: "@types/node" "*" +"@types/request@^2.48.8": + version "2.48.8" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.8.tgz#0b90fde3b655ab50976cb8c5ac00faca22f5a82c" + integrity sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ== + dependencies: + "@types/caseless" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + form-data "^2.5.0" + "@types/semver@^7.3.12": version "7.3.13" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" @@ -1124,6 +1139,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/tough-cookie@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" + integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== + "@types/uuid@9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.0.tgz#53ef263e5239728b56096b0a869595135b7952d2" @@ -1259,7 +1279,7 @@ agent-base@6: dependencies: debug "4" -ajv@^6.10.0, ajv@^6.12.4: +ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1345,6 +1365,18 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + async@^2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" @@ -1357,6 +1389,16 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" + integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== + axios@^0.26.0: version "0.26.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" @@ -1444,6 +1486,13 @@ base64url@3.x.x: resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + bcryptjs@2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" @@ -1562,6 +1611,11 @@ caniuse-lite@^1.0.30001370: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356" integrity sha512-N/od11RX+Gsk+1qY/jbPa0R6zJupEa0lxeBG598EbrtblxVCTJsQwbRBm6+V+rxpc5lHKdsXb9RY83cZIPLseA== +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + chalk-template@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.5.0.tgz#fc4051675895e931eacf7f9a5f38763824c2d543" @@ -1676,7 +1730,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -1750,6 +1804,11 @@ copyfiles@2.4.1: untildify "^4.0.0" yargs "^16.1.0" +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -1782,6 +1841,13 @@ crypto-js@4.1.1: resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + data-uri-to-buffer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" @@ -1887,6 +1953,14 @@ dotenv@16.0.3, dotenv@^16.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2211,6 +2285,21 @@ express@4.18.2: utils-merge "1.0.1" vary "~1.1.2" +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2325,6 +2414,20 @@ follow-redirects@^1.14.8: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + +form-data@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.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" @@ -2334,6 +2437,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + 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" @@ -2402,6 +2514,13 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -2474,6 +2593,19 @@ handlebars@4.7.7: optionalDependencies: uglify-js "^3.1.4" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -2517,6 +2649,15 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -2647,6 +2788,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -2662,6 +2808,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -3154,6 +3305,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -3169,16 +3325,36 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + json-stable-stringify-without-jsonify@^1.0.1: version "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== +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json5@^2.2.1: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -3310,7 +3486,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -3482,6 +3658,11 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + oauth@0.9.x: version "0.9.15" resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" @@ -3725,6 +3906,11 @@ pause@0.0.1: resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + pg-connection-string@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" @@ -3881,6 +4067,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +psl@^1.1.28: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" @@ -3896,6 +4087,11 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +punycode@^2.1.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + qs@6.11.0, qs@^6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -3903,6 +4099,11 @@ qs@6.11.0, qs@^6.11.0: dependencies: side-channel "^1.0.4" +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -4005,6 +4206,32 @@ regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== +request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -4072,7 +4299,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@5.2.1, safe-buffer@^5.0.1: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4082,7 +4309,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -4280,6 +4507,21 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sshpk@^1.7.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + stack-utils@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" @@ -4449,6 +4691,14 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -4504,6 +4754,18 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -4634,6 +4896,11 @@ uuid@9.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== +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== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -4658,6 +4925,15 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" diff --git a/docs/package.json b/docs/package.json index 4487a4c3b..d593605cf 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "docs", - "version": "4.19.1", + "version": "4.19.2", "private": true, "scripts": { "docusaurus": "docusaurus", diff --git a/frontend/package.json b/frontend/package.json index 0e2f603be..64d352a98 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@retrospected/frontend", - "version": "4.19.1", + "version": "4.19.2", "license": "GNU GPLv3", "private": true, "dependencies": { @@ -57,7 +57,7 @@ "react-color": "2.19.3", "react-copy-to-clipboard": "5.1.0", "react-dom": "18.2.0", - "react-ga": "3.3.1", + "react-ga4": "^2.0.0", "react-giphy-searchbox": "1.5.4", "react-helmet": "6.1.0", "react-i18next": "^11.18.6", diff --git a/frontend/src/auth/modal/AnonAuth.tsx b/frontend/src/auth/modal/AnonAuth.tsx index 1b5ddb90b..29b79c21b 100644 --- a/frontend/src/auth/modal/AnonAuth.tsx +++ b/frontend/src/auth/modal/AnonAuth.tsx @@ -7,6 +7,7 @@ import { FullUser } from 'common'; import Wrapper from './Wrapper'; import { useTranslation } from 'react-i18next'; import { useLanguage } from 'translations'; +import { trackEvent } from 'track'; interface AnonAuthProps { onClose: () => void; @@ -28,6 +29,7 @@ const AnonAuth = ({ onClose, onUser }: AnonAuthProps) => { setError('Your anonymous account is not valid.'); return; } + trackEvent('register/anonymous'); let updatedUser = await me(); if (updatedUser?.language === null) { updatedUser = await updateLanguage(language.locale); diff --git a/frontend/src/auth/modal/account/Register.tsx b/frontend/src/auth/modal/account/Register.tsx index d542f4969..7bb237c52 100644 --- a/frontend/src/auth/modal/account/Register.tsx +++ b/frontend/src/auth/modal/account/Register.tsx @@ -17,17 +17,13 @@ import { validate } from 'isemail'; import UserContext from '../../Context'; import useBackendCapabilities from 'global/useBackendCapabilities'; import { useTranslation } from 'react-i18next'; +import { trackEvent } from 'track'; type RegisterProps = { onClose: () => void; }; -const PasswordStrength = lazy( - () => - import( - 'react-password-strength-bar' /* webpackChunkName: "password-strength" */ - ) -); +const PasswordStrength = lazy(() => import('react-password-strength-bar')); const Register = ({ onClose }: RegisterProps) => { const { t } = useTranslation(); @@ -64,6 +60,7 @@ const Register = ({ onClose }: RegisterProps) => { return; } } else { + trackEvent('register/password'); setIsSuccessful(true); if (response.loggedIn) { setUser(response.user); diff --git a/frontend/src/common/types.ts b/frontend/src/common/types.ts index 797c83b1f..198f07819 100644 --- a/frontend/src/common/types.ts +++ b/frontend/src/common/types.ts @@ -215,6 +215,12 @@ export type TrackingEvent = | 'trial/modal/open' | 'ack/error' | 'ack/refresh' + | 'register/password' + | 'register/oauth' + | 'register/anonymous' + | 'subscribe/initial' + | 'subscribe/launch-stripe' + | 'subscribe/purchased' | 'language/change/'; export type ColumnDefinitionType = diff --git a/frontend/src/components/Icon/Icon.tsx b/frontend/src/components/Icon/Icon.tsx index 54332ad27..3a7ad3c35 100644 --- a/frontend/src/components/Icon/Icon.tsx +++ b/frontend/src/components/Icon/Icon.tsx @@ -1,8 +1,6 @@ import { lazy, Suspense } from 'react'; -const IconInner = lazy( - () => import('./IconInner' /* webpackChunkName: "icon" */) -); +const IconInner = lazy(() => import('./IconInner')); type IconProps = { icon: string | null; diff --git a/frontend/src/ga.md b/frontend/src/ga.md new file mode 100644 index 000000000..229209016 --- /dev/null +++ b/frontend/src/ga.md @@ -0,0 +1 @@ +https://stripe.com/docs/payments/checkout/analyze-conversion-funnel#link-client-and-server-side-events \ No newline at end of file diff --git a/frontend/src/track.ts b/frontend/src/track.ts index ca8b83046..d9835059e 100644 --- a/frontend/src/track.ts +++ b/frontend/src/track.ts @@ -1,5 +1,5 @@ -import ReactGA from 'react-ga'; -import { TrackingEvent } from 'common'; +import ReactGA from 'react-ga4'; +import { Plan, TrackingEvent } from 'common'; import * as Sentry from '@sentry/browser'; import config from './utils/getConfig'; @@ -63,9 +63,20 @@ export const trackEvent = (event: TrackingEvent) => { } }; +export const trackPurchase = (plan: Plan, valueUsd: number) => { + if (isGAEnabled()) { + ReactGA.event({ + category: 'Event', + action: 'purchase', + value: valueUsd, + label: plan, + }); + } +}; + export const trackPageView = (path: string) => { if (isGAEnabled()) { - ReactGA.pageview(path); + ReactGA.send({ hitType: 'pageview', page: path }); } }; diff --git a/frontend/src/translations/languages.ts b/frontend/src/translations/languages.ts index cf03a0445..616d58311 100644 --- a/frontend/src/translations/languages.ts +++ b/frontend/src/translations/languages.ts @@ -1,35 +1,20 @@ import { Locale } from 'date-fns'; import { StripeLocales } from 'common'; -const arDZ = () => - import('date-fns/locale/ar-DZ' /* webpackChunkName: "date-fns-ar-DZ" */); -const zhCN = () => - import('date-fns/locale/zh-CN' /* webpackChunkName: "date-fns-zh-CN" */); -const zhTW = () => - import('date-fns/locale/zh-TW' /* webpackChunkName: "date-fns-zh-TW" */); -const fr = () => - import('date-fns/locale/fr' /* webpackChunkName: "date-fns-fr" */); -const enGB = () => - import('date-fns/locale/en-GB' /* webpackChunkName: "date-fns-en-GB" */); -const nl = () => - import('date-fns/locale/nl' /* webpackChunkName: "date-fns-nl" */); -const de = () => - import('date-fns/locale/de' /* webpackChunkName: "date-fns-de" */); -const hu = () => - import('date-fns/locale/hu' /* webpackChunkName: "date-fns-hu" */); -const it = () => - import('date-fns/locale/it' /* webpackChunkName: "date-fns-it" */); -const ja = () => - import('date-fns/locale/ja' /* webpackChunkName: "date-fns-ja" */); -const pl = () => - import('date-fns/locale/pl' /* webpackChunkName: "date-fns-pl" */); -const ptBR = () => - import('date-fns/locale/pt-BR' /* webpackChunkName: "date-fns-pt-BR" */); -const pt = () => - import('date-fns/locale/pt' /* webpackChunkName: "date-fns-pt" */); -const uk = () => - import('date-fns/locale/uk' /* webpackChunkName: "date-fns-uk" */); -const es = () => - import('date-fns/locale/es' /* webpackChunkName: "date-fns-es" */); +const arDZ = () => import('date-fns/locale/ar-DZ'); +const zhCN = () => import('date-fns/locale/zh-CN'); +const zhTW = () => import('date-fns/locale/zh-TW'); +const fr = () => import('date-fns/locale/fr'); +const enGB = () => import('date-fns/locale/en-GB'); +const nl = () => import('date-fns/locale/nl'); +const de = () => import('date-fns/locale/de'); +const hu = () => import('date-fns/locale/hu'); +const it = () => import('date-fns/locale/it'); +const ja = () => import('date-fns/locale/ja'); +const pl = () => import('date-fns/locale/pl'); +const ptBR = () => import('date-fns/locale/pt-BR'); +const pt = () => import('date-fns/locale/pt'); +const uk = () => import('date-fns/locale/uk'); +const es = () => import('date-fns/locale/es'); export interface Language { iso: string; diff --git a/frontend/src/views/Reset.tsx b/frontend/src/views/Reset.tsx index 23846f062..83c5d2266 100644 --- a/frontend/src/views/Reset.tsx +++ b/frontend/src/views/Reset.tsx @@ -9,12 +9,7 @@ import { VpnKey } from '@mui/icons-material'; import Button from '@mui/material/Button'; import { useTranslation } from 'react-i18next'; -const PasswordStrength = lazy( - () => - import( - 'react-password-strength-bar' /* webpackChunkName: "password-strength" */ - ) -); +const PasswordStrength = lazy(() => import('react-password-strength-bar')); function ResetPasswordPage() { const { setUser } = useContext(UserContext); diff --git a/frontend/src/views/admin/NewAccountModal.tsx b/frontend/src/views/admin/NewAccountModal.tsx index 78018a485..4b079f603 100644 --- a/frontend/src/views/admin/NewAccountModal.tsx +++ b/frontend/src/views/admin/NewAccountModal.tsx @@ -22,12 +22,7 @@ type NewAccountModalProps = { onAdd: (user: FullUser) => void; }; -const PasswordStrength = lazy( - () => - import( - 'react-password-strength-bar' /* webpackChunkName: "password-strength" */ - ) -); +const PasswordStrength = lazy(() => import('react-password-strength-bar')); export function NewAccountModal({ open, diff --git a/frontend/src/views/session-editor/sections/template/ColumnEditor.tsx b/frontend/src/views/session-editor/sections/template/ColumnEditor.tsx index 9322366a3..186ba7528 100644 --- a/frontend/src/views/session-editor/sections/template/ColumnEditor.tsx +++ b/frontend/src/views/session-editor/sections/template/ColumnEditor.tsx @@ -10,9 +10,7 @@ import { colors } from '@mui/material'; import useMediaQuery from '@mui/material/useMediaQuery'; import { DeleteForeverOutlined } from '@mui/icons-material'; -const IconPicker = lazy( - () => import('./IconPicker' /* webpackChunkName: "icon-picker" */) -); +const IconPicker = lazy(() => import('./IconPicker')); interface ColumnEditorProps { value: ColumnSettings; diff --git a/frontend/src/views/subscribe/SubscribePage.tsx b/frontend/src/views/subscribe/SubscribePage.tsx index 0165b2f33..eb6e5b8a4 100644 --- a/frontend/src/views/subscribe/SubscribePage.tsx +++ b/frontend/src/views/subscribe/SubscribePage.tsx @@ -18,6 +18,7 @@ import { useLanguage } from '../../translations'; import { useTranslation } from 'react-i18next'; import useProducts from './components/useProducts'; import { find } from 'lodash'; +import { trackEvent } from 'track'; function guessDomain(user: FullUser): string { if (user.email) { @@ -58,6 +59,10 @@ function SubscriberPage() { (!user || user.accountType === 'anonymous'); const [validDomain, setValidDomain] = useState(false); + useEffect(() => { + trackEvent('subscribe/initial'); + }, []); + useEffect(() => { setValidDomain(false); const domainRegex = @@ -83,6 +88,7 @@ function SubscriberPage() { const handleCheckout = useCallback(async () => { if (product) { + trackEvent('subscribe/launch-stripe'); if (!product.recurring) { const stripeUrl = product.paymentsUrls?.[currency]; if (stripeUrl) { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5051ad688..e356875f0 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -8731,10 +8731,10 @@ react-fast-compare@^3.1.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-ga@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.1.tgz#d8e1f4e05ec55ed6ff944dcb14b99011dfaf9504" - integrity sha512-4Vc0W5EvXAXUN/wWyxvsAKDLLgtJ3oLmhYYssx+YzphJpejtOst6cbIHCIyF50Fdxuf5DDKqRYny24yJ2y7GFQ== +react-ga4@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-ga4/-/react-ga4-2.0.0.tgz#d125807cc30b087a6aa24401ec5f1f1c2d176fe9" + integrity sha512-WHi98hMunzh4ngRdNil8NN6Qly3ZZUkprhbgSqA+NFzovp8zqpYV3jcbKL9FnMdeBQHGhv8AVNCT2oFEE+EFXA== react-giphy-searchbox@1.5.4: version "1.5.4" diff --git a/integration/package.json b/integration/package.json index 603f06876..bcfcf2c0f 100644 --- a/integration/package.json +++ b/integration/package.json @@ -1,6 +1,6 @@ { "name": "retro-board-integration", - "version": "4.19.1", + "version": "4.19.2", "description": "Integrations tests", "main": "index.js", "directories": { diff --git a/package.json b/package.json index 8dc4bb5d8..4605b0f31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "retrospected", - "version": "4.19.1", + "version": "4.19.2", "description": "An agile retrospective board - Powering www.retrospected.com", "private": true, "scripts": {