2
0

Add Meta Pixel block

Closes #582
This commit is contained in:
Baptiste Arnaud
2023-06-28 09:52:03 +02:00
parent 92f7f3cbe2
commit 033f8f99dd
39 changed files with 826 additions and 38 deletions

View File

@ -1,6 +1,6 @@
import { LiteBadge } from './LiteBadge'
import { createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { injectCustomHeadCode, isNotEmpty } from '@typebot.io/lib'
import { isNotEmpty } from '@typebot.io/lib'
import { getInitialChatReplyQuery } from '@/queries/getInitialChatReplyQuery'
import { ConversationContainer } from './ConversationContainer'
import { setIsMobile } from '@/utils/isMobileSignal'
@ -89,8 +89,6 @@ export const Bot = (props: BotProps & { class?: string }) => {
groupId: data.input.groupId,
})
if (data.logs) props.onNewLogs?.(data.logs)
const customHeadCode = data.typebot.settings.metadata.customHeadCode
if (customHeadCode) injectCustomHeadCode(customHeadCode)
}
createEffect(() => {

View File

@ -5,7 +5,5 @@ export const executeGoogleAnalyticsBlock = async (
options: GoogleAnalyticsOptions
) => {
if (!options?.trackingId) return
const { default: initGoogleAnalytics } = await import('@/lib/gtag')
await initGoogleAnalytics(options.trackingId)
sendGaEvent(options)
}

View File

@ -0,0 +1,8 @@
import { trackPixelEvent } from '@/lib/pixel'
import { isEmpty } from '@typebot.io/lib/utils'
import type { PixelBlock } from '@typebot.io/schemas'
export const executePixel = async (options: PixelBlock['options']) => {
if (isEmpty(options?.pixelId)) return
trackPixelEvent(options)
}

View File

@ -1,3 +1,4 @@
import { isEmpty } from '@typebot.io/lib/utils'
import type { GoogleAnalyticsOptions } from '@typebot.io/schemas'
declare const gtag: (
@ -11,7 +12,7 @@ declare const gtag: (
}
) => void
const initGoogleAnalytics = (id: string): Promise<void> =>
export const initGoogleAnalytics = (id: string): Promise<void> =>
new Promise((resolve) => {
const existingScript = document.getElementById('gtag')
if (!existingScript) {
@ -37,11 +38,9 @@ const initGoogleAnalytics = (id: string): Promise<void> =>
export const sendGaEvent = (options: GoogleAnalyticsOptions) => {
if (!options) return
gtag('event', options.action, {
event_category: options.label?.length ? options.category : undefined,
event_label: options.label?.length ? options.label : undefined,
event_category: isEmpty(options.category) ? undefined : options.category,
event_label: isEmpty(options.label) ? undefined : options.label,
value: options.value as number,
send_to: options.sendTo?.length ? options.sendTo : undefined,
send_to: isEmpty(options.sendTo) ? undefined : options.sendTo,
})
}
export default initGoogleAnalytics

View File

@ -0,0 +1,23 @@
export const gtmHeadSnippet = (
googleTagManagerId: string
) => `<!-- Google Tag Manager -->
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${googleTagManagerId}');
<!-- End Google Tag Manager -->`
export const gtmBodyElement = (googleTagManagerId: string) => {
if (document.getElementById('gtm-noscript')) return ''
const noScriptElement = document.createElement('noscript')
noScriptElement.id = 'gtm-noscript'
const iframeElement = document.createElement('iframe')
iframeElement.src = `https://www.googletagmanager.com/ns.html?id=${googleTagManagerId}`
iframeElement.height = '0'
iframeElement.width = '0'
iframeElement.style.display = 'none'
iframeElement.style.visibility = 'hidden'
noScriptElement.appendChild(iframeElement)
return noScriptElement
}

View File

@ -0,0 +1,41 @@
import { PixelBlock } from '@typebot.io/schemas'
declare const fbq: (
arg0: string,
arg1: string,
arg2: Record<string, string> | undefined
) => void
export const initPixel = (pixelId: string) => {
const script = document.createElement('script')
script.innerHTML = `!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '${pixelId}');
fbq('track', 'PageView');`
document.head.appendChild(script)
const noscript = document.createElement('noscript')
noscript.innerHTML = `<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=${pixelId}&ev=PageView&noscript=1"/>`
document.head.appendChild(noscript)
}
export const trackPixelEvent = (options: PixelBlock['options']) => {
if (!options.eventType) return
const params = options.params?.length
? options.params.reduce<Record<string, string>>((obj, param) => {
if (!param.key || !param.value) return obj
return { ...obj, [param.key]: param.value }
}, {})
: undefined
if (options.eventType === 'Custom') {
if (!options.name) return
fbq('trackCustom', options.name, params)
}
fbq('track', options.eventType, params)
}

View File

@ -6,8 +6,10 @@ import { executeScript } from '@/features/blocks/logic/script/executeScript'
import { executeSetVariable } from '@/features/blocks/logic/setVariable/executeSetVariable'
import { executeWait } from '@/features/blocks/logic/wait/utils/executeWait'
import { executeWebhook } from '@/features/blocks/integrations/webhook/executeWebhook'
import { executePixel } from '@/features/blocks/integrations/pixel/executePixel'
import { ClientSideActionContext } from '@/types'
import type { ChatReply, ReplyLog } from '@typebot.io/schemas'
import { injectStartProps } from './injectStartProps'
export const executeClientSideAction = async (
clientSideAction: NonNullable<ChatReply['clientSideActions']>[0],
@ -58,4 +60,10 @@ export const executeClientSideAction = async (
const response = await executeWebhook(clientSideAction.webhookToExecute)
return { replyToSend: response }
}
if ('startPropsToInject' in clientSideAction) {
return injectStartProps(clientSideAction.startPropsToInject)
}
if ('pixel' in clientSideAction) {
return executePixel(clientSideAction.pixel)
}
}

View File

@ -0,0 +1,20 @@
/* eslint-disable solid/reactivity */
import { initGoogleAnalytics } from '@/lib/gtag'
import { gtmBodyElement } from '@/lib/gtm'
import { initPixel } from '@/lib/pixel'
import { injectCustomHeadCode, isNotEmpty } from '@typebot.io/lib/utils'
import { StartPropsToInject } from '@typebot.io/schemas'
export const injectStartProps = async (
startPropsToInject: StartPropsToInject
) => {
const customHeadCode = startPropsToInject.customHeadCode
if (isNotEmpty(customHeadCode)) injectCustomHeadCode(customHeadCode)
const gtmId = startPropsToInject.gtmId
if (isNotEmpty(gtmId)) document.body.prepend(gtmBodyElement(gtmId))
const googleAnalyticsId = startPropsToInject.googleAnalyticsId
if (isNotEmpty(googleAnalyticsId))
await initGoogleAnalytics(googleAnalyticsId)
const pixelId = startPropsToInject.pixelId
if (isNotEmpty(pixelId)) initPixel(pixelId)
}