First pass at a PosthogAnalytics class

This commit is contained in:
James Salter 2021-07-21 07:40:39 +01:00
parent 48817499e4
commit 2a48d3c9bc
4 changed files with 179 additions and 0 deletions

View file

@ -86,6 +86,7 @@
"pako": "^2.0.3", "pako": "^2.0.3",
"parse5": "^6.0.1", "parse5": "^6.0.1",
"png-chunks-extract": "^1.0.0", "png-chunks-extract": "^1.0.0",
"posthog-js": "^1.12.1",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"re-resizable": "^6.9.0", "re-resizable": "^6.9.0",

82
src/PosthogAnalytics.ts Normal file
View file

@ -0,0 +1,82 @@
import posthog from 'posthog-js';
import SdkConfig from './SdkConfig';
export interface IEvent {
key: string;
properties: {}
}
export interface IOnboardingLoginBegin extends IEvent {
key: "onboarding_login_begin",
}
const hashHex = async (input: string): Promise<string> => {
const buf = new TextEncoder().encode(input);
const digestBuf = await window.crypto.subtle.digest("sha-256", buf);
return [...new Uint8Array(digestBuf)].map((b: number) => b.toString(16).padStart(2, "0")).join("");
};
export class PosthogAnalytics {
private onlyTrackAnonymousEvents = false;
private initialised = false;
private posthog = null;
private static _instance = null;
public static instance(): PosthogAnalytics {
if (!this.instance) {
this._instance = new PosthogAnalytics(posthog);
}
return this._instance;
}
constructor(posthog) {
this.posthog = posthog;
}
public init(onlyTrackAnonymousEvents: boolean) {
if (Boolean(navigator.doNotTrack === "1")) {
this.initialised = false;
return;
}
this.onlyTrackAnonymousEvents = onlyTrackAnonymousEvents;
const posthogConfig = SdkConfig.get()["posthog"];
if (posthogConfig) {
console.log(`Initialising Posthog for ${posthogConfig.apiHost}`);
this.posthog.init(posthogConfig.projectApiKey, { api_host: posthogConfig.apiHost });
this.initialised = true;
}
}
public isInitialised(): boolean {
return this.initialised;
}
public setOnlyTrackAnonymousEvents(enabled: boolean) {
this.onlyTrackAnonymousEvents = enabled;
}
public track<E extends IEvent>(
key: E["key"],
properties: E["properties"],
anonymous = false,
) {
if (!this.initialised) return;
if (this.onlyTrackAnonymousEvents && !anonymous) return;
this.posthog.capture(key, properties);
}
public async trackRoomEvent<E extends IEvent>(
key: E["key"],
roomId: string,
properties: E["properties"],
...args
) {
const updatedProperties = {
...properties,
hashedRoomId: roomId ? await hashHex(roomId) : null,
};
this.track(key, updatedProperties, ...args);
}
}

View file

@ -0,0 +1,84 @@
import { IEvent, PosthogAnalytics } from '../src/PosthogAnalytics';
import SdkConfig from '../src/SdkConfig';
const crypto = require('crypto');
class FakePosthog {
public capture;
public init;
constructor() {
this.capture = jest.fn();
this.init = jest.fn();
}
}
export interface ITestEvent extends IEvent {
key: "jest_test_event",
properties: {
foo: string
}
}
describe("PosthogAnalytics", () => {
let analytics: PosthogAnalytics;
let fakePosthog: FakePosthog;
beforeEach(() => {
fakePosthog = new FakePosthog();
analytics = new PosthogAnalytics(fakePosthog);
window.crypto = {
subtle: crypto.webcrypto.subtle,
};
});
afterEach(() => {
navigator.doNotTrack = null;
window.crypto = null;
});
it("Should not initialise if DNT is enabled", () => {
navigator.doNotTrack = "1";
analytics.init(false);
expect(analytics.isInitialised()).toBe(false);
});
it("Should not initialise if config is not set", () => {
jest.spyOn(SdkConfig, "get").mockReturnValue({});
analytics.init(false);
expect(analytics.isInitialised()).toBe(false);
});
it("Should initialise if config is set", () => {
jest.spyOn(SdkConfig, "get").mockReturnValue({
posthog: {
projectApiKey: "foo",
apiHost: "bar",
},
});
analytics.init(false);
expect(analytics.isInitialised()).toBe(true);
});
it("Should pass track() to posthog", () => {
analytics.init(false);
analytics.track<ITestEvent>("jest_test_event", {
foo: "bar",
});
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event");
expect(fakePosthog.capture.mock.calls[0][1]).toEqual({ foo: "bar" });
});
it("Should pass trackRoomEvent to posthog", () => {
analytics.init(false);
const roomId = "42";
return analytics.trackRoomEvent<ITestEvent>("jest_test_event", roomId, {
foo: "bar",
}).then(() => {
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event");
expect(fakePosthog.capture.mock.calls[0][1]).toEqual({
foo: "bar",
hashedRoomId: "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049",
});
});
});
});

View file

@ -3601,6 +3601,11 @@ fbjs@^0.8.4:
setimmediate "^1.0.5" setimmediate "^1.0.5"
ua-parser-js "^0.7.18" ua-parser-js "^0.7.18"
fflate@^0.4.1:
version "0.4.8"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
file-entry-cache@^6.0.0: file-entry-cache@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a"
@ -6287,6 +6292,13 @@ postcss@^8.0.2:
nanoid "^3.1.20" nanoid "^3.1.20"
source-map "^0.6.1" source-map "^0.6.1"
posthog-js@^1.12.1:
version "1.12.1"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.1.tgz#97834ee2574f34ffb5db2f5b07452c847e3c4d27"
integrity sha512-Y3lzcWkS8xFY6Ryj3I4ees7qWP2WGkLw0Arcbk5xaT0+5YlA6UC2jlL/+fN9bz/Bl62EoN3BML901Cuot/QNjg==
dependencies:
fflate "^0.4.1"
prelude-ls@^1.2.1: prelude-ls@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"