From 92a1d672fd1bd0e101e5ff2d3a99b8f45db189b6 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Thu, 1 Feb 2024 14:19:24 +0100 Subject: [PATCH] :recycle: Re-organize telemetry package --- apps/builder/package.json | 5 +- .../src/features/account/UserProvider.tsx | 2 - .../src/features/auth/api/customAdapter.ts | 6 +- .../billing/api/updateSubscription.ts | 4 +- .../telemetry/api/processTelemetryEvent.ts | 91 ------------------- .../src/features/telemetry/posthog.tsx | 27 ------ .../src/features/typebot/api/createTypebot.ts | 4 +- .../src/features/typebot/api/getTypebot.ts | 2 +- .../src/features/typebot/api/importTypebot.ts | 4 +- .../features/typebot/api/publishTypebot.ts | 4 +- .../typebot/helpers/isReadTypebotForbidden.ts | 2 +- .../features/workspace/api/createWorkspace.ts | 4 +- .../helpers/isReadWorkspaceFobidden.ts | 2 +- .../helpers/parseWorkspaceDefaultPlan.ts | 3 +- apps/builder/src/helpers/databaseRules.ts | 3 +- .../helpers/server/routers/publicRouter.ts | 2 - apps/builder/src/pages/_app.tsx | 3 - apps/builder/src/pages/api/stripe/webhook.ts | 12 +-- apps/docs/self-hosting/configuration.mdx | 2 +- packages/embeds/js/package.json | 2 +- packages/env/env.ts | 6 +- packages/lib/package.json | 1 + packages/lib/telemetry/sendTelemetryEvent.ts | 29 ------ packages/lib/telemetry/trackEvents.ts | 54 +++++++++++ packages/scripts/checkAndReportChatsUsage.ts | 4 +- pnpm-lock.yaml | 30 ++---- 26 files changed, 102 insertions(+), 206 deletions(-) delete mode 100644 apps/builder/src/features/telemetry/api/processTelemetryEvent.ts delete mode 100644 apps/builder/src/features/telemetry/posthog.tsx delete mode 100644 packages/lib/telemetry/sendTelemetryEvent.ts create mode 100644 packages/lib/telemetry/trackEvents.ts diff --git a/apps/builder/package.json b/apps/builder/package.json index b56758372..a6560f983 100644 --- a/apps/builder/package.json +++ b/apps/builder/package.json @@ -78,8 +78,6 @@ "nprogress": "0.2.0", "openai": "4.24.1", "papaparse": "5.4.1", - "posthog-js": "1.77.1", - "posthog-node": "3.1.1", "prettier": "2.8.8", "qs": "6.11.2", "react": "18.2.0", @@ -95,7 +93,8 @@ "tinycolor2": "1.6.0", "unsplash-js": "7.0.18", "use-debounce": "9.0.4", - "zustand": "4.5.0" + "zustand": "4.5.0", + "ky": "1.1.3" }, "devDependencies": { "@chakra-ui/styled-system": "2.9.1", diff --git a/apps/builder/src/features/account/UserProvider.tsx b/apps/builder/src/features/account/UserProvider.tsx index 00e1e2b94..c59bda65f 100644 --- a/apps/builder/src/features/account/UserProvider.tsx +++ b/apps/builder/src/features/account/UserProvider.tsx @@ -8,7 +8,6 @@ import { useToast } from '@/hooks/useToast' import { updateUserQuery } from './queries/updateUserQuery' import { useDebouncedCallback } from 'use-debounce' import { env } from '@typebot.io/env' -import { identifyUser } from '../telemetry/posthog' import { useColorMode } from '@chakra-ui/react' export const userContext = createContext<{ @@ -62,7 +61,6 @@ export const UserProvider = ({ children }: { children: ReactNode }) => { if (parsedUser?.id) { setSentryUser({ id: parsedUser.id }) - identifyUser(parsedUser.id) } }, [session, user]) diff --git a/apps/builder/src/features/auth/api/customAdapter.ts b/apps/builder/src/features/auth/api/customAdapter.ts index 828ea8f37..c07367b21 100644 --- a/apps/builder/src/features/auth/api/customAdapter.ts +++ b/apps/builder/src/features/auth/api/customAdapter.ts @@ -8,13 +8,13 @@ import { import type { Adapter, AdapterUser } from 'next-auth/adapters' import { createId } from '@paralleldrive/cuid2' import { generateId } from '@typebot.io/lib' -import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry' import { convertInvitationsToCollaborations } from '@/features/auth/helpers/convertInvitationsToCollaborations' import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations' import { joinWorkspaces } from '@/features/auth/helpers/joinWorkspaces' import { parseWorkspaceDefaultPlan } from '@/features/workspace/helpers/parseWorkspaceDefaultPlan' import { env } from '@typebot.io/env' +import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents' export function customAdapter(p: PrismaClient): Adapter { return { @@ -28,7 +28,7 @@ export function customAdapter(p: PrismaClient): Adapter { ) if ( env.DISABLE_SIGNUP && - env.ADMIN_EMAIL !== user.email && + env.ADMIN_EMAIL?.every((email) => email !== user.email) && invitations.length === 0 && workspaceInvitations.length === 0 ) @@ -80,7 +80,7 @@ export function customAdapter(p: PrismaClient): Adapter { name: data.name ? (data.name as string).split(' ')[0] : undefined, }, }) - await sendTelemetryEvents(events) + await trackEvents(events) if (invitations.length > 0) await convertInvitationsToCollaborations(p, user, invitations) if (workspaceInvitations.length > 0) diff --git a/apps/builder/src/features/billing/api/updateSubscription.ts b/apps/builder/src/features/billing/api/updateSubscription.ts index f96cda1b3..ed92f3a08 100644 --- a/apps/builder/src/features/billing/api/updateSubscription.ts +++ b/apps/builder/src/features/billing/api/updateSubscription.ts @@ -1,4 +1,3 @@ -import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' import prisma from '@typebot.io/lib/prisma' import { authenticatedProcedure } from '@/helpers/server/trpc' import { TRPCError } from '@trpc/server' @@ -9,6 +8,7 @@ import { z } from 'zod' import { createCheckoutSessionUrl } from './createCheckoutSession' import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden' import { env } from '@typebot.io/env' +import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents' export const updateSubscription = authenticatedProcedure .meta({ @@ -157,7 +157,7 @@ export const updateSubscription = authenticatedProcedure }, }) - await sendTelemetryEvents([ + await trackEvents([ { name: 'Subscription updated', workspaceId, diff --git a/apps/builder/src/features/telemetry/api/processTelemetryEvent.ts b/apps/builder/src/features/telemetry/api/processTelemetryEvent.ts deleted file mode 100644 index bbdb27c6b..000000000 --- a/apps/builder/src/features/telemetry/api/processTelemetryEvent.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { eventSchema } from '@typebot.io/schemas/features/telemetry' -import { z } from 'zod' -import { PostHog } from 'posthog-node' -import { TRPCError } from '@trpc/server' -import got from 'got' -import { authenticatedProcedure } from '@/helpers/server/trpc' -import { env } from '@typebot.io/env' - -export const processTelemetryEvent = authenticatedProcedure - .meta({ - openapi: { - method: 'POST', - path: '/v1/t/process', - description: - "Only used for the cloud version of Typebot. It's the way it processes telemetry events and inject it to thrid-party services.", - tags: ['Telemetry'], - }, - }) - .input( - z.object({ - events: z.array(eventSchema), - }) - ) - .output( - z.object({ - message: z.literal('Events injected'), - }) - ) - .mutation(async ({ input: { events }, ctx: { user } }) => { - if (user.email !== env.ADMIN_EMAIL) - throw new TRPCError({ - code: 'BAD_REQUEST', - message: 'Only app admin can process telemetry events', - }) - if (!env.NEXT_PUBLIC_POSTHOG_KEY) - throw new TRPCError({ - code: 'BAD_REQUEST', - message: 'Server does not have POSTHOG_API_KEY configured', - }) - const client = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, { - host: env.NEXT_PUBLIC_POSTHOG_HOST, - }) - - events.forEach(async (event) => { - if (event.name === 'User created') { - client.identify({ - distinctId: event.userId, - properties: event.data, - }) - } - if ( - event.name === 'Workspace created' || - event.name === 'Subscription updated' - ) - client.groupIdentify({ - groupType: 'workspace', - groupKey: event.workspaceId, - properties: event.data, - }) - if ( - event.name === 'Typebot created' || - event.name === 'Typebot published' - ) - client.groupIdentify({ - groupType: 'typebot', - groupKey: event.typebotId, - properties: { name: event.data.name }, - }) - if (event.name === 'User created' && env.USER_CREATED_WEBHOOK_URL) { - await got.post(env.USER_CREATED_WEBHOOK_URL, { - json: { - email: event.data.email, - name: event.data.name ? event.data.name.split(' ')[0] : undefined, - }, - }) - } - const groups: { workspace?: string; typebot?: string } = {} - if ('workspaceId' in event) groups['workspace'] = event.workspaceId - if ('typebotId' in event) groups['typebot'] = event.typebotId - client.capture({ - distinctId: event.userId, - event: event.name, - properties: 'data' in event ? event.data : undefined, - groups, - }) - }) - - await client.shutdownAsync() - - return { message: 'Events injected' } - }) diff --git a/apps/builder/src/features/telemetry/posthog.tsx b/apps/builder/src/features/telemetry/posthog.tsx deleted file mode 100644 index 138c252ca..000000000 --- a/apps/builder/src/features/telemetry/posthog.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { env } from '@typebot.io/env' -import posthog from 'posthog-js' - -export const initPostHogIfEnabled = () => { - if (typeof window === 'undefined') return - - const posthogKey = env.NEXT_PUBLIC_POSTHOG_KEY - - if (!posthogKey) return - - posthog.init(posthogKey, { - api_host: env.NEXT_PUBLIC_POSTHOG_HOST, - loaded: (posthog) => { - if (process.env.NODE_ENV === 'development') posthog.debug() - }, - capture_pageview: false, - capture_pageleave: false, - autocapture: false, - }) -} - -export const identifyUser = (userId: string) => { - if (!posthog.__loaded) return - posthog.identify(userId) -} - -export { posthog } diff --git a/apps/builder/src/features/typebot/api/createTypebot.ts b/apps/builder/src/features/typebot/api/createTypebot.ts index 26730321a..41ff49f15 100644 --- a/apps/builder/src/features/typebot/api/createTypebot.ts +++ b/apps/builder/src/features/typebot/api/createTypebot.ts @@ -11,9 +11,9 @@ import { sanitizeGroups, sanitizeSettings, } from '../helpers/sanitizers' -import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' import { createId } from '@paralleldrive/cuid2' import { EventType } from '@typebot.io/schemas/features/events/constants' +import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents' const typebotCreateSchemaPick = { name: true, @@ -116,7 +116,7 @@ export const createTypebot = authenticatedProcedure const parsedNewTypebot = typebotV6Schema.parse(newTypebot) - await sendTelemetryEvents([ + await trackEvents([ { name: 'Typebot created', workspaceId: parsedNewTypebot.workspaceId, diff --git a/apps/builder/src/features/typebot/api/getTypebot.ts b/apps/builder/src/features/typebot/api/getTypebot.ts index 3716ced1d..bf4cf4ad8 100644 --- a/apps/builder/src/features/typebot/api/getTypebot.ts +++ b/apps/builder/src/features/typebot/api/getTypebot.ts @@ -104,6 +104,6 @@ const getCurrentUserMode = ( return 'write' if (collaborator) return 'read' - if (user?.email === env.ADMIN_EMAIL) return 'read' + if (user?.email && env.ADMIN_EMAIL?.includes(user.email)) return 'read' return 'guest' } diff --git a/apps/builder/src/features/typebot/api/importTypebot.ts b/apps/builder/src/features/typebot/api/importTypebot.ts index e00a91519..8db7cf2cb 100644 --- a/apps/builder/src/features/typebot/api/importTypebot.ts +++ b/apps/builder/src/features/typebot/api/importTypebot.ts @@ -12,9 +12,9 @@ import { import { z } from 'zod' import { getUserRoleInWorkspace } from '@/features/workspace/helpers/getUserRoleInWorkspace' import { sanitizeGroups, sanitizeSettings } from '../helpers/sanitizers' -import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' import { preprocessTypebot } from '@typebot.io/schemas/features/typebot/helpers/preprocessTypebot' import { migrateTypebot } from '@typebot.io/lib/migrations/migrateTypebot' +import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents' const omittedProps = { id: true, @@ -151,7 +151,7 @@ export const importTypebot = authenticatedProcedure const parsedNewTypebot = typebotV6Schema.parse(newTypebot) - await sendTelemetryEvents([ + await trackEvents([ { name: 'Typebot created', workspaceId: parsedNewTypebot.workspaceId, diff --git a/apps/builder/src/features/typebot/api/publishTypebot.ts b/apps/builder/src/features/typebot/api/publishTypebot.ts index afe6ab3b9..8b3f665e7 100644 --- a/apps/builder/src/features/typebot/api/publishTypebot.ts +++ b/apps/builder/src/features/typebot/api/publishTypebot.ts @@ -11,11 +11,11 @@ import { } from '@typebot.io/schemas' import { z } from 'zod' import { isWriteTypebotForbidden } from '../helpers/isWriteTypebotForbidden' -import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' import { Plan } from '@typebot.io/prisma' import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants' import { computeRiskLevel } from '@typebot.io/radar' import { env } from '@typebot.io/env' +import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents' export const publishTypebot = authenticatedProcedure .meta({ @@ -174,7 +174,7 @@ export const publishTypebot = authenticatedProcedure }, }) - await sendTelemetryEvents([ + await trackEvents([ { name: 'Typebot published', workspaceId: existingTypebot.workspaceId, diff --git a/apps/builder/src/features/typebot/helpers/isReadTypebotForbidden.ts b/apps/builder/src/features/typebot/helpers/isReadTypebotForbidden.ts index f8647510a..924a86dc7 100644 --- a/apps/builder/src/features/typebot/helpers/isReadTypebotForbidden.ts +++ b/apps/builder/src/features/typebot/helpers/isReadTypebotForbidden.ts @@ -28,7 +28,7 @@ export const isReadTypebotForbidden = async ( !user || typebot.workspace.isSuspended || typebot.workspace.isPastDue || - (env.ADMIN_EMAIL !== user.email && + (env.ADMIN_EMAIL?.every((email) => email !== user.email) && !typebot.collaborators.some( (collaborator) => collaborator.userId === user.id ) && diff --git a/apps/builder/src/features/workspace/api/createWorkspace.ts b/apps/builder/src/features/workspace/api/createWorkspace.ts index 2639733e1..765643a08 100644 --- a/apps/builder/src/features/workspace/api/createWorkspace.ts +++ b/apps/builder/src/features/workspace/api/createWorkspace.ts @@ -1,10 +1,10 @@ -import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' import prisma from '@typebot.io/lib/prisma' import { authenticatedProcedure } from '@/helpers/server/trpc' import { TRPCError } from '@trpc/server' import { Workspace, workspaceSchema } from '@typebot.io/schemas' import { z } from 'zod' import { parseWorkspaceDefaultPlan } from '../helpers/parseWorkspaceDefaultPlan' +import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents' export const createWorkspace = authenticatedProcedure .meta({ @@ -62,7 +62,7 @@ export const createWorkspace = authenticatedProcedure }, })) as Workspace - await sendTelemetryEvents([ + await trackEvents([ { name: 'Workspace created', workspaceId: newWorkspace.id, diff --git a/apps/builder/src/features/workspace/helpers/isReadWorkspaceFobidden.ts b/apps/builder/src/features/workspace/helpers/isReadWorkspaceFobidden.ts index 3ab54b546..be24384f9 100644 --- a/apps/builder/src/features/workspace/helpers/isReadWorkspaceFobidden.ts +++ b/apps/builder/src/features/workspace/helpers/isReadWorkspaceFobidden.ts @@ -8,7 +8,7 @@ export const isReadWorkspaceFobidden = ( user: Pick ) => { if ( - env.ADMIN_EMAIL === user.email || + env.ADMIN_EMAIL?.some((email) => email === user.email) || workspace.members.find((member) => member.userId === user.id) ) return false diff --git a/apps/builder/src/features/workspace/helpers/parseWorkspaceDefaultPlan.ts b/apps/builder/src/features/workspace/helpers/parseWorkspaceDefaultPlan.ts index 53ab8d9d2..944dd1661 100644 --- a/apps/builder/src/features/workspace/helpers/parseWorkspaceDefaultPlan.ts +++ b/apps/builder/src/features/workspace/helpers/parseWorkspaceDefaultPlan.ts @@ -2,7 +2,8 @@ import { env } from '@typebot.io/env' import { Plan } from '@typebot.io/prisma' export const parseWorkspaceDefaultPlan = (userEmail: string) => { - if (env.ADMIN_EMAIL === userEmail) return Plan.UNLIMITED + if (env.ADMIN_EMAIL?.some((email) => email === userEmail)) + return Plan.UNLIMITED const defaultPlan = env.DEFAULT_WORKSPACE_PLAN as Plan if (defaultPlan && Object.values(Plan).includes(defaultPlan)) return defaultPlan diff --git a/apps/builder/src/helpers/databaseRules.ts b/apps/builder/src/helpers/databaseRules.ts index 6a4aba67d..b781e8745 100644 --- a/apps/builder/src/helpers/databaseRules.ts +++ b/apps/builder/src/helpers/databaseRules.ts @@ -40,7 +40,8 @@ export const canReadTypebots = ( ) => ({ id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds }, workspace: - user.email === env.ADMIN_EMAIL || env.NEXT_PUBLIC_E2E_TEST + env.ADMIN_EMAIL?.some((email) => email === user.email) || + env.NEXT_PUBLIC_E2E_TEST ? undefined : { members: { diff --git a/apps/builder/src/helpers/server/routers/publicRouter.ts b/apps/builder/src/helpers/server/routers/publicRouter.ts index 484bc20fd..fd7b4065a 100644 --- a/apps/builder/src/helpers/server/routers/publicRouter.ts +++ b/apps/builder/src/helpers/server/routers/publicRouter.ts @@ -10,7 +10,6 @@ import { router } from '../trpc' import { analyticsRouter } from '@/features/analytics/api/router' import { collaboratorsRouter } from '@/features/collaboration/api/router' import { customDomainsRouter } from '@/features/customDomains/api/router' -import { processTelemetryEvent } from '@/features/telemetry/api/processTelemetryEvent' import { publicWhatsAppRouter } from '@/features/whatsapp/router' export const publicRouter = router({ @@ -25,7 +24,6 @@ export const publicRouter = router({ theme: themeRouter, collaborators: collaboratorsRouter, customDomains: customDomainsRouter, - processTelemetryEvent, whatsApp: publicWhatsAppRouter, }) diff --git a/apps/builder/src/pages/_app.tsx b/apps/builder/src/pages/_app.tsx index 9ad8542b7..4925e0797 100644 --- a/apps/builder/src/pages/_app.tsx +++ b/apps/builder/src/pages/_app.tsx @@ -19,13 +19,10 @@ import { NewVersionPopup } from '@/components/NewVersionPopup' import { TypebotProvider } from '@/features/editor/providers/TypebotProvider' import { WorkspaceProvider } from '@/features/workspace/WorkspaceProvider' import { isCloudProdInstance } from '@/helpers/isCloudProdInstance' -import { initPostHogIfEnabled } from '@/features/telemetry/posthog' import { TolgeeProvider, useTolgeeSSR } from '@tolgee/react' import { tolgee } from '@/lib/tolgee' import { Toaster } from '@/components/Toaster' -initPostHogIfEnabled() - const { ToastContainer, toast } = createStandaloneToast(customTheme) const App = ({ Component, pageProps }: AppProps) => { diff --git a/apps/builder/src/pages/api/stripe/webhook.ts b/apps/builder/src/pages/api/stripe/webhook.ts index 501aab3e8..0796772e6 100644 --- a/apps/builder/src/pages/api/stripe/webhook.ts +++ b/apps/builder/src/pages/api/stripe/webhook.ts @@ -6,10 +6,10 @@ import { buffer } from 'micro' import prisma from '@typebot.io/lib/prisma' import { Plan, WorkspaceRole } from '@typebot.io/prisma' import { RequestHandler } from 'next/dist/server/next' -import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' import { Settings } from '@typebot.io/schemas' import { env } from '@typebot.io/env' import { prices } from '@typebot.io/lib/billing/constants' +import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents' if (!env.STRIPE_SECRET_KEY || !env.STRIPE_WEBHOOK_SECRET) throw new Error('STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET missing') @@ -75,7 +75,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => { }, }) - await sendTelemetryEvents( + await trackEvents( workspace.members.map((m) => ({ name: 'Subscription updated', workspaceId, @@ -108,7 +108,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => { }, }) - await sendTelemetryEvents([ + await trackEvents([ { name: 'Subscription updated', workspaceId, @@ -152,7 +152,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => { isPastDue: true, }, }) - await sendTelemetryEvents( + await trackEvents( existingWorkspace.members.map((m) => ({ name: 'Workspace past due', workspaceId: existingWorkspace.id, @@ -202,7 +202,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => { }, }, }) - await sendTelemetryEvents( + await trackEvents( updatedWorkspace.members.map((m) => ({ name: 'Workspace past due status removed', workspaceId: updatedWorkspace.id, @@ -255,7 +255,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => { }, }) - await sendTelemetryEvents( + await trackEvents( workspace.members.map((m) => ({ name: 'Subscription updated', workspaceId: workspace.id, diff --git a/apps/docs/self-hosting/configuration.mdx b/apps/docs/self-hosting/configuration.mdx index f028f6762..cc607cd4f 100644 --- a/apps/docs/self-hosting/configuration.mdx +++ b/apps/docs/self-hosting/configuration.mdx @@ -21,7 +21,7 @@ Parameters marked with \* are required. | ENCRYPTION_SECRET \* | | A 256-bit key used to encrypt sensitive data. It is strongly recommended to [generate](https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx) a new one. The secret should be the same between builder and viewer. | | NEXTAUTH_URL \* | | The builder base URL. Should be the publicly accessible URL (i.e. `https://typebot.domain.com`) | | NEXT_PUBLIC_VIEWER_URL \* | | The viewer base URL. Should be the publicly accessible URL (i.e. `https://bot.domain.com`) | -| ADMIN_EMAIL | | The email that will get an `UNLIMITED` plan on user creation. The associated user will be able to bypass database rules. | +| ADMIN_EMAIL | | The email that will get an `UNLIMITED` plan on user creation. The associated user will be able to bypass database rules. You can provide multiple emails separated by a comma without spaces. | | NEXTAUTH_URL_INTERNAL | | The internal builder base URL. You have to set it only when `NEXTAUTH_URL` can't be reached by your builder container / server. For a docker deployment, you should set it to `http://localhost:3000`. | | DEFAULT_WORKSPACE_PLAN | FREE | Default workspace plan on user creation or when a user creates a new workspace. Possible values are `FREE`, `STARTER`, `PRO`, `LIFETIME`, `UNLIMITED`. The default plan for admin user is `UNLIMITED` | | DISABLE_SIGNUP | false | Disable new user sign ups. Invited users are still able to sign up. | diff --git a/packages/embeds/js/package.json b/packages/embeds/js/package.json index 5defcc30f..6ab41dbdc 100644 --- a/packages/embeds/js/package.json +++ b/packages/embeds/js/package.json @@ -16,7 +16,7 @@ "@stripe/stripe-js": "1.54.1", "@udecode/plate-common": "21.1.5", "dompurify": "3.0.6", - "ky": "^1.1.3", + "ky": "1.1.3", "marked": "9.0.3", "solid-element": "1.7.1", "solid-js": "1.7.8" diff --git a/packages/env/env.ts b/packages/env/env.ts index 04be48070..fc759e648 100644 --- a/packages/env/env.ts +++ b/packages/env/env.ts @@ -57,7 +57,11 @@ const baseEnv = { z.string().url() ), DISABLE_SIGNUP: boolean.optional().default('false'), - ADMIN_EMAIL: z.string().email().optional(), + ADMIN_EMAIL: z + .string() + .min(1) + .optional() + .transform((val) => val?.split(',')), DEFAULT_WORKSPACE_PLAN: z .enum(['FREE', 'STARTER', 'PRO', 'LIFETIME', 'UNLIMITED']) .refine((str) => diff --git a/packages/lib/package.json b/packages/lib/package.json index 3a4d1db47..5ad6d5c4c 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -40,6 +40,7 @@ "google-auth-library": "8.9.0", "got": "12.6.0", "minio": "7.1.3", + "posthog-node": "3.1.1", "remark-parse": "11.0.0", "stripe": "12.13.0", "unified": "11.0.4", diff --git a/packages/lib/telemetry/sendTelemetryEvent.ts b/packages/lib/telemetry/sendTelemetryEvent.ts deleted file mode 100644 index 24ef2b52a..000000000 --- a/packages/lib/telemetry/sendTelemetryEvent.ts +++ /dev/null @@ -1,29 +0,0 @@ -import got from 'got' -import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry' -import { env } from '@typebot.io/env' - -export const sendTelemetryEvents = async (events: TelemetryEvent[]) => { - if (events.length === 0) return { message: 'No events to send' } - if (!env.TELEMETRY_WEBHOOK_URL) return { message: 'Telemetry not enabled' } - - try { - await got.post(env.TELEMETRY_WEBHOOK_URL, { - json: { events }, - headers: { - authorization: env.TELEMETRY_WEBHOOK_BEARER_TOKEN - ? `Bearer ${env.TELEMETRY_WEBHOOK_BEARER_TOKEN}` - : undefined, - }, - }) - } catch (err) { - console.error('Failed to send event', err) - return { - message: 'Failed to send event', - error: err instanceof Error ? err.message : 'Unknown error', - } - } - - return { - message: 'Event sent', - } -} diff --git a/packages/lib/telemetry/trackEvents.ts b/packages/lib/telemetry/trackEvents.ts new file mode 100644 index 000000000..2bd8c5223 --- /dev/null +++ b/packages/lib/telemetry/trackEvents.ts @@ -0,0 +1,54 @@ +import { env } from '@typebot.io/env' +import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry' +import { PostHog } from 'posthog-node' +import ky from 'ky' + +export const trackEvents = async (events: TelemetryEvent[]) => { + if (!env.NEXT_PUBLIC_POSTHOG_KEY) return + const client = new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, { + host: env.NEXT_PUBLIC_POSTHOG_HOST, + }) + + events.forEach(async (event) => { + if (event.name === 'User created') { + client.identify({ + distinctId: event.userId, + properties: event.data, + }) + if (env.USER_CREATED_WEBHOOK_URL) { + await ky.post(env.USER_CREATED_WEBHOOK_URL, { + json: { + email: event.data.email, + name: event.data.name ? event.data.name.split(' ')[0] : undefined, + }, + }) + } + } + if ( + event.name === 'Workspace created' || + event.name === 'Subscription updated' + ) + client.groupIdentify({ + groupType: 'workspace', + groupKey: event.workspaceId, + properties: event.data, + }) + if (event.name === 'Typebot created' || event.name === 'Typebot published') + client.groupIdentify({ + groupType: 'typebot', + groupKey: event.typebotId, + properties: { name: event.data.name }, + }) + const groups: { workspace?: string; typebot?: string } = {} + if ('workspaceId' in event) groups['workspace'] = event.workspaceId + if ('typebotId' in event) groups['typebot'] = event.typebotId + client.capture({ + distinctId: event.userId, + event: event.name, + properties: 'data' in event ? event.data : undefined, + groups, + }) + }) + + await client.shutdownAsync() +} diff --git a/packages/scripts/checkAndReportChatsUsage.ts b/packages/scripts/checkAndReportChatsUsage.ts index f6fdc4a0a..ad9de9395 100644 --- a/packages/scripts/checkAndReportChatsUsage.ts +++ b/packages/scripts/checkAndReportChatsUsage.ts @@ -5,7 +5,7 @@ import { promptAndSetEnvironment } from './utils' import { Workspace } from '@typebot.io/schemas' import { sendAlmostReachedChatsLimitEmail } from '@typebot.io/emails/src/emails/AlmostReachedChatsLimitEmail' import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry' -import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' +import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents' import Stripe from 'stripe' import { createId } from '@paralleldrive/cuid2' @@ -222,7 +222,7 @@ export const checkAndReportChatsUsage = async () => { `Send ${newResultsCollectedEvents.length} new results events and ${quarantineEvents.length} auto quarantine events...` ) - await sendTelemetryEvents(quarantineEvents.concat(newResultsCollectedEvents)) + await trackEvents(quarantineEvents.concat(newResultsCollectedEvents)) } const getSubscription = async ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6258e4639..17b11b21b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -197,6 +197,9 @@ importers: jsonwebtoken: specifier: 9.0.1 version: 9.0.1 + ky: + specifier: 1.1.3 + version: 1.1.3 libphonenumber-js: specifier: 1.10.37 version: 1.10.37 @@ -227,12 +230,6 @@ importers: papaparse: specifier: 5.4.1 version: 5.4.1 - posthog-js: - specifier: 1.77.1 - version: 1.77.1 - posthog-node: - specifier: 3.1.1 - version: 3.1.1 prettier: specifier: 2.8.8 version: 2.8.8 @@ -902,8 +899,8 @@ importers: specifier: 3.0.6 version: 3.0.6 ky: - specifier: ^1.1.3 - version: 1.2.0 + specifier: 1.1.3 + version: 1.1.3 marked: specifier: 9.0.3 version: 9.0.3 @@ -1445,6 +1442,9 @@ importers: minio: specifier: 7.1.3 version: 7.1.3 + posthog-node: + specifier: 3.1.1 + version: 3.1.1 remark-parse: specifier: 11.0.0 version: 11.0.0 @@ -13717,10 +13717,6 @@ packages: pend: 1.2.0 dev: true - /fflate@0.4.8: - resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} - dev: false - /figures@2.0.0: resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} engines: {node: '>=4'} @@ -15911,8 +15907,8 @@ packages: resolution: {integrity: sha512-RTSoaUAfLvpR357vWzAz/50Q/BmHfmE6ETSWfutT0AJiw10e6CmcdYRQJlLRd95B53D0Y2aD1jSxD3V3ySF+PA==} dev: true - /ky@1.2.0: - resolution: {integrity: sha512-dnPW+T78MuJ9tLAiF/apJV7bP7RRRCARXQxsCmsWiKLXqGtMBOgDVOFRYzCAfNe/OrRyFyor5ESgvvC+QWEqOA==} + /ky@1.1.3: + resolution: {integrity: sha512-t7q8sJfazzHbfYxiCtuLIH4P+pWoCgunDll17O/GBZBqMt2vHjGSx5HzSxhOc2BDEg3YN/EmeA7VKrHnwuWDag==} engines: {node: '>=18'} dev: false @@ -18733,12 +18729,6 @@ packages: source-map-js: 1.0.2 dev: false - /posthog-js@1.77.1: - resolution: {integrity: sha512-QBwyeCvXf2JvnLBDSrp0DIwn8DnUEj6giyFtFD3xNQR7s89DUATWi9qnfu4DxUCOzLvN8eXmD94vAnXWVjw7cA==} - dependencies: - fflate: 0.4.8 - dev: false - /posthog-node@3.1.1: resolution: {integrity: sha512-OUSYcnLHbzvY/dxNsbUGoYuTZz5XNx48BkfiCkOIJZMFvot5VPQ0KWEjX+kzYxEwHeXbjW9plqsOVcYCYfidgg==} engines: {node: '>=15.0.0'}