-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs(api): 📝 Simplified API endpoints
- Loading branch information
1 parent
281fddc
commit 29254f6
Showing
8 changed files
with
376 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
204 changes: 204 additions & 0 deletions
204
apps/viewer/pages/api/typebots/[typebotId]/blocks/[blockId]/executeWebhook.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
import prisma from 'libs/prisma' | ||
import { | ||
defaultWebhookAttributes, | ||
HttpMethod, | ||
KeyValue, | ||
PublicTypebot, | ||
ResultValues, | ||
Typebot, | ||
Variable, | ||
Webhook, | ||
WebhookOptions, | ||
WebhookResponse, | ||
WebhookStep, | ||
} from 'models' | ||
import { parseVariables } from 'bot-engine' | ||
import { NextApiRequest, NextApiResponse } from 'next' | ||
import got, { Method, Headers, HTTPError } from 'got' | ||
import { | ||
byId, | ||
initMiddleware, | ||
methodNotAllowed, | ||
notFound, | ||
parseAnswers, | ||
} from 'utils' | ||
import { stringify } from 'qs' | ||
import { withSentry } from '@sentry/nextjs' | ||
import Cors from 'cors' | ||
import { parseSampleResult } from 'services/api/webhooks' | ||
|
||
const cors = initMiddleware(Cors()) | ||
const handler = async (req: NextApiRequest, res: NextApiResponse) => { | ||
await cors(req, res) | ||
if (req.method === 'POST') { | ||
const typebotId = req.query.typebotId.toString() | ||
const stepId = req.query.blockId.toString() | ||
const { resultValues, variables } = ( | ||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body | ||
) as { | ||
resultValues: ResultValues | undefined | ||
variables: Variable[] | ||
} | ||
const typebot = (await prisma.typebot.findUnique({ | ||
where: { id: typebotId }, | ||
include: { webhooks: true }, | ||
})) as unknown as (Typebot & { webhooks: Webhook[] }) | null | ||
if (!typebot) return notFound(res) | ||
const step = typebot.blocks | ||
.flatMap((b) => b.steps) | ||
.find(byId(stepId)) as WebhookStep | ||
const webhook = typebot.webhooks.find(byId(step.webhookId)) | ||
if (!webhook) | ||
return res | ||
.status(404) | ||
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } }) | ||
const preparedWebhook = prepareWebhookAttributes(webhook, step.options) | ||
const result = await executeWebhook(typebot)( | ||
preparedWebhook, | ||
variables, | ||
step.blockId, | ||
resultValues | ||
) | ||
return res.status(200).send(result) | ||
} | ||
return methodNotAllowed(res) | ||
} | ||
|
||
const prepareWebhookAttributes = ( | ||
webhook: Webhook, | ||
options: WebhookOptions | ||
): Webhook => { | ||
if (options.isAdvancedConfig === false) { | ||
return { ...webhook, body: '{{state}}', ...defaultWebhookAttributes } | ||
} else if (options.isCustomBody === false) { | ||
return { ...webhook, body: '{{state}}' } | ||
} | ||
return webhook | ||
} | ||
|
||
const executeWebhook = | ||
(typebot: Typebot) => | ||
async ( | ||
webhook: Webhook, | ||
variables: Variable[], | ||
blockId: string, | ||
resultValues?: ResultValues | ||
): Promise<WebhookResponse> => { | ||
if (!webhook.url || !webhook.method) | ||
return { | ||
statusCode: 400, | ||
data: { message: `Webhook doesn't have url or method` }, | ||
} | ||
const basicAuth: { username?: string; password?: string } = {} | ||
const basicAuthHeaderIdx = webhook.headers.findIndex( | ||
(h) => | ||
h.key?.toLowerCase() === 'authorization' && | ||
h.value?.toLowerCase()?.includes('basic') | ||
) | ||
const isUsernamePasswordBasicAuth = | ||
basicAuthHeaderIdx !== -1 && | ||
webhook.headers[basicAuthHeaderIdx].value?.includes(':') | ||
if (isUsernamePasswordBasicAuth) { | ||
const [username, password] = | ||
webhook.headers[basicAuthHeaderIdx].value?.slice(6).split(':') ?? [] | ||
basicAuth.username = username | ||
basicAuth.password = password | ||
webhook.headers.splice(basicAuthHeaderIdx, 1) | ||
} | ||
const headers = convertKeyValueTableToObject(webhook.headers, variables) as | ||
| Headers | ||
| undefined | ||
const queryParams = stringify( | ||
convertKeyValueTableToObject(webhook.queryParams, variables) | ||
) | ||
const contentType = headers ? headers['Content-Type'] : undefined | ||
const body = | ||
webhook.method !== HttpMethod.GET | ||
? getBodyContent(typebot)({ | ||
body: webhook.body, | ||
resultValues, | ||
blockId, | ||
}) | ||
: undefined | ||
try { | ||
const response = await got( | ||
parseVariables(variables)( | ||
webhook.url + (queryParams !== '' ? `?${queryParams}` : '') | ||
), | ||
{ | ||
method: webhook.method as Method, | ||
headers, | ||
...basicAuth, | ||
json: | ||
contentType !== 'x-www-form-urlencoded' && body | ||
? JSON.parse(parseVariables(variables)(body)) | ||
: undefined, | ||
form: | ||
contentType === 'x-www-form-urlencoded' && body | ||
? JSON.parse(parseVariables(variables)(body)) | ||
: undefined, | ||
} | ||
) | ||
return { | ||
statusCode: response.statusCode, | ||
data: parseBody(response.body), | ||
} | ||
} catch (error) { | ||
if (error instanceof HTTPError) { | ||
return { | ||
statusCode: error.response.statusCode, | ||
data: parseBody(error.response.body as string), | ||
} | ||
} | ||
console.error(error) | ||
return { | ||
statusCode: 500, | ||
data: { message: `Error from Typebot server: ${error}` }, | ||
} | ||
} | ||
} | ||
|
||
const getBodyContent = | ||
(typebot: Pick<Typebot | PublicTypebot, 'blocks' | 'variables' | 'edges'>) => | ||
({ | ||
body, | ||
resultValues, | ||
blockId, | ||
}: { | ||
body?: string | null | ||
resultValues?: ResultValues | ||
blockId: string | ||
}): string | undefined => { | ||
if (!body) return | ||
return body === '{{state}}' | ||
? JSON.stringify( | ||
resultValues | ||
? parseAnswers(typebot)(resultValues) | ||
: parseSampleResult(typebot)(blockId) | ||
) | ||
: body | ||
} | ||
|
||
const parseBody = (body: string) => { | ||
try { | ||
return JSON.parse(body) | ||
} catch (err) { | ||
return body | ||
} | ||
} | ||
|
||
const convertKeyValueTableToObject = ( | ||
keyValues: KeyValue[] | undefined, | ||
variables: Variable[] | ||
) => { | ||
if (!keyValues) return | ||
return keyValues.reduce((object, item) => { | ||
if (!item.key) return {} | ||
return { | ||
...object, | ||
[item.key]: parseVariables(variables)(item.value ?? ''), | ||
} | ||
}, {}) | ||
} | ||
|
||
export default withSentry(handler) |
27 changes: 27 additions & 0 deletions
27
apps/viewer/pages/api/typebots/[typebotId]/blocks/[blockId]/sampleResult.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import prisma from 'libs/prisma' | ||
import { Typebot } from 'models' | ||
import { NextApiRequest, NextApiResponse } from 'next' | ||
import { authenticateUser } from 'services/api/utils' | ||
import { parseSampleResult } from 'services/api/webhooks' | ||
import { methodNotAllowed } from 'utils' | ||
|
||
const handler = async (req: NextApiRequest, res: NextApiResponse) => { | ||
if (req.method === 'GET') { | ||
const user = await authenticateUser(req) | ||
if (!user) return res.status(401).json({ message: 'Not authenticated' }) | ||
const typebotId = req.query.typebotId.toString() | ||
const stepId = req.query.blockId.toString() | ||
const typebot = (await prisma.typebot.findUnique({ | ||
where: { id_ownerId: { id: typebotId, ownerId: user.id } }, | ||
})) as unknown as Typebot | undefined | ||
if (!typebot) return res.status(400).send({ message: 'Typebot not found' }) | ||
const step = typebot.blocks | ||
.flatMap((b) => b.steps) | ||
.find((s) => s.id === stepId) | ||
if (!step) return res.status(404).send({ message: 'Block not found' }) | ||
return res.send(parseSampleResult(typebot)(step.blockId)) | ||
} | ||
methodNotAllowed(res) | ||
} | ||
|
||
export default handler |
42 changes: 42 additions & 0 deletions
42
apps/viewer/pages/api/typebots/[typebotId]/blocks/[blockId]/subscribeWebhook.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { withSentry } from '@sentry/nextjs' | ||
import prisma from 'libs/prisma' | ||
import { Typebot, WebhookStep } from 'models' | ||
import { NextApiRequest, NextApiResponse } from 'next' | ||
import { authenticateUser } from 'services/api/utils' | ||
import { byId, methodNotAllowed } from 'utils' | ||
|
||
const handler = async (req: NextApiRequest, res: NextApiResponse) => { | ||
if (req.method === 'POST') { | ||
const user = await authenticateUser(req) | ||
if (!user) return res.status(401).json({ message: 'Not authenticated' }) | ||
const body = req.body as Record<string, string> | ||
if (!('url' in body)) | ||
return res.status(403).send({ message: 'url is missing in body' }) | ||
const { url } = body | ||
const typebotId = req.query.typebotId.toString() | ||
const stepId = req.query.blockId.toString() | ||
const typebot = (await prisma.typebot.findUnique({ | ||
where: { id_ownerId: { id: typebotId, ownerId: user.id } }, | ||
})) as unknown as Typebot | undefined | ||
if (!typebot) return res.status(400).send({ message: 'Typebot not found' }) | ||
try { | ||
const { webhookId } = typebot.blocks | ||
.flatMap((b) => b.steps) | ||
.find(byId(stepId)) as WebhookStep | ||
await prisma.webhook.upsert({ | ||
where: { id: webhookId }, | ||
update: { url, body: '{{state}}', method: 'POST' }, | ||
create: { url, body: '{{state}}', method: 'POST', typebotId }, | ||
}) | ||
|
||
return res.send({ message: 'success' }) | ||
} catch (err) { | ||
return res | ||
.status(400) | ||
.send({ message: "blockId doesn't point to a Webhook step" }) | ||
} | ||
} | ||
return methodNotAllowed(res) | ||
} | ||
|
||
export default withSentry(handler) |
Oops, something went wrong.
29254f6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
viewer-v2 – ./apps/viewer
bot.joof.it
app.yvon.earth
chat.hayuri.id
bergamo.store
bot.jesopizz.it
bots.bridge.ai
yoda.riku.ai
bot.tvbeat.it
talk.gocare.io
gollum.riku.ai
bot.contakit.com
fitness.riku.ai
zap.fundviser.in
bot.rihabilita.it
viewer.typebot.io
bot.danyservice.it
bot.dsignagency.com
chatbot.matthesv.de
demo.wemakebots.xyz
88584434.therpm.club
92109660.therpm.club
bot.barrettamario.it
hello.advergreen.com
bot.outstandbrand.com
bot.coachayongzul.com
bot.eikju.photography
bot.digitalpointer.id
bot.robertohairlab.it
bot.ilmuseoaiborghi.it
criar.somaperuzzo.com
bot.pratikmandalia.com
michaeljackson.riku.ai
form.bridesquadapp.com
87656003.actualizar.xyz
88152257.actualizar.xyz
arrivalx2.wpwakanda.com
91375310.actualizar.xyz
bot.hotelplayarimini.it
invite.bridesquadapp.com
link.venturasuceder.com
bot.amicidisanfaustino.it
chat.thehomebuyersusa.com
forms.hiabhaykulkarni.com
typebot-viewer.vercel.app
casestudyemb.wpwakanda.com
chat.atlasoutfittersk9.com
bot.adventureconsulting.hu
herbalife.barrettamario.it
liveconvert.kandalearn.com
mainmenu1one.wpwakanda.com
homepageonly.wpwakanda.com
tarian.theiofoundation.org
bot.pinpointinteractive.com
bot.polychromes-project.com
bot.studiotecnicoimmobiliaremerelli.it
viewer-v2-git-main-typebot-io.vercel.app
liveconvert2.kandalearn.com
viewer-v2-typebot-io.vercel.app
bot.seidibergamoseanchetu.it
forms.escoladeautomacao.com.br
bot.seidinembroseanchetu.it
29254f6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
landing-page-v2 – ./apps/landing-page
typebot.io
get-typebot.com
landing-page-v2-git-main-typebot-io.vercel.app
landing-page-v2-typebot-io.vercel.app
www.typebot.io
www.get-typebot.com
29254f6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
builder-v2 – ./apps/builder
builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app
app.typebot.io
29254f6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
viewer-v2-alpha – ./apps/viewer
apr.nigerias.io
bot.upfunnel.art
aso.nigerias.io
stan.vselise.com
am.nigerias.io
faqs.nigerias.io
sakuranembro.it
an.nigerias.io
apo.nigerias.io
ar.nigerias.io
chat.hayurihijab.com
zap.techadviser.in
forms.webisharp.com
gcase.barrettamario.it
bot.incusservices.com
c23111azqw.nigerias.io
83242573.actualizar.xyz
aibot.angrybranding.co.uk
type.opaulovieira.com.br
boyfriend-breakup.riku.ai
piazzatorre.barrettamario.it
viewer-v2-alpha-typebot-io.vercel.app
onboarding.libertydreamcare.ie
viewer-v2-alpha-git-main-typebot-io.vercel.app
type.talitasouzamarques.com.br
personal-trainer.barrettamario.it
29254f6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
docs – ./apps/docs
docs-git-main-typebot-io.vercel.app
docs-typebot-io.vercel.app
docs.typebot.io