279 lines
6.9 KiB
TypeScript
279 lines
6.9 KiB
TypeScript
import fs from 'node:fs';
|
|
import * as faker from '@faker-js/faker';
|
|
import { hashPassword } from '@openpanel/common/server';
|
|
import { ClientType, db } from '@openpanel/db';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
const DOMAIN_COUNT = 5;
|
|
const PROFILE_COUNT = 50;
|
|
|
|
interface Event {
|
|
track: Track;
|
|
headers: Record<string, string>;
|
|
}
|
|
|
|
interface Track {
|
|
type: 'track';
|
|
payload: {
|
|
name: string;
|
|
properties: {
|
|
__referrer: string;
|
|
__path: string;
|
|
__title: string;
|
|
};
|
|
};
|
|
}
|
|
|
|
interface Profile {
|
|
id?: string;
|
|
name?: string;
|
|
userAgent: string;
|
|
ip: string;
|
|
}
|
|
|
|
const domains = Array.from({ length: DOMAIN_COUNT }, () => ({
|
|
domain: `https://${faker.allFakers.en.internet.domainName()}`,
|
|
clientId: uuidv4(),
|
|
profiles: Array.from({ length: PROFILE_COUNT }, () => ({
|
|
// id: uuidv4(),
|
|
// name: faker.allFakers.en.name.findName(),
|
|
userAgent: faker.allFakers.en.internet.userAgent(),
|
|
ip: faker.allFakers.en.internet.ipv4(),
|
|
})),
|
|
}));
|
|
|
|
const referrers = [
|
|
'',
|
|
'https://www.google.com',
|
|
'https://www.facebook.com',
|
|
'https://www.twitter.com',
|
|
'https://www.linkedin.com',
|
|
'https://www.bing.com',
|
|
'https://www.duckduckgo.com',
|
|
'https://www.baidu.com',
|
|
'https://www.yandex.com',
|
|
'https://www.pinterest.com',
|
|
'https://www.reddit.com',
|
|
'https://www.tumblr.com',
|
|
'https://www.flickr.com',
|
|
'https://www.vimeo.com',
|
|
'https://www.mixcloud.com',
|
|
'',
|
|
];
|
|
|
|
function generatePath(): string {
|
|
const basePath = `/${faker.allFakers.en.lorem.slug()}`;
|
|
const queryString =
|
|
Math.random() < 0.7
|
|
? `?${faker.allFakers.en.internet.domainWord()}=${faker.allFakers.en.lorem.word()}`
|
|
: '';
|
|
const hashFragment =
|
|
Math.random() < 0.3 ? `#${faker.allFakers.en.lorem.word()}` : '';
|
|
return `${basePath}${queryString}${hashFragment}`;
|
|
}
|
|
|
|
async function trackit(event: Event) {
|
|
console.log('trackit', JSON.stringify(event.track, null, 2));
|
|
await fetch('http://localhost:3333/track', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...event.headers,
|
|
},
|
|
body: JSON.stringify(event.track),
|
|
});
|
|
}
|
|
|
|
function generateScreenViews({
|
|
domain,
|
|
clientId,
|
|
profile,
|
|
eventsCount,
|
|
}: {
|
|
domain: string;
|
|
clientId: string;
|
|
profile: Profile;
|
|
eventsCount: number;
|
|
}): Event[] {
|
|
return Array.from({ length: eventsCount }, (_, index) => ({
|
|
headers: {
|
|
'openpanel-client-id': clientId,
|
|
'x-client-ip': profile.ip,
|
|
'user-agent': profile.userAgent,
|
|
origin: domain,
|
|
},
|
|
track: {
|
|
type: 'track',
|
|
payload: {
|
|
name: 'screen_view',
|
|
properties: {
|
|
__referrer:
|
|
referrers[Math.floor(Math.random() * referrers.length)] ?? '',
|
|
__path: `${domain}${generatePath()}`,
|
|
__title: faker.allFakers.en.lorem.sentence(),
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
}
|
|
|
|
function generateEvents(): Event[] {
|
|
const events: Event[] = [];
|
|
|
|
domains.forEach(({ domain, clientId, profiles }) => {
|
|
for (let i = 0; i < profiles.length; i++) {
|
|
events.push(
|
|
...generateScreenViews({
|
|
domain,
|
|
clientId,
|
|
profile: profiles[i % PROFILE_COUNT]!,
|
|
eventsCount: Math.floor(Math.random() * 10),
|
|
}),
|
|
);
|
|
}
|
|
});
|
|
|
|
return events;
|
|
}
|
|
|
|
function scrambleEvents(events: Event[]) {
|
|
return events.sort(() => Math.random() - 0.5);
|
|
}
|
|
|
|
let lastTriggeredIndex = 0;
|
|
|
|
async function triggerEvents(generatedEvents: any[]) {
|
|
const EVENTS_PER_SECOND = 100; // Adjust this value to set the desired events per second
|
|
const INTERVAL_MS = 1000 / EVENTS_PER_SECOND;
|
|
|
|
if (lastTriggeredIndex >= generatedEvents.length) {
|
|
console.log('All events triggered.');
|
|
return;
|
|
}
|
|
|
|
const event = generatedEvents[lastTriggeredIndex]!;
|
|
try {
|
|
await trackit(event);
|
|
console.log(`Event ${lastTriggeredIndex + 1} sent successfully`);
|
|
console.log(
|
|
`sending ${event.track.payload?.properties?.__path} from user ${event.headers['user-agent']}`,
|
|
);
|
|
} catch (error) {
|
|
console.error(`Failed to send event ${lastTriggeredIndex + 1}:`, error);
|
|
}
|
|
|
|
lastTriggeredIndex++;
|
|
const remainingEvents = generatedEvents.length - lastTriggeredIndex;
|
|
|
|
console.log(
|
|
`Triggered ${lastTriggeredIndex} events. Remaining: ${remainingEvents}`,
|
|
);
|
|
|
|
if (remainingEvents > 0) {
|
|
setTimeout(() => triggerEvents(generatedEvents), INTERVAL_MS);
|
|
} else {
|
|
console.log('All events triggered.');
|
|
}
|
|
|
|
console.log(`Total events to trigger: ${generatedEvents.length}`);
|
|
}
|
|
|
|
async function createMock(file: string) {
|
|
for (const project of domains) {
|
|
await db.project.create({
|
|
data: {
|
|
organizationId: 'openpanel-dev',
|
|
name: project.domain,
|
|
clients: {
|
|
create: {
|
|
organizationId: 'openpanel-dev',
|
|
name: project.domain,
|
|
secret: await hashPassword('secret'),
|
|
id: project.clientId,
|
|
type: ClientType.write,
|
|
cors: project.domain,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
fs.writeFileSync(
|
|
file,
|
|
JSON.stringify(scrambleEvents(generateEvents()), null, 2),
|
|
'utf-8',
|
|
);
|
|
}
|
|
|
|
function insertFakeEvents(events: Event[]) {
|
|
const blueprint = {
|
|
headers: {
|
|
'openpanel-client-id': '5b679c47-9ec0-470a-8944-a9ab8f42b14f',
|
|
'x-client-ip': '229.145.77.175',
|
|
'user-agent':
|
|
'Opera/13.66 (Macintosh; Intel Mac OS X 10.8.3 U; GV Presto/2.9.183 Version/11.00)',
|
|
origin: 'https://classic-hovel.info',
|
|
},
|
|
track: {
|
|
type: 'track',
|
|
payload: {
|
|
name: 'screen_view',
|
|
properties: {
|
|
__referrer: 'https://www.google.com',
|
|
__path: 'https://classic-hovel.info/beneficium-arcesso-quisquam',
|
|
__title: 'Hic thesis laboriosam copiose admoveo sufficio.',
|
|
},
|
|
},
|
|
},
|
|
};
|
|
const newEvents = [];
|
|
for (const event of events) {
|
|
newEvents.push(event);
|
|
if (Math.random() < 0.6) {
|
|
const fakeEvent = JSON.parse(JSON.stringify(blueprint));
|
|
fakeEvent.track.payload.name = faker.allFakers.en.lorem.word();
|
|
delete fakeEvent.track.payload.properties;
|
|
newEvents.push(fakeEvent);
|
|
}
|
|
}
|
|
|
|
return newEvents;
|
|
}
|
|
|
|
async function simultaneousRequests() {
|
|
const events = require('./mock-basic.json');
|
|
const screenView = events[0]!;
|
|
const event = JSON.parse(JSON.stringify(events[0]));
|
|
event.track.payload.name = 'click_button';
|
|
delete event.track.payload.properties.__referrer;
|
|
|
|
await trackit(event);
|
|
await trackit(event);
|
|
trackit(screenView);
|
|
trackit(screenView);
|
|
await trackit(event);
|
|
trackit(screenView);
|
|
trackit(event);
|
|
}
|
|
|
|
async function main() {
|
|
const [type, file = 'mock-basic.json'] = process.argv.slice(2);
|
|
|
|
switch (type) {
|
|
case 'send':
|
|
await triggerEvents(insertFakeEvents(require(`./${file}`)));
|
|
break;
|
|
case 'sim':
|
|
await simultaneousRequests();
|
|
break;
|
|
case 'mock':
|
|
await createMock(file);
|
|
break;
|
|
default:
|
|
console.log('usage: jiti mock.ts send|mock|sim [file]');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main();
|