From 263b02d4e77b7bc2dfac3915b6151fbf0b777c7f Mon Sep 17 00:00:00 2001 From: LongYinan Date: Fri, 22 Mar 2024 16:30:33 +0800 Subject: [PATCH] chore(core): add mixpanel track --- .github/workflows/deploy.yml | 1 + .github/workflows/release-desktop.yml | 1 + packages/backend/server/package.json | 2 + packages/backend/server/src/app.ts | 7 + .../server/src/fundamentals/config/def.ts | 5 + .../server/src/fundamentals/config/default.ts | 4 + packages/frontend/core/package.json | 2 + .../src/components/affine/auth/sign-in.tsx | 5 + .../quota-reached-modal/cloud-quota-modal.tsx | 10 ++ .../core/src/providers/modal-provider.tsx | 6 +- .../core/src/providers/session-provider.tsx | 7 + packages/frontend/core/src/router.tsx | 134 ++++++++++-------- packages/frontend/core/src/utils/index.ts | 1 + packages/frontend/core/src/utils/mixpanel.ts | 25 ++++ packages/frontend/electron/package.json | 2 + packages/frontend/electron/renderer/app.tsx | 8 ++ packages/frontend/web/package.json | 2 + packages/frontend/web/src/app.tsx | 8 ++ tests/storybook/src/stories/core.stories.tsx | 16 +-- .../src/stories/page-list.stories.tsx | 4 +- yarn.lock | 48 +++++++ 21 files changed, 230 insertions(+), 68 deletions(-) create mode 100644 packages/frontend/core/src/utils/mixpanel.ts diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 96c23fe6babd5..fb47d43e88f5f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,6 +15,7 @@ on: env: APP_NAME: affine NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + MIXPANEL_TOKEN: '389c0615a69b57cca7d3fa0a4824c930' jobs: build-server: diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 95a12a9a59963..63bf14c4d884f 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -33,6 +33,7 @@ env: DEBUG: napi:* APP_NAME: affine MACOSX_DEPLOYMENT_TARGET: '10.13' + MIXPANEL_TOKEN: '389c0615a69b57cca7d3fa0a4824c930' jobs: before-make: diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json index 67db8461b655a..8768adec3576c 100644 --- a/packages/backend/server/package.json +++ b/packages/backend/server/package.json @@ -71,6 +71,7 @@ "ioredis": "^5.3.2", "keyv": "^4.5.4", "lodash-es": "^4.17.21", + "mixpanel": "^0.18.0", "nanoid": "^5.0.6", "nest-commander": "^3.12.5", "nestjs-throttler-storage-redis": "^0.4.1", @@ -102,6 +103,7 @@ "@types/graphql-upload": "^16.0.7", "@types/keyv": "^4.2.0", "@types/lodash-es": "^4.17.12", + "@types/mixpanel": "^2.14.8", "@types/node": "^20.11.20", "@types/nodemailer": "^6.4.14", "@types/on-headers": "^1.0.3", diff --git a/packages/backend/server/src/app.ts b/packages/backend/server/src/app.ts index b1aff485983f7..bb825398cedfc 100644 --- a/packages/backend/server/src/app.ts +++ b/packages/backend/server/src/app.ts @@ -43,5 +43,12 @@ export async function createApp() { app.useWebSocketAdapter(adapter); } + if (AFFiNE.isSelfhosted && AFFiNE.telemetry.enabled) { + const mixpanel = await import('mixpanel'); + mixpanel.init(AFFiNE.telemetry.token).track('selfhost-server-started', { + version: AFFiNE.version, + }); + } + return app; } diff --git a/packages/backend/server/src/fundamentals/config/def.ts b/packages/backend/server/src/fundamentals/config/def.ts index 9c8825610f6cb..203ab66eec32a 100644 --- a/packages/backend/server/src/fundamentals/config/def.ts +++ b/packages/backend/server/src/fundamentals/config/def.ts @@ -333,6 +333,11 @@ export interface AFFiNEConfig { metrics: { enabled: boolean; }; + + telemetry: { + enabled: boolean; + token: string; + }; } export * from './storage'; diff --git a/packages/backend/server/src/fundamentals/config/default.ts b/packages/backend/server/src/fundamentals/config/default.ts index b06aad366d9d6..c8253e8ee8cf1 100644 --- a/packages/backend/server/src/fundamentals/config/default.ts +++ b/packages/backend/server/src/fundamentals/config/default.ts @@ -192,6 +192,10 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => { metrics: { enabled: false, }, + telemetry: { + enabled: isSelfhosted && !process.env.DISABLE_SERVER_TELEMETRY, + token: '389c0615a69b57cca7d3fa0a4824c930', + }, plugins: { enabled: new Set(), use(plugin, config) { diff --git a/packages/frontend/core/package.json b/packages/frontend/core/package.json index 1b4a97ac83696..f5478aca1b91f 100644 --- a/packages/frontend/core/package.json +++ b/packages/frontend/core/package.json @@ -68,6 +68,7 @@ "lodash-es": "^4.17.21", "lottie-react": "^2.4.0", "lottie-web": "^5.12.2", + "mixpanel-browser": "^2.49.0", "nanoid": "^5.0.6", "next-themes": "^0.3.0", "react": "18.2.0", @@ -97,6 +98,7 @@ "@types/bytes": "^3.1.4", "@types/image-blob-reduce": "^4.1.4", "@types/lodash-es": "^4.17.12", + "@types/mixpanel-browser": "^2.49.0", "@types/uuid": "^9.0.8", "@vanilla-extract/css": "^1.14.1", "express": "^4.18.2", diff --git a/packages/frontend/core/src/components/affine/auth/sign-in.tsx b/packages/frontend/core/src/components/affine/auth/sign-in.tsx index 0d1a7a6a2fa5c..aa5e8b0caf6b7 100644 --- a/packages/frontend/core/src/components/affine/auth/sign-in.tsx +++ b/packages/frontend/core/src/components/affine/auth/sign-in.tsx @@ -18,6 +18,7 @@ import { useCallback } from 'react'; import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status'; import { useMutation } from '../../../hooks/use-mutation'; +import { mixpanel } from '../../../utils'; import { emailRegex } from '../../../utils/email-regex'; import type { AuthPanelProps } from './index'; import { OAuth } from './oauth'; @@ -97,6 +98,10 @@ export const SignIn: FC = ({ if (res?.status === 403 && res?.url === INTERNAL_BETA_URL) { return setAuthState('noAccess'); } + // TODO, should always get id from user + if ('id' in user) { + mixpanel.identify(user.id); + } setAuthState('afterSignInSendEmail'); } } else { diff --git a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx index e05e0d7f4fd6d..3e1f44ebf6a36 100644 --- a/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx +++ b/packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx @@ -9,6 +9,8 @@ import bytes from 'bytes'; import { useAtom, useSetAtom } from 'jotai'; import { useCallback, useEffect, useMemo } from 'react'; +import { mixpanel } from '../../../utils'; + export const CloudQuotaModal = () => { const t = useAFFiNEI18N(); const currentWorkspace = useService(Workspace); @@ -70,6 +72,14 @@ export const CloudQuotaModal = () => { }; }, [currentWorkspace.engine.blob, setOpen, workspaceQuota.blobLimit]); + useEffect(() => { + if (userQuota?.humanReadable) { + mixpanel.people.set({ + plan: userQuota.humanReadable.name, + }); + } + }, [userQuota]); + return ( @@ -218,6 +218,8 @@ export const SignOutConfirmModal = () => { setOpen(false); await signOutCloud(); + mixpanel.reset(); + // if current workspace is affine cloud, switch to local workspace if (currentWorkspace?.flavour === WorkspaceFlavour.AFFINE_CLOUD) { const localWorkspace = workspaces.find( diff --git a/packages/frontend/core/src/providers/session-provider.tsx b/packages/frontend/core/src/providers/session-provider.tsx index 4993b40568f3a..d0abae552180a 100644 --- a/packages/frontend/core/src/providers/session-provider.tsx +++ b/packages/frontend/core/src/providers/session-provider.tsx @@ -13,6 +13,7 @@ import { } from 'react'; import { useOnceSignedInEvents } from '../atoms/event'; +import { mixpanel } from '../utils'; export const CloudSessionProvider = (props: PropsWithChildren) => { const session = useSession(); @@ -28,6 +29,12 @@ export const CloudSessionProvider = (props: PropsWithChildren) => { ).postMessage(1); }, [onceSignedInEvents]); + useEffect(() => { + if (session.user?.id) { + mixpanel.identify(session.user.id); + } + }, [session]); + useEffect(() => { if (prevSession.current !== session && session.status !== 'loading') { // unauthenticated -> authenticated diff --git a/packages/frontend/core/src/router.tsx b/packages/frontend/core/src/router.tsx index d0b46d2c28753..053963a6041a1 100644 --- a/packages/frontend/core/src/router.tsx +++ b/packages/frontend/core/src/router.tsx @@ -1,59 +1,81 @@ -import * as Sentry from '@sentry/react'; +import { wrapCreateBrowserRouter } from '@sentry/react'; +import { useEffect } from 'react'; import type { RouteObject } from 'react-router-dom'; -import { createBrowserRouter as reactRouterCreateBrowserRouter } from 'react-router-dom'; +import { + createBrowserRouter as reactRouterCreateBrowserRouter, + Outlet, + useLocation, +} from 'react-router-dom'; -export const workbenchRoutes = [ - { - path: '/', - lazy: () => import('./pages/index'), - }, - { - path: '/workspace/:workspaceId/*', - lazy: () => import('./pages/workspace/index'), - }, - { - path: '/share/:workspaceId/:pageId', - lazy: () => import('./pages/share/share-detail-page'), - }, - { - path: '/404', - lazy: () => import('./pages/404'), - }, - { - path: '/auth/:authType', - lazy: () => import('./pages/auth'), - }, - { - path: '/expired', - lazy: () => import('./pages/expired'), - }, - { - path: '/invite/:inviteId', - lazy: () => import('./pages/invite'), - }, - { - path: '/signIn', - lazy: () => import('./pages/sign-in'), - }, - { - path: '/open-app/:action', - lazy: () => import('./pages/open-app'), - }, - { - path: '/upgrade-success', - lazy: () => import('./pages/upgrade-success'), - }, - { - path: '/desktop-signin', - lazy: () => import('./pages/desktop-signin'), - }, - { - path: '/onboarding', - lazy: () => import('./pages/onboarding'), - }, - { - path: '*', - lazy: () => import('./pages/404'), +import { mixpanel } from './utils'; + +function RootRouter() { + const location = useLocation(); + useEffect(() => { + mixpanel.track_pageview({ + page: location.pathname, + }); + }, [location]); + return ; +} + +export const topLevelRoutes = [ + { + element: , + children: [ + { + path: '/', + lazy: () => import('./pages/index'), + }, + { + path: '/workspace/:workspaceId/*', + lazy: () => import('./pages/workspace/index'), + }, + { + path: '/share/:workspaceId/:pageId', + lazy: () => import('./pages/share/share-detail-page'), + }, + { + path: '/404', + lazy: () => import('./pages/404'), + }, + { + path: '/auth/:authType', + lazy: () => import('./pages/auth'), + }, + { + path: '/expired', + lazy: () => import('./pages/expired'), + }, + { + path: '/invite/:inviteId', + lazy: () => import('./pages/invite'), + }, + { + path: '/signIn', + lazy: () => import('./pages/sign-in'), + }, + { + path: '/open-app/:action', + lazy: () => import('./pages/open-app'), + }, + { + path: '/upgrade-success', + lazy: () => import('./pages/upgrade-success'), + }, + { + path: '/desktop-signin', + lazy: () => import('./pages/desktop-signin'), + }, + { + path: '/onboarding', + lazy: () => import('./pages/onboarding'), + }, + { + path: '*', + lazy: () => import('./pages/404'), + }, + ], }, ] satisfies [RouteObject, ...RouteObject[]]; @@ -92,10 +114,10 @@ export const viewRoutes = [ }, ] satisfies [RouteObject, ...RouteObject[]]; -const createBrowserRouter = Sentry.wrapCreateBrowserRouter( +const createBrowserRouter = wrapCreateBrowserRouter( reactRouterCreateBrowserRouter ); -export const router = createBrowserRouter(workbenchRoutes, { +export const router = createBrowserRouter(topLevelRoutes, { future: { v7_normalizeFormMethod: true, }, diff --git a/packages/frontend/core/src/utils/index.ts b/packages/frontend/core/src/utils/index.ts index d410ad668c6db..a2defbb1809ba 100644 --- a/packages/frontend/core/src/utils/index.ts +++ b/packages/frontend/core/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './create-emotion-cache'; export * from './intl-formatter'; +export * from './mixpanel'; export * from './string2color'; export * from './toast'; diff --git a/packages/frontend/core/src/utils/mixpanel.ts b/packages/frontend/core/src/utils/mixpanel.ts new file mode 100644 index 0000000000000..6920d2e170a91 --- /dev/null +++ b/packages/frontend/core/src/utils/mixpanel.ts @@ -0,0 +1,25 @@ +import mixpanelBrowser, { type OverridedMixpanel } from 'mixpanel-browser'; + +export const mixpanel = process.env.MIXPANEL_TOKEN + ? mixpanelBrowser + : new Proxy( + function () {} as unknown as OverridedMixpanel, + createProxyHandler() + ); + +function createProxyHandler(property?: string | symbol) { + const handler = { + get: (_target, property) => { + return new Proxy( + function () {} as unknown as OverridedMixpanel, + createProxyHandler(property) + ); + }, + apply: (_target, _thisArg, args) => { + console.info( + `Mixpanel is not initialized, calling ${property ? String(property) : 'mixpanel'} with args: ${JSON.stringify(args)}` + ); + }, + } as ProxyHandler; + return handler; +} diff --git a/packages/frontend/electron/package.json b/packages/frontend/electron/package.json index 5e2f5d3d04586..b64b452d80af2 100644 --- a/packages/frontend/electron/package.json +++ b/packages/frontend/electron/package.json @@ -44,6 +44,7 @@ "@emotion/react": "^11.11.4", "@pengx17/electron-forge-maker-appimage": "^1.1.1", "@toeverything/infra": "workspace:*", + "@types/mixpanel-browser": "^2.49.0", "@types/uuid": "^9.0.8", "builder-util-runtime": "^9.2.4", "cross-env": "^7.0.3", @@ -57,6 +58,7 @@ "jotai": "^2.6.5", "jotai-devtools": "^0.8.0", "lodash-es": "^4.17.21", + "mixpanel-browser": "^2.49.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.3", diff --git a/packages/frontend/electron/renderer/app.tsx b/packages/frontend/electron/renderer/app.tsx index a75c38f4ee648..d6f250c3db435 100644 --- a/packages/frontend/electron/renderer/app.tsx +++ b/packages/frontend/electron/renderer/app.tsx @@ -18,6 +18,7 @@ import { createI18n, setUpLanguage } from '@affine/i18n'; import { CacheProvider } from '@emotion/react'; import { getCurrentStore } from '@toeverything/infra/atom'; import { ServiceCollection } from '@toeverything/infra/di'; +import mixpanel from 'mixpanel-browser'; import type { PropsWithChildren, ReactElement } from 'react'; import { lazy, Suspense } from 'react'; import { RouterProvider } from 'react-router-dom'; @@ -62,6 +63,13 @@ const serviceProvider = services.provider(); export function App() { performanceRenderLogger.info('App'); + if (process.env.MIXPANEL_TOKEN) { + mixpanel.init(process.env.MIXPANEL_TOKEN || '', { + track_pageview: true, + persistence: 'localStorage', + }); + } + if (!languageLoadingPromise) { languageLoadingPromise = loadLanguage().catch(console.error); } diff --git a/packages/frontend/web/package.json b/packages/frontend/web/package.json index afa91f82eb2a0..6ecd6a1aae20f 100644 --- a/packages/frontend/web/package.json +++ b/packages/frontend/web/package.json @@ -15,11 +15,13 @@ "@affine/env": "workspace:*", "@juggle/resize-observer": "^3.4.0", "intl-segmenter-polyfill-rs": "^0.1.7", + "mixpanel-browser": "^2.49.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@affine/cli": "workspace:*", + "@types/mixpanel-browser": "^2.49.0", "@types/react": "^18.2.60", "@types/react-dom": "^18.2.19", "typescript": "^5.3.3" diff --git a/packages/frontend/web/src/app.tsx b/packages/frontend/web/src/app.tsx index a75c38f4ee648..d6f250c3db435 100644 --- a/packages/frontend/web/src/app.tsx +++ b/packages/frontend/web/src/app.tsx @@ -18,6 +18,7 @@ import { createI18n, setUpLanguage } from '@affine/i18n'; import { CacheProvider } from '@emotion/react'; import { getCurrentStore } from '@toeverything/infra/atom'; import { ServiceCollection } from '@toeverything/infra/di'; +import mixpanel from 'mixpanel-browser'; import type { PropsWithChildren, ReactElement } from 'react'; import { lazy, Suspense } from 'react'; import { RouterProvider } from 'react-router-dom'; @@ -62,6 +63,13 @@ const serviceProvider = services.provider(); export function App() { performanceRenderLogger.info('App'); + if (process.env.MIXPANEL_TOKEN) { + mixpanel.init(process.env.MIXPANEL_TOKEN || '', { + track_pageview: true, + persistence: 'localStorage', + }); + } + if (!languageLoadingPromise) { languageLoadingPromise = loadLanguage().catch(console.error); } diff --git a/tests/storybook/src/stories/core.stories.tsx b/tests/storybook/src/stories/core.stories.tsx index 1fde586bd19f8..fbaa44dd8b8bf 100644 --- a/tests/storybook/src/stories/core.stories.tsx +++ b/tests/storybook/src/stories/core.stories.tsx @@ -1,5 +1,5 @@ import { NavigateContext } from '@affine/core/hooks/use-navigate-helper'; -import { workbenchRoutes } from '@affine/core/router'; +import { topLevelRoutes } from '@affine/core/router'; import { assertExists } from '@blocksuite/global/utils'; import type { StoryFn } from '@storybook/react'; import { screen, userEvent, waitFor, within } from '@storybook/testing-library'; @@ -34,7 +34,7 @@ export const Index: StoryFn = () => { Index.decorators = [withRouter]; Index.parameters = { reactRouter: reactRouterParameters({ - routing: reactRouterOutlets(workbenchRoutes), + routing: reactRouterOutlets(topLevelRoutes), }), }; @@ -80,7 +80,7 @@ SettingPage.play = async ({ canvasElement, step }) => { SettingPage.decorators = [withRouter]; SettingPage.parameters = { reactRouter: reactRouterParameters({ - routing: reactRouterOutlets(workbenchRoutes), + routing: reactRouterOutlets(topLevelRoutes), }), }; @@ -90,7 +90,7 @@ export const NotFoundPage: StoryFn = () => { NotFoundPage.decorators = [withRouter]; NotFoundPage.parameters = { reactRouter: reactRouterParameters({ - routing: reactRouterOutlets(workbenchRoutes), + routing: reactRouterOutlets(topLevelRoutes), location: { path: '/404', }, @@ -120,7 +120,7 @@ WorkspaceList.play = async ({ canvasElement }) => { WorkspaceList.decorators = [withRouter]; WorkspaceList.parameters = { reactRouter: reactRouterParameters({ - routing: reactRouterOutlets(workbenchRoutes), + routing: reactRouterOutlets(topLevelRoutes), location: { path: '/', }, @@ -157,7 +157,7 @@ SearchPage.play = async ({ canvasElement }) => { SearchPage.decorators = [withRouter]; SearchPage.parameters = { reactRouter: reactRouterParameters({ - routing: reactRouterOutlets(workbenchRoutes), + routing: reactRouterOutlets(topLevelRoutes), location: { path: '/', }, @@ -199,7 +199,7 @@ ImportPage.play = async ({ canvasElement }) => { ImportPage.decorators = [withRouter]; ImportPage.parameters = { reactRouter: reactRouterParameters({ - routing: reactRouterOutlets(workbenchRoutes), + routing: reactRouterOutlets(topLevelRoutes), location: { path: '/', }, @@ -212,7 +212,7 @@ export const OpenAppPage: StoryFn = () => { OpenAppPage.decorators = [withRouter]; OpenAppPage.parameters = { reactRouter: reactRouterParameters({ - routing: reactRouterOutlets(workbenchRoutes), + routing: reactRouterOutlets(topLevelRoutes), location: { path: '/open-app/url', searchParams: { diff --git a/tests/storybook/src/stories/page-list.stories.tsx b/tests/storybook/src/stories/page-list.stories.tsx index 61259f6e6431b..24440abca08c2 100644 --- a/tests/storybook/src/stories/page-list.stories.tsx +++ b/tests/storybook/src/stories/page-list.stories.tsx @@ -13,7 +13,7 @@ import { PageTags, type PageTagsProps, } from '@affine/core/components/page-list'; -import { workbenchRoutes } from '@affine/core/router'; +import { topLevelRoutes } from '@affine/core/router'; import { AffineSchemas } from '@blocksuite/blocks/schemas'; import { PageIcon, TagsIcon } from '@blocksuite/icons'; import { DocCollection, Schema } from '@blocksuite/store'; @@ -49,7 +49,7 @@ AffineOperationCell.args = { }; AffineOperationCell.parameters = { reactRouter: reactRouterParameters({ - routing: reactRouterOutlets(workbenchRoutes), + routing: reactRouterOutlets(topLevelRoutes), }), }; AffineOperationCell.play = async ({ canvasElement }) => { diff --git a/yarn.lock b/yarn.lock index 0fc3f1d1516e7..105f0ab95493e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -362,6 +362,7 @@ __metadata: "@types/bytes": "npm:^3.1.4" "@types/image-blob-reduce": "npm:^4.1.4" "@types/lodash-es": "npm:^4.17.12" + "@types/mixpanel-browser": "npm:^2.49.0" "@types/uuid": "npm:^9.0.8" "@vanilla-extract/css": "npm:^1.14.1" "@vanilla-extract/dynamic": "npm:^2.1.0" @@ -389,6 +390,7 @@ __metadata: lottie-react: "npm:^2.4.0" lottie-web: "npm:^5.12.2" mime-types: "npm:^2.1.35" + mixpanel-browser: "npm:^2.49.0" nanoid: "npm:^5.0.6" next-themes: "npm:^0.3.0" react: "npm:18.2.0" @@ -465,6 +467,7 @@ __metadata: "@emotion/react": "npm:^11.11.4" "@pengx17/electron-forge-maker-appimage": "npm:^1.1.1" "@toeverything/infra": "workspace:*" + "@types/mixpanel-browser": "npm:^2.49.0" "@types/uuid": "npm:^9.0.8" async-call-rpc: "npm:^6.4.0" builder-util-runtime: "npm:^9.2.4" @@ -481,6 +484,7 @@ __metadata: jotai-devtools: "npm:^0.8.0" link-preview-js: "npm:^3.0.5" lodash-es: "npm:^4.17.21" + mixpanel-browser: "npm:^2.49.0" nanoid: "npm:^5.0.6" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" @@ -684,6 +688,7 @@ __metadata: "@types/graphql-upload": "npm:^16.0.7" "@types/keyv": "npm:^4.2.0" "@types/lodash-es": "npm:^4.17.12" + "@types/mixpanel": "npm:^2.14.8" "@types/node": "npm:^20.11.20" "@types/nodemailer": "npm:^6.4.14" "@types/on-headers": "npm:^1.0.3" @@ -706,6 +711,7 @@ __metadata: ioredis: "npm:^5.3.2" keyv: "npm:^4.5.4" lodash-es: "npm:^4.17.21" + mixpanel: "npm:^0.18.0" nanoid: "npm:^5.0.6" nest-commander: "npm:^3.12.5" nestjs-throttler-storage-redis: "npm:^0.4.1" @@ -818,9 +824,11 @@ __metadata: "@affine/core": "workspace:*" "@affine/env": "workspace:*" "@juggle/resize-observer": "npm:^3.4.0" + "@types/mixpanel-browser": "npm:^2.49.0" "@types/react": "npm:^18.2.60" "@types/react-dom": "npm:^18.2.19" intl-segmenter-polyfill-rs: "npm:^0.1.7" + mixpanel-browser: "npm:^2.49.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" typescript: "npm:^5.3.3" @@ -14280,6 +14288,20 @@ __metadata: languageName: node linkType: hard +"@types/mixpanel-browser@npm:^2.49.0": + version: 2.49.0 + resolution: "@types/mixpanel-browser@npm:2.49.0" + checksum: 10/8aa9f114098cd9e2b06314586d73934aa83a8116b7de61bd41208728aebd47daa30134a98c4af3d65c4e755fbbdb6c450058a34ad2204305587fbe8e27f844b3 + languageName: node + linkType: hard + +"@types/mixpanel@npm:^2.14.8": + version: 2.14.8 + resolution: "@types/mixpanel@npm:2.14.8" + checksum: 10/b0be92f54226986aa7e4a789692d223008673f3eb42cb2a52fe65089c15dc1ee21bbe083c14a8867fe9a2ed32925510cbd772a6ea4aebb5b93460ece9bd602a7 + languageName: node + linkType: hard + "@types/ms@npm:*": version: 0.7.34 resolution: "@types/ms@npm:0.7.34" @@ -23243,6 +23265,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:5.0.0": + version: 5.0.0 + resolution: "https-proxy-agent@npm:5.0.0" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10/517037badcbbe30757a9a88aaf5e8c198d31aa0b1e9c0a49a0053ab8e812809242218cc9ea1929171f74d95ae1ec89782ba471ffc3709b8910e91d1761f5f1a6 + languageName: node + linkType: hard + "https-proxy-agent@npm:^4.0.0": version: 4.0.0 resolution: "https-proxy-agent@npm:4.0.0" @@ -27642,6 +27674,22 @@ __metadata: languageName: node linkType: hard +"mixpanel-browser@npm:^2.49.0": + version: 2.49.0 + resolution: "mixpanel-browser@npm:2.49.0" + checksum: 10/46b738792153caa35c083f3e23b208d48fac0f040074d2f36dc95bac49e86729676ffb599b4b50d814231c4ae8170110419e748a1cbe0d5432ea415f0fe8adc4 + languageName: node + linkType: hard + +"mixpanel@npm:^0.18.0": + version: 0.18.0 + resolution: "mixpanel@npm:0.18.0" + dependencies: + https-proxy-agent: "npm:5.0.0" + checksum: 10/90cd6be667e75d5a34fc54c2b7947f9d095736f8925d68586143f73ad525503d3951e852b3ae4349df04f21a1fa810325023d20126c145ebaff96b03745b732c + languageName: node + linkType: hard + "mkdirp-classic@npm:^0.5.2": version: 0.5.3 resolution: "mkdirp-classic@npm:0.5.3"