fix(api): sending duplicated responses for bot events (improved code as well)
This commit is contained in:
26
apps/api/src/hooks/client.hook.ts
Normal file
26
apps/api/src/hooks/client.hook.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { SdkAuthError, validateSdkRequest } from '@/utils/auth';
|
||||||
|
import type { TrackHandlerPayload } from '@openpanel/sdk';
|
||||||
|
import type {
|
||||||
|
FastifyReply,
|
||||||
|
FastifyRequest,
|
||||||
|
HookHandlerDoneFunction,
|
||||||
|
} from 'fastify';
|
||||||
|
|
||||||
|
export async function clientHook(
|
||||||
|
req: FastifyRequest<{
|
||||||
|
Body: TrackHandlerPayload;
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const client = await validateSdkRequest(req.headers);
|
||||||
|
req.projectId = client.projectId;
|
||||||
|
req.client = client;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SdkAuthError) {
|
||||||
|
return reply.status(401).send(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.status(500).send('Internal server error');
|
||||||
|
}
|
||||||
|
}
|
||||||
51
apps/api/src/hooks/is-bot.hook.ts
Normal file
51
apps/api/src/hooks/is-bot.hook.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { isBot } from '@/bots';
|
||||||
|
import { createBotEvent } from '@openpanel/db';
|
||||||
|
import type { TrackHandlerPayload } from '@openpanel/sdk';
|
||||||
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
|
type DeprecatedEventPayload = {
|
||||||
|
name: string;
|
||||||
|
properties: Record<string, unknown>;
|
||||||
|
timestamp: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function isBotHook(
|
||||||
|
req: FastifyRequest<{
|
||||||
|
Body: TrackHandlerPayload | DeprecatedEventPayload;
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) {
|
||||||
|
const bot = req.headers['user-agent']
|
||||||
|
? isBot(req.headers['user-agent'])
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (bot && req.client?.projectId) {
|
||||||
|
if ('type' in req.body && req.body.type === 'track') {
|
||||||
|
const path = (req.body.payload.properties?.__path ||
|
||||||
|
req.body.payload.properties?.path) as string | undefined;
|
||||||
|
if (path) {
|
||||||
|
await createBotEvent({
|
||||||
|
...bot,
|
||||||
|
projectId: req.client.projectId,
|
||||||
|
path: path ?? '',
|
||||||
|
createdAt: new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Handle deprecated events (v1)
|
||||||
|
} else if ('name' in req.body && 'properties' in req.body) {
|
||||||
|
const path = (req.body.properties?.__path || req.body.properties?.path) as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
if (path) {
|
||||||
|
await createBotEvent({
|
||||||
|
...bot,
|
||||||
|
projectId: req.client.projectId,
|
||||||
|
path: path ?? '',
|
||||||
|
createdAt: new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.status(202).send('OK');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,58 +1,12 @@
|
|||||||
import { isBot } from '@/bots';
|
|
||||||
import * as controller from '@/controllers/event.controller';
|
import * as controller from '@/controllers/event.controller';
|
||||||
import { SdkAuthError, validateSdkRequest } from '@/utils/auth';
|
import type { FastifyPluginCallback } from 'fastify';
|
||||||
import { logger } from '@/utils/logger';
|
|
||||||
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
|
||||||
|
|
||||||
import { createBotEvent } from '@openpanel/db';
|
import { clientHook } from '@/hooks/client.hook';
|
||||||
import type { PostEventPayload } from '@openpanel/sdk';
|
import { isBotHook } from '@/hooks/is-bot.hook';
|
||||||
|
|
||||||
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||||
fastify.addHook(
|
fastify.addHook('preHandler', clientHook);
|
||||||
'preHandler',
|
fastify.addHook('preHandler', isBotHook);
|
||||||
async (
|
|
||||||
req: FastifyRequest<{
|
|
||||||
Body: PostEventPayload;
|
|
||||||
}>,
|
|
||||||
reply,
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const client = await validateSdkRequest(req.headers).catch((error) => {
|
|
||||||
if (error instanceof SdkAuthError) {
|
|
||||||
return reply.status(401).send(error.message);
|
|
||||||
}
|
|
||||||
logger.error('Failed to validate sdk request', { error });
|
|
||||||
return reply.status(401).send('Unknown validation error');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!client?.projectId) {
|
|
||||||
return reply.status(401).send('No project found for this client');
|
|
||||||
}
|
|
||||||
req.projectId = client.projectId;
|
|
||||||
req.client = client;
|
|
||||||
|
|
||||||
const bot = req.headers['user-agent']
|
|
||||||
? isBot(req.headers['user-agent'])
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (bot) {
|
|
||||||
const path = (req.body?.properties?.__path ||
|
|
||||||
req.body?.properties?.path) as string | undefined;
|
|
||||||
await createBotEvent({
|
|
||||||
...bot,
|
|
||||||
projectId: client.projectId,
|
|
||||||
path: path ?? '',
|
|
||||||
createdAt: new Date(req.body?.timestamp),
|
|
||||||
});
|
|
||||||
reply.status(202).send('OK');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to create bot event', { error });
|
|
||||||
reply.status(401).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
fastify.route({
|
fastify.route({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -1,39 +1,11 @@
|
|||||||
import { isBot } from '@/bots';
|
|
||||||
import * as controller from '@/controllers/profile.controller';
|
import * as controller from '@/controllers/profile.controller';
|
||||||
import { SdkAuthError, validateSdkRequest } from '@/utils/auth';
|
import { clientHook } from '@/hooks/client.hook';
|
||||||
import { logger } from '@/utils/logger';
|
import { isBotHook } from '@/hooks/is-bot.hook';
|
||||||
import type { FastifyPluginCallback } from 'fastify';
|
import type { FastifyPluginCallback } from 'fastify';
|
||||||
|
|
||||||
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||||
fastify.addHook('preHandler', async (req, reply) => {
|
fastify.addHook('preHandler', clientHook);
|
||||||
try {
|
fastify.addHook('preHandler', isBotHook);
|
||||||
const client = await validateSdkRequest(req.headers).catch((error) => {
|
|
||||||
if (error instanceof SdkAuthError) {
|
|
||||||
return reply.status(401).send(error.message);
|
|
||||||
}
|
|
||||||
logger.error('Failed to validate sdk request', { error });
|
|
||||||
return reply.status(401).send('Unknown validation error');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!client?.projectId) {
|
|
||||||
return reply.status(401).send('No project found for this client');
|
|
||||||
}
|
|
||||||
req.projectId = client.projectId;
|
|
||||||
req.client = client;
|
|
||||||
|
|
||||||
const bot = req.headers['user-agent']
|
|
||||||
? isBot(req.headers['user-agent'])
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (bot) {
|
|
||||||
return reply.status(202).send('OK');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to create bot event', { error });
|
|
||||||
reply.status(401).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fastify.route({
|
fastify.route({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -1,62 +1,16 @@
|
|||||||
import { isBot } from '@/bots';
|
import { isBot } from '@/bots';
|
||||||
import { handler } from '@/controllers/track.controller';
|
import { handler } from '@/controllers/track.controller';
|
||||||
import { SdkAuthError, validateSdkRequest } from '@/utils/auth';
|
import { SdkAuthError, validateSdkRequest } from '@/utils/auth';
|
||||||
import { logger } from '@/utils/logger';
|
|
||||||
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
|
import { clientHook } from '@/hooks/client.hook';
|
||||||
|
import { isBotHook } from '@/hooks/is-bot.hook';
|
||||||
import { createBotEvent } from '@openpanel/db';
|
import { createBotEvent } from '@openpanel/db';
|
||||||
import type { TrackHandlerPayload } from '@openpanel/sdk';
|
import type { TrackHandlerPayload } from '@openpanel/sdk';
|
||||||
|
|
||||||
const trackRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
const trackRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||||
fastify.addHook(
|
fastify.addHook('preHandler', clientHook);
|
||||||
'preHandler',
|
fastify.addHook('preHandler', isBotHook);
|
||||||
async (
|
|
||||||
req: FastifyRequest<{
|
|
||||||
Body: TrackHandlerPayload;
|
|
||||||
}>,
|
|
||||||
reply,
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const client = await validateSdkRequest(req.headers).catch((error) => {
|
|
||||||
if (error instanceof SdkAuthError) {
|
|
||||||
return reply.status(401).send(error.message);
|
|
||||||
}
|
|
||||||
logger.error('Failed to validate sdk request', { error });
|
|
||||||
return reply.status(401).send('Unknown validation error');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!client?.projectId) {
|
|
||||||
return reply.status(401).send('No project found for this client');
|
|
||||||
}
|
|
||||||
|
|
||||||
req.projectId = client.projectId;
|
|
||||||
req.client = client;
|
|
||||||
|
|
||||||
const bot = req.headers['user-agent']
|
|
||||||
? isBot(req.headers['user-agent'])
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (bot) {
|
|
||||||
if (req.body.type === 'track') {
|
|
||||||
const path = (req.body.payload.properties?.__path ||
|
|
||||||
req.body.payload.properties?.path) as string | undefined;
|
|
||||||
await createBotEvent({
|
|
||||||
...bot,
|
|
||||||
projectId: client.projectId,
|
|
||||||
path: path ?? '',
|
|
||||||
createdAt: new Date(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reply.status(202).send('OK');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to create bot event', { error });
|
|
||||||
reply.status(401).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
fastify.route({
|
fastify.route({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ export class SdkAuthError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientWithProjectId = Client & { projectId: string };
|
||||||
|
|
||||||
export async function validateSdkRequest(
|
export async function validateSdkRequest(
|
||||||
headers: RawRequestDefaultExpression['headers'],
|
headers: RawRequestDefaultExpression['headers'],
|
||||||
): Promise<Client> {
|
): Promise<ClientWithProjectId> {
|
||||||
const clientIdNew = headers['openpanel-client-id'] as string;
|
const clientIdNew = headers['openpanel-client-id'] as string;
|
||||||
const clientIdOld = headers['mixan-client-id'] as string;
|
const clientIdOld = headers['mixan-client-id'] as string;
|
||||||
const clientSecretNew = headers['openpanel-client-secret'] as string;
|
const clientSecretNew = headers['openpanel-client-secret'] as string;
|
||||||
@@ -57,13 +59,11 @@ export async function validateSdkRequest(
|
|||||||
throw createError('Ingestion: Missing client id');
|
throw createError('Ingestion: Missing client id');
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = await db.client
|
const client = await db.client.findUnique({
|
||||||
.findUnique({
|
where: {
|
||||||
where: {
|
id: clientId,
|
||||||
id: clientId,
|
},
|
||||||
},
|
});
|
||||||
})
|
|
||||||
.catch(() => null);
|
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw createError('Ingestion: Invalid client id');
|
throw createError('Ingestion: Invalid client id');
|
||||||
@@ -91,17 +91,17 @@ export async function validateSdkRequest(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (domainAllowed) {
|
if (domainAllowed) {
|
||||||
return client;
|
return client as ClientWithProjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.cors === '*' && origin) {
|
if (client.cors === '*' && origin) {
|
||||||
return client;
|
return client as ClientWithProjectId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.secret && clientSecret) {
|
if (client.secret && clientSecret) {
|
||||||
if (await verifyPassword(clientSecret, client.secret)) {
|
if (await verifyPassword(clientSecret, client.secret)) {
|
||||||
return client;
|
return client as ClientWithProjectId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user