Files
stats/apps/worker/src/jobs/events.incoming-event.ts
Carl-Gerhard Lindesvärd 71bf22af51 feature(queue): use postgres instead of redis for buffer
* wip(buffer): initial implementation of psql buffer

* wip(buffer): add both profile and bots buffer
2025-01-30 22:50:57 +00:00

135 lines
4.4 KiB
TypeScript

import { getReferrerWithQuery, parseReferrer } from '@/utils/parse-referrer';
import type { Job } from 'bullmq';
import { omit } from 'ramda';
import { logger } from '@/utils/logger';
import { createSessionEnd, getSessionEnd } from '@/utils/session-handler';
import { isSameDomain, parsePath } from '@openpanel/common';
import { parseUserAgent } from '@openpanel/common/server';
import type { IServiceCreateEventPayload, IServiceEvent } from '@openpanel/db';
import { checkNotificationRulesForEvent, createEvent } from '@openpanel/db';
import { getLastScreenViewFromProfileId } from '@openpanel/db';
import type { EventsQueuePayloadIncomingEvent } from '@openpanel/queue';
import * as R from 'ramda';
const GLOBAL_PROPERTIES = ['__path', '__referrer'];
// This function will merge two objects.
// First it will strip '' and undefined/null from B
// Then it will merge the two objects with a standard ramda merge function
const merge = <A, B>(a: Partial<A>, b: Partial<B>): A & B =>
R.mergeDeepRight(a, R.reject(R.anyPass([R.isEmpty, R.isNil]))(b)) as A & B;
async function createEventAndNotify(payload: IServiceCreateEventPayload) {
await checkNotificationRulesForEvent(payload).catch((e) => {
logger.error('Error checking notification rules', { error: e });
});
return createEvent(payload);
}
export async function incomingEvent(job: Job<EventsQueuePayloadIncomingEvent>) {
const {
geo,
event: body,
headers,
projectId,
currentDeviceId,
previousDeviceId,
priority,
} = job.data.payload;
const properties = body.properties ?? {};
const getProperty = (name: string): string | undefined => {
// replace thing is just for older sdks when we didn't have `__`
// remove when kiddokitchen app (24.09.02) is not used anymore
return (
((properties[name] || properties[name.replace('__', '')]) as
| string
| null
| undefined) ?? undefined
);
};
// this will get the profileId from the alias table if it exists
const profileId = body.profileId ? String(body.profileId) : '';
const createdAt = new Date(body.timestamp);
const isTimestampFromThePast = body.isTimestampFromThePast;
const url = getProperty('__path');
const { path, hash, query, origin } = parsePath(url);
const referrer = isSameDomain(getProperty('__referrer'), url)
? null
: parseReferrer(getProperty('__referrer'));
const utmReferrer = getReferrerWithQuery(query);
const userAgent = headers['user-agent'];
const sdkName = headers['openpanel-sdk-name'];
const sdkVersion = headers['openpanel-sdk-version'];
const uaInfo = parseUserAgent(userAgent, properties);
const baseEvent = {
name: body.name,
profileId,
projectId,
properties: omit(GLOBAL_PROPERTIES, {
...properties,
user_agent: userAgent,
__hash: hash,
__query: query,
}),
createdAt,
duration: 0,
sdkName,
sdkVersion,
city: geo.city,
country: geo.country,
region: geo.region,
longitude: geo.longitude,
latitude: geo.latitude,
path,
origin,
referrer: utmReferrer?.url || referrer?.url || '',
referrerName: utmReferrer?.name || referrer?.name || '',
referrerType: utmReferrer?.type || referrer?.type || '',
os: uaInfo.os,
osVersion: uaInfo.osVersion,
browser: uaInfo.browser,
browserVersion: uaInfo.browserVersion,
device: uaInfo.device,
brand: uaInfo.brand,
model: uaInfo.model,
} as const;
// if timestamp is from the past we dont want to create a new session
if (uaInfo.isServer || isTimestampFromThePast) {
const event = profileId
? await getLastScreenViewFromProfileId({
profileId,
projectId,
})
: null;
const payload = merge(omit(['properties'], event ?? {}), baseEvent);
return createEventAndNotify(payload as IServiceEvent);
}
const sessionEnd = await getSessionEnd({
priority,
projectId,
currentDeviceId,
previousDeviceId,
profileId,
});
const payload: IServiceCreateEventPayload = merge(baseEvent, {
deviceId: sessionEnd.payload.deviceId,
sessionId: sessionEnd.payload.sessionId,
referrer: sessionEnd.payload?.referrer,
referrerName: sessionEnd.payload?.referrerName,
referrerType: sessionEnd.payload?.referrerType,
}) as IServiceCreateEventPayload;
if (sessionEnd.notFound) {
await createSessionEnd({ payload });
}
return createEventAndNotify(payload);
}