Enforce anon/pseudo-anon via types

pull/21833/head
James Salter 2021-07-21 08:23:42 +01:00
parent 3135e42586
commit 74b0e52f9a
2 changed files with 54 additions and 19 deletions

View File

@ -1,11 +1,28 @@
import posthog from 'posthog-js'; import posthog from 'posthog-js';
import SdkConfig from './SdkConfig'; import SdkConfig from './SdkConfig';
export interface IEvent { interface IEvent {
key: string; // The event name that will be used by PostHog.
// TODO: standard format (camel case? snake? UpperCase?)
eventName: string;
// The properties of the event that will be stored in PostHog.
properties: {} properties: {}
} }
// If an event extends IPseudonymousEvent, the event contains pseudonymous data
// that won't be sent unless the user has explicitly consented to pseudonymous tracking.
// For example, hashed user IDs or room IDs.
export interface IPseudonymousEvent extends IEvent {}
// If an event extends IAnonymousEvent, the event strictly contains *only* anonymous data which
// may be sent without explicit user consent.
export interface IAnonymousEvent extends IEvent {}
export interface IRoomEvent extends IPseudonymousEvent {
hashedRoomId: string
}
export interface IOnboardingLoginBegin extends IEvent { export interface IOnboardingLoginBegin extends IEvent {
key: "onboarding_login_begin", key: "onboarding_login_begin",
} }
@ -55,27 +72,32 @@ export class PosthogAnalytics {
this.onlyTrackAnonymousEvents = enabled; this.onlyTrackAnonymousEvents = enabled;
} }
public track<E extends IEvent>( public trackPseudonymousEvent<E extends IPseudonymousEvent>(
key: E["key"], eventName: E["eventName"],
properties: E["properties"], properties: E["properties"],
anonymous = false,
) { ) {
if (!this.initialised) return; if (!this.initialised) return;
if (this.onlyTrackAnonymousEvents && !anonymous) return; if (this.onlyTrackAnonymousEvents) return;
this.posthog.capture(eventName, properties);
this.posthog.capture(key, properties);
} }
public async trackRoomEvent<E extends IEvent>( public trackAnonymousEvent<E extends IAnonymousEvent>(
key: E["key"], eventName: E["eventName"],
roomId: string,
properties: E["properties"], properties: E["properties"],
...args ) {
if (!this.initialised) return;
this.posthog.capture(eventName, properties);
}
public async trackRoomEvent<E extends IRoomEvent>(
eventName: E["eventName"],
roomId: string,
properties: Omit<E["properties"], "roomId">,
) { ) {
const updatedProperties = { const updatedProperties = {
...properties, ...properties,
hashedRoomId: roomId ? await hashHex(roomId) : null, hashedRoomId: roomId ? await hashHex(roomId) : null,
}; };
this.track(key, updatedProperties, ...args); this.trackPseudonymousEvent(eventName, updatedProperties);
} }
} }

View File

@ -1,4 +1,4 @@
import { IEvent, PosthogAnalytics } from '../src/PosthogAnalytics'; import { IAnonymousEvent, IRoomEvent, PosthogAnalytics } from '../src/PosthogAnalytics';
import SdkConfig from '../src/SdkConfig'; import SdkConfig from '../src/SdkConfig';
const crypto = require('crypto'); const crypto = require('crypto');
@ -12,13 +12,20 @@ class FakePosthog {
} }
} }
export interface ITestEvent extends IEvent { export interface ITestEvent extends IAnonymousEvent {
key: "jest_test_event", key: "jest_test_event",
properties: { properties: {
foo: string foo: string
} }
} }
export interface ITestRoomEvent extends IRoomEvent {
key: "jest_test_room_event",
properties: {
foo: string
}
}
describe("PosthogAnalytics", () => { describe("PosthogAnalytics", () => {
let analytics: PosthogAnalytics; let analytics: PosthogAnalytics;
let fakePosthog: FakePosthog; let fakePosthog: FakePosthog;
@ -61,7 +68,7 @@ describe("PosthogAnalytics", () => {
it("Should pass track() to posthog", () => { it("Should pass track() to posthog", () => {
analytics.init(false); analytics.init(false);
analytics.track<ITestEvent>("jest_test_event", { analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", {
foo: "bar", foo: "bar",
}); });
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event");
@ -71,7 +78,7 @@ describe("PosthogAnalytics", () => {
it("Should pass trackRoomEvent to posthog", () => { it("Should pass trackRoomEvent to posthog", () => {
analytics.init(false); analytics.init(false);
const roomId = "42"; const roomId = "42";
return analytics.trackRoomEvent<ITestEvent>("jest_test_event", roomId, { return analytics.trackRoomEvent<IRoomEvent>("jest_test_event", roomId, {
foo: "bar", foo: "bar",
}).then(() => { }).then(() => {
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event");
@ -82,11 +89,17 @@ describe("PosthogAnalytics", () => {
}); });
}); });
it("Should silently not send messages if not inititalised", () => { it("Should silently not track if not inititalised", () => {
analytics.track<ITestEvent>("jest_test_event", { analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", {
foo: "bar", foo: "bar",
}); });
expect(fakePosthog.capture.mock.calls.length).toBe(0); expect(fakePosthog.capture.mock.calls.length).toBe(0);
}); });
it("Should not track non-anonymous messages if onlyTrackAnonymousEvents is true", () => {
analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", {
foo: "bar",
});
});
}); });