♻️ Re-organize telemetry package
This commit is contained in:
@@ -78,8 +78,6 @@
|
|||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"openai": "4.24.1",
|
"openai": "4.24.1",
|
||||||
"papaparse": "5.4.1",
|
"papaparse": "5.4.1",
|
||||||
"posthog-js": "1.77.1",
|
|
||||||
"posthog-node": "3.1.1",
|
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"qs": "6.11.2",
|
"qs": "6.11.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
@@ -95,7 +93,8 @@
|
|||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"unsplash-js": "7.0.18",
|
"unsplash-js": "7.0.18",
|
||||||
"use-debounce": "9.0.4",
|
"use-debounce": "9.0.4",
|
||||||
"zustand": "4.5.0"
|
"zustand": "4.5.0",
|
||||||
|
"ky": "1.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@chakra-ui/styled-system": "2.9.1",
|
"@chakra-ui/styled-system": "2.9.1",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { useToast } from '@/hooks/useToast'
|
|||||||
import { updateUserQuery } from './queries/updateUserQuery'
|
import { updateUserQuery } from './queries/updateUserQuery'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import { env } from '@typebot.io/env'
|
import { env } from '@typebot.io/env'
|
||||||
import { identifyUser } from '../telemetry/posthog'
|
|
||||||
import { useColorMode } from '@chakra-ui/react'
|
import { useColorMode } from '@chakra-ui/react'
|
||||||
|
|
||||||
export const userContext = createContext<{
|
export const userContext = createContext<{
|
||||||
@@ -62,7 +61,6 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
|
|
||||||
if (parsedUser?.id) {
|
if (parsedUser?.id) {
|
||||||
setSentryUser({ id: parsedUser.id })
|
setSentryUser({ id: parsedUser.id })
|
||||||
identifyUser(parsedUser.id)
|
|
||||||
}
|
}
|
||||||
}, [session, user])
|
}, [session, user])
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import {
|
|||||||
import type { Adapter, AdapterUser } from 'next-auth/adapters'
|
import type { Adapter, AdapterUser } from 'next-auth/adapters'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { generateId } from '@typebot.io/lib'
|
import { generateId } from '@typebot.io/lib'
|
||||||
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
|
||||||
import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry'
|
import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry'
|
||||||
import { convertInvitationsToCollaborations } from '@/features/auth/helpers/convertInvitationsToCollaborations'
|
import { convertInvitationsToCollaborations } from '@/features/auth/helpers/convertInvitationsToCollaborations'
|
||||||
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
|
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
|
||||||
import { joinWorkspaces } from '@/features/auth/helpers/joinWorkspaces'
|
import { joinWorkspaces } from '@/features/auth/helpers/joinWorkspaces'
|
||||||
import { parseWorkspaceDefaultPlan } from '@/features/workspace/helpers/parseWorkspaceDefaultPlan'
|
import { parseWorkspaceDefaultPlan } from '@/features/workspace/helpers/parseWorkspaceDefaultPlan'
|
||||||
import { env } from '@typebot.io/env'
|
import { env } from '@typebot.io/env'
|
||||||
|
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
|
||||||
|
|
||||||
export function customAdapter(p: PrismaClient): Adapter {
|
export function customAdapter(p: PrismaClient): Adapter {
|
||||||
return {
|
return {
|
||||||
@@ -28,7 +28,7 @@ export function customAdapter(p: PrismaClient): Adapter {
|
|||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
env.DISABLE_SIGNUP &&
|
env.DISABLE_SIGNUP &&
|
||||||
env.ADMIN_EMAIL !== user.email &&
|
env.ADMIN_EMAIL?.every((email) => email !== user.email) &&
|
||||||
invitations.length === 0 &&
|
invitations.length === 0 &&
|
||||||
workspaceInvitations.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,
|
name: data.name ? (data.name as string).split(' ')[0] : undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await sendTelemetryEvents(events)
|
await trackEvents(events)
|
||||||
if (invitations.length > 0)
|
if (invitations.length > 0)
|
||||||
await convertInvitationsToCollaborations(p, user, invitations)
|
await convertInvitationsToCollaborations(p, user, invitations)
|
||||||
if (workspaceInvitations.length > 0)
|
if (workspaceInvitations.length > 0)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
|
||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
@@ -9,6 +8,7 @@ import { z } from 'zod'
|
|||||||
import { createCheckoutSessionUrl } from './createCheckoutSession'
|
import { createCheckoutSessionUrl } from './createCheckoutSession'
|
||||||
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
||||||
import { env } from '@typebot.io/env'
|
import { env } from '@typebot.io/env'
|
||||||
|
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
|
||||||
|
|
||||||
export const updateSubscription = authenticatedProcedure
|
export const updateSubscription = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@@ -157,7 +157,7 @@ export const updateSubscription = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendTelemetryEvents([
|
await trackEvents([
|
||||||
{
|
{
|
||||||
name: 'Subscription updated',
|
name: 'Subscription updated',
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@@ -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' }
|
|
||||||
})
|
|
||||||
@@ -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 }
|
|
||||||
@@ -11,9 +11,9 @@ import {
|
|||||||
sanitizeGroups,
|
sanitizeGroups,
|
||||||
sanitizeSettings,
|
sanitizeSettings,
|
||||||
} from '../helpers/sanitizers'
|
} from '../helpers/sanitizers'
|
||||||
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { EventType } from '@typebot.io/schemas/features/events/constants'
|
import { EventType } from '@typebot.io/schemas/features/events/constants'
|
||||||
|
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
|
||||||
|
|
||||||
const typebotCreateSchemaPick = {
|
const typebotCreateSchemaPick = {
|
||||||
name: true,
|
name: true,
|
||||||
@@ -116,7 +116,7 @@ export const createTypebot = authenticatedProcedure
|
|||||||
|
|
||||||
const parsedNewTypebot = typebotV6Schema.parse(newTypebot)
|
const parsedNewTypebot = typebotV6Schema.parse(newTypebot)
|
||||||
|
|
||||||
await sendTelemetryEvents([
|
await trackEvents([
|
||||||
{
|
{
|
||||||
name: 'Typebot created',
|
name: 'Typebot created',
|
||||||
workspaceId: parsedNewTypebot.workspaceId,
|
workspaceId: parsedNewTypebot.workspaceId,
|
||||||
|
|||||||
@@ -104,6 +104,6 @@ const getCurrentUserMode = (
|
|||||||
return 'write'
|
return 'write'
|
||||||
|
|
||||||
if (collaborator) return 'read'
|
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'
|
return 'guest'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import {
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { getUserRoleInWorkspace } from '@/features/workspace/helpers/getUserRoleInWorkspace'
|
import { getUserRoleInWorkspace } from '@/features/workspace/helpers/getUserRoleInWorkspace'
|
||||||
import { sanitizeGroups, sanitizeSettings } from '../helpers/sanitizers'
|
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 { preprocessTypebot } from '@typebot.io/schemas/features/typebot/helpers/preprocessTypebot'
|
||||||
import { migrateTypebot } from '@typebot.io/lib/migrations/migrateTypebot'
|
import { migrateTypebot } from '@typebot.io/lib/migrations/migrateTypebot'
|
||||||
|
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
|
||||||
|
|
||||||
const omittedProps = {
|
const omittedProps = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -151,7 +151,7 @@ export const importTypebot = authenticatedProcedure
|
|||||||
|
|
||||||
const parsedNewTypebot = typebotV6Schema.parse(newTypebot)
|
const parsedNewTypebot = typebotV6Schema.parse(newTypebot)
|
||||||
|
|
||||||
await sendTelemetryEvents([
|
await trackEvents([
|
||||||
{
|
{
|
||||||
name: 'Typebot created',
|
name: 'Typebot created',
|
||||||
workspaceId: parsedNewTypebot.workspaceId,
|
workspaceId: parsedNewTypebot.workspaceId,
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import {
|
|||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { isWriteTypebotForbidden } from '../helpers/isWriteTypebotForbidden'
|
import { isWriteTypebotForbidden } from '../helpers/isWriteTypebotForbidden'
|
||||||
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
|
||||||
import { Plan } from '@typebot.io/prisma'
|
import { Plan } from '@typebot.io/prisma'
|
||||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||||
import { computeRiskLevel } from '@typebot.io/radar'
|
import { computeRiskLevel } from '@typebot.io/radar'
|
||||||
import { env } from '@typebot.io/env'
|
import { env } from '@typebot.io/env'
|
||||||
|
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
|
||||||
|
|
||||||
export const publishTypebot = authenticatedProcedure
|
export const publishTypebot = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@@ -174,7 +174,7 @@ export const publishTypebot = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendTelemetryEvents([
|
await trackEvents([
|
||||||
{
|
{
|
||||||
name: 'Typebot published',
|
name: 'Typebot published',
|
||||||
workspaceId: existingTypebot.workspaceId,
|
workspaceId: existingTypebot.workspaceId,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const isReadTypebotForbidden = async (
|
|||||||
!user ||
|
!user ||
|
||||||
typebot.workspace.isSuspended ||
|
typebot.workspace.isSuspended ||
|
||||||
typebot.workspace.isPastDue ||
|
typebot.workspace.isPastDue ||
|
||||||
(env.ADMIN_EMAIL !== user.email &&
|
(env.ADMIN_EMAIL?.every((email) => email !== user.email) &&
|
||||||
!typebot.collaborators.some(
|
!typebot.collaborators.some(
|
||||||
(collaborator) => collaborator.userId === user.id
|
(collaborator) => collaborator.userId === user.id
|
||||||
) &&
|
) &&
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
|
||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { Workspace, workspaceSchema } from '@typebot.io/schemas'
|
import { Workspace, workspaceSchema } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { parseWorkspaceDefaultPlan } from '../helpers/parseWorkspaceDefaultPlan'
|
import { parseWorkspaceDefaultPlan } from '../helpers/parseWorkspaceDefaultPlan'
|
||||||
|
import { trackEvents } from '@typebot.io/lib/telemetry/trackEvents'
|
||||||
|
|
||||||
export const createWorkspace = authenticatedProcedure
|
export const createWorkspace = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@@ -62,7 +62,7 @@ export const createWorkspace = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
})) as Workspace
|
})) as Workspace
|
||||||
|
|
||||||
await sendTelemetryEvents([
|
await trackEvents([
|
||||||
{
|
{
|
||||||
name: 'Workspace created',
|
name: 'Workspace created',
|
||||||
workspaceId: newWorkspace.id,
|
workspaceId: newWorkspace.id,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const isReadWorkspaceFobidden = (
|
|||||||
user: Pick<User, 'email' | 'id'>
|
user: Pick<User, 'email' | 'id'>
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
env.ADMIN_EMAIL === user.email ||
|
env.ADMIN_EMAIL?.some((email) => email === user.email) ||
|
||||||
workspace.members.find((member) => member.userId === user.id)
|
workspace.members.find((member) => member.userId === user.id)
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { env } from '@typebot.io/env'
|
|||||||
import { Plan } from '@typebot.io/prisma'
|
import { Plan } from '@typebot.io/prisma'
|
||||||
|
|
||||||
export const parseWorkspaceDefaultPlan = (userEmail: string) => {
|
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
|
const defaultPlan = env.DEFAULT_WORKSPACE_PLAN as Plan
|
||||||
if (defaultPlan && Object.values(Plan).includes(defaultPlan))
|
if (defaultPlan && Object.values(Plan).includes(defaultPlan))
|
||||||
return defaultPlan
|
return defaultPlan
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export const canReadTypebots = (
|
|||||||
) => ({
|
) => ({
|
||||||
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
||||||
workspace:
|
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
|
? undefined
|
||||||
: {
|
: {
|
||||||
members: {
|
members: {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { router } from '../trpc'
|
|||||||
import { analyticsRouter } from '@/features/analytics/api/router'
|
import { analyticsRouter } from '@/features/analytics/api/router'
|
||||||
import { collaboratorsRouter } from '@/features/collaboration/api/router'
|
import { collaboratorsRouter } from '@/features/collaboration/api/router'
|
||||||
import { customDomainsRouter } from '@/features/customDomains/api/router'
|
import { customDomainsRouter } from '@/features/customDomains/api/router'
|
||||||
import { processTelemetryEvent } from '@/features/telemetry/api/processTelemetryEvent'
|
|
||||||
import { publicWhatsAppRouter } from '@/features/whatsapp/router'
|
import { publicWhatsAppRouter } from '@/features/whatsapp/router'
|
||||||
|
|
||||||
export const publicRouter = router({
|
export const publicRouter = router({
|
||||||
@@ -25,7 +24,6 @@ export const publicRouter = router({
|
|||||||
theme: themeRouter,
|
theme: themeRouter,
|
||||||
collaborators: collaboratorsRouter,
|
collaborators: collaboratorsRouter,
|
||||||
customDomains: customDomainsRouter,
|
customDomains: customDomainsRouter,
|
||||||
processTelemetryEvent,
|
|
||||||
whatsApp: publicWhatsAppRouter,
|
whatsApp: publicWhatsAppRouter,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,10 @@ import { NewVersionPopup } from '@/components/NewVersionPopup'
|
|||||||
import { TypebotProvider } from '@/features/editor/providers/TypebotProvider'
|
import { TypebotProvider } from '@/features/editor/providers/TypebotProvider'
|
||||||
import { WorkspaceProvider } from '@/features/workspace/WorkspaceProvider'
|
import { WorkspaceProvider } from '@/features/workspace/WorkspaceProvider'
|
||||||
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
||||||
import { initPostHogIfEnabled } from '@/features/telemetry/posthog'
|
|
||||||
import { TolgeeProvider, useTolgeeSSR } from '@tolgee/react'
|
import { TolgeeProvider, useTolgeeSSR } from '@tolgee/react'
|
||||||
import { tolgee } from '@/lib/tolgee'
|
import { tolgee } from '@/lib/tolgee'
|
||||||
import { Toaster } from '@/components/Toaster'
|
import { Toaster } from '@/components/Toaster'
|
||||||
|
|
||||||
initPostHogIfEnabled()
|
|
||||||
|
|
||||||
const { ToastContainer, toast } = createStandaloneToast(customTheme)
|
const { ToastContainer, toast } = createStandaloneToast(customTheme)
|
||||||
|
|
||||||
const App = ({ Component, pageProps }: AppProps) => {
|
const App = ({ Component, pageProps }: AppProps) => {
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import { buffer } from 'micro'
|
|||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import { Plan, WorkspaceRole } from '@typebot.io/prisma'
|
import { Plan, WorkspaceRole } from '@typebot.io/prisma'
|
||||||
import { RequestHandler } from 'next/dist/server/next'
|
import { RequestHandler } from 'next/dist/server/next'
|
||||||
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
|
||||||
import { Settings } from '@typebot.io/schemas'
|
import { Settings } from '@typebot.io/schemas'
|
||||||
import { env } from '@typebot.io/env'
|
import { env } from '@typebot.io/env'
|
||||||
import { prices } from '@typebot.io/lib/billing/constants'
|
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)
|
if (!env.STRIPE_SECRET_KEY || !env.STRIPE_WEBHOOK_SECRET)
|
||||||
throw new Error('STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET missing')
|
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) => ({
|
workspace.members.map((m) => ({
|
||||||
name: 'Subscription updated',
|
name: 'Subscription updated',
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -108,7 +108,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendTelemetryEvents([
|
await trackEvents([
|
||||||
{
|
{
|
||||||
name: 'Subscription updated',
|
name: 'Subscription updated',
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -152,7 +152,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
isPastDue: true,
|
isPastDue: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await sendTelemetryEvents(
|
await trackEvents(
|
||||||
existingWorkspace.members.map((m) => ({
|
existingWorkspace.members.map((m) => ({
|
||||||
name: 'Workspace past due',
|
name: 'Workspace past due',
|
||||||
workspaceId: existingWorkspace.id,
|
workspaceId: existingWorkspace.id,
|
||||||
@@ -202,7 +202,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await sendTelemetryEvents(
|
await trackEvents(
|
||||||
updatedWorkspace.members.map((m) => ({
|
updatedWorkspace.members.map((m) => ({
|
||||||
name: 'Workspace past due status removed',
|
name: 'Workspace past due status removed',
|
||||||
workspaceId: updatedWorkspace.id,
|
workspaceId: updatedWorkspace.id,
|
||||||
@@ -255,7 +255,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendTelemetryEvents(
|
await trackEvents(
|
||||||
workspace.members.map((m) => ({
|
workspace.members.map((m) => ({
|
||||||
name: 'Subscription updated',
|
name: 'Subscription updated',
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Parameters marked with <span style={{color: '#ef5151'}}>\*</span> are required.
|
|||||||
| ENCRYPTION_SECRET <span style={{color: '#ef5151'}}>\*</span> | | 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. |
|
| ENCRYPTION_SECRET <span style={{color: '#ef5151'}}>\*</span> | | 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 <span style={{color: '#ef5151'}}>\*</span> | | The builder base URL. Should be the publicly accessible URL (i.e. `https://typebot.domain.com`) |
|
| NEXTAUTH_URL <span style={{color: '#ef5151'}}>\*</span> | | The builder base URL. Should be the publicly accessible URL (i.e. `https://typebot.domain.com`) |
|
||||||
| NEXT_PUBLIC_VIEWER_URL <span style={{color: '#ef5151'}}>\*</span> | | The viewer base URL. Should be the publicly accessible URL (i.e. `https://bot.domain.com`) |
|
| NEXT_PUBLIC_VIEWER_URL <span style={{color: '#ef5151'}}>\*</span> | | 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`. |
|
| 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` |
|
| 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. |
|
| DISABLE_SIGNUP | false | Disable new user sign ups. Invited users are still able to sign up. |
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"@stripe/stripe-js": "1.54.1",
|
"@stripe/stripe-js": "1.54.1",
|
||||||
"@udecode/plate-common": "21.1.5",
|
"@udecode/plate-common": "21.1.5",
|
||||||
"dompurify": "3.0.6",
|
"dompurify": "3.0.6",
|
||||||
"ky": "^1.1.3",
|
"ky": "1.1.3",
|
||||||
"marked": "9.0.3",
|
"marked": "9.0.3",
|
||||||
"solid-element": "1.7.1",
|
"solid-element": "1.7.1",
|
||||||
"solid-js": "1.7.8"
|
"solid-js": "1.7.8"
|
||||||
|
|||||||
6
packages/env/env.ts
vendored
6
packages/env/env.ts
vendored
@@ -57,7 +57,11 @@ const baseEnv = {
|
|||||||
z.string().url()
|
z.string().url()
|
||||||
),
|
),
|
||||||
DISABLE_SIGNUP: boolean.optional().default('false'),
|
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
|
DEFAULT_WORKSPACE_PLAN: z
|
||||||
.enum(['FREE', 'STARTER', 'PRO', 'LIFETIME', 'UNLIMITED'])
|
.enum(['FREE', 'STARTER', 'PRO', 'LIFETIME', 'UNLIMITED'])
|
||||||
.refine((str) =>
|
.refine((str) =>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"google-auth-library": "8.9.0",
|
"google-auth-library": "8.9.0",
|
||||||
"got": "12.6.0",
|
"got": "12.6.0",
|
||||||
"minio": "7.1.3",
|
"minio": "7.1.3",
|
||||||
|
"posthog-node": "3.1.1",
|
||||||
"remark-parse": "11.0.0",
|
"remark-parse": "11.0.0",
|
||||||
"stripe": "12.13.0",
|
"stripe": "12.13.0",
|
||||||
"unified": "11.0.4",
|
"unified": "11.0.4",
|
||||||
|
|||||||
@@ -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',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
packages/lib/telemetry/trackEvents.ts
Normal file
54
packages/lib/telemetry/trackEvents.ts
Normal file
@@ -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()
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { promptAndSetEnvironment } from './utils'
|
|||||||
import { Workspace } from '@typebot.io/schemas'
|
import { Workspace } from '@typebot.io/schemas'
|
||||||
import { sendAlmostReachedChatsLimitEmail } from '@typebot.io/emails/src/emails/AlmostReachedChatsLimitEmail'
|
import { sendAlmostReachedChatsLimitEmail } from '@typebot.io/emails/src/emails/AlmostReachedChatsLimitEmail'
|
||||||
import { TelemetryEvent } from '@typebot.io/schemas/features/telemetry'
|
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 Stripe from 'stripe'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
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...`
|
`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 (
|
const getSubscription = async (
|
||||||
|
|||||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -197,6 +197,9 @@ importers:
|
|||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: 9.0.1
|
specifier: 9.0.1
|
||||||
version: 9.0.1
|
version: 9.0.1
|
||||||
|
ky:
|
||||||
|
specifier: 1.1.3
|
||||||
|
version: 1.1.3
|
||||||
libphonenumber-js:
|
libphonenumber-js:
|
||||||
specifier: 1.10.37
|
specifier: 1.10.37
|
||||||
version: 1.10.37
|
version: 1.10.37
|
||||||
@@ -227,12 +230,6 @@ importers:
|
|||||||
papaparse:
|
papaparse:
|
||||||
specifier: 5.4.1
|
specifier: 5.4.1
|
||||||
version: 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:
|
prettier:
|
||||||
specifier: 2.8.8
|
specifier: 2.8.8
|
||||||
version: 2.8.8
|
version: 2.8.8
|
||||||
@@ -902,8 +899,8 @@ importers:
|
|||||||
specifier: 3.0.6
|
specifier: 3.0.6
|
||||||
version: 3.0.6
|
version: 3.0.6
|
||||||
ky:
|
ky:
|
||||||
specifier: ^1.1.3
|
specifier: 1.1.3
|
||||||
version: 1.2.0
|
version: 1.1.3
|
||||||
marked:
|
marked:
|
||||||
specifier: 9.0.3
|
specifier: 9.0.3
|
||||||
version: 9.0.3
|
version: 9.0.3
|
||||||
@@ -1445,6 +1442,9 @@ importers:
|
|||||||
minio:
|
minio:
|
||||||
specifier: 7.1.3
|
specifier: 7.1.3
|
||||||
version: 7.1.3
|
version: 7.1.3
|
||||||
|
posthog-node:
|
||||||
|
specifier: 3.1.1
|
||||||
|
version: 3.1.1
|
||||||
remark-parse:
|
remark-parse:
|
||||||
specifier: 11.0.0
|
specifier: 11.0.0
|
||||||
version: 11.0.0
|
version: 11.0.0
|
||||||
@@ -13717,10 +13717,6 @@ packages:
|
|||||||
pend: 1.2.0
|
pend: 1.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/fflate@0.4.8:
|
|
||||||
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/figures@2.0.0:
|
/figures@2.0.0:
|
||||||
resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==}
|
resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -15911,8 +15907,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-RTSoaUAfLvpR357vWzAz/50Q/BmHfmE6ETSWfutT0AJiw10e6CmcdYRQJlLRd95B53D0Y2aD1jSxD3V3ySF+PA==}
|
resolution: {integrity: sha512-RTSoaUAfLvpR357vWzAz/50Q/BmHfmE6ETSWfutT0AJiw10e6CmcdYRQJlLRd95B53D0Y2aD1jSxD3V3ySF+PA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/ky@1.2.0:
|
/ky@1.1.3:
|
||||||
resolution: {integrity: sha512-dnPW+T78MuJ9tLAiF/apJV7bP7RRRCARXQxsCmsWiKLXqGtMBOgDVOFRYzCAfNe/OrRyFyor5ESgvvC+QWEqOA==}
|
resolution: {integrity: sha512-t7q8sJfazzHbfYxiCtuLIH4P+pWoCgunDll17O/GBZBqMt2vHjGSx5HzSxhOc2BDEg3YN/EmeA7VKrHnwuWDag==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -18733,12 +18729,6 @@ packages:
|
|||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/posthog-js@1.77.1:
|
|
||||||
resolution: {integrity: sha512-QBwyeCvXf2JvnLBDSrp0DIwn8DnUEj6giyFtFD3xNQR7s89DUATWi9qnfu4DxUCOzLvN8eXmD94vAnXWVjw7cA==}
|
|
||||||
dependencies:
|
|
||||||
fflate: 0.4.8
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/posthog-node@3.1.1:
|
/posthog-node@3.1.1:
|
||||||
resolution: {integrity: sha512-OUSYcnLHbzvY/dxNsbUGoYuTZz5XNx48BkfiCkOIJZMFvot5VPQ0KWEjX+kzYxEwHeXbjW9plqsOVcYCYfidgg==}
|
resolution: {integrity: sha512-OUSYcnLHbzvY/dxNsbUGoYuTZz5XNx48BkfiCkOIJZMFvot5VPQ0KWEjX+kzYxEwHeXbjW9plqsOVcYCYfidgg==}
|
||||||
engines: {node: '>=15.0.0'}
|
engines: {node: '>=15.0.0'}
|
||||||
|
|||||||
Reference in New Issue
Block a user