diff --git a/src/libs/external-telemetry/event-tracking.ts b/src/libs/external-telemetry/event-tracking.ts index bb8ac811f..9ef30ecd8 100644 --- a/src/libs/external-telemetry/event-tracking.ts +++ b/src/libs/external-telemetry/event-tracking.ts @@ -1,35 +1,105 @@ +import ky from 'ky' +import localforage from 'localforage' import posthog from 'posthog-js' +type EventPayload = { + /** + * The name of the event + */ + eventName: string + /** + * The moment the event was captured (in milliseconds since epoch) + */ + timestamp: number + /** + * Arbitrary properties to be recorded with the event + */ + properties?: Record +} + /** - * PostHog client + * */ -class PostHog { +class EventTracker { + static postHogApiUrl = 'https://us.i.posthog.com' + static postHogApiKey = 'phc_SfqVeZcpYHmhUn9NRizThxFxiI9fKqvjRjmBDB8ToRs' static posthog: ReturnType | undefined = undefined + eventTrackingQueue: LocalForage /** - * Initialize the PostHog client if not already initialized + * Initialize the event tracking system */ constructor() { - if (!PostHog.posthog) { - PostHog.posthog = posthog.init('phc_SfqVeZcpYHmhUn9NRizThxFxiI9fKqvjRjmBDB8ToRs', { - api_host: 'https://us.i.posthog.com', + if (!EventTracker.posthog) { + EventTracker.posthog = posthog.init(EventTracker.postHogApiKey, { + api_host: EventTracker.postHogApiUrl, person_profiles: 'always', // Create profiles for anonymous users as well }) } + + if (!this.eventTrackingQueue) { + this.eventTrackingQueue = localforage.createInstance({ + driver: localforage.INDEXEDDB, + name: 'Cockpit - Event-tracking Queue', + storeName: 'cockpit-event-tracking-queue', + version: 1.0, + description: 'Queue of events to be sent to the event tracking system.', + }) + } + + this.sendEvents() } /** * Capture an event * @param {string} eventName - The name of the event - * @param {Record} properties - The properties of the event + * @param {Record} eventProperties - The properties of the event + */ + async capture(eventName: string, eventProperties?: Record): Promise { + const eventId = `${eventName}-${Date.now()}` + const eventPayload: EventPayload = { + eventName, + timestamp: Date.now(), + properties: eventProperties, + } + await this.eventTrackingQueue.setItem(eventId, eventPayload) + await this.sendEvents() + } + + /** + * Send all events in the queue to the event tracking system */ - capture(eventName: string, properties?: Record): void { - if (!PostHog.posthog) { - throw new Error('PostHog client not initialized.') + async sendEvents(): Promise { + const queuedEventsKeys = await this.eventTrackingQueue.keys() + const successfullySentEventsKeys: string[] = [] + + for (const eventId of queuedEventsKeys) { + const eventPayload = await this.eventTrackingQueue.getItem(eventId) + if (!eventPayload) continue + + try { + const body = { + api_key: EventTracker.postHogApiKey, + event: eventPayload.eventName, + properties: eventPayload.properties, + timestamp: eventPayload.timestamp, + distinct_id: EventTracker.posthog?.get_distinct_id(), + } + + await ky.post(`${EventTracker.postHogApiUrl}/capture/`, { json: body, mode: 'no-cors', throwHttpErrors: false }) + successfullySentEventsKeys.push(eventId) + console.log('Event sent successfully:', eventId) + } catch (error) { + console.error('Error sending event to PostHog:', error) + break + } + } + + for (const eventId of successfullySentEventsKeys) { + await this.eventTrackingQueue.removeItem(eventId) } - PostHog.posthog.capture(eventName, properties) } } -const eventTracker = new PostHog() +const eventTracker = new EventTracker() export default eventTracker