diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index 63cb3bc422..fd8bb44e0b 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -69,7 +69,6 @@ export class PosthogAnalytics { private anonymity = Anonymity.Anonymous; private initialised = false; private posthog?: PostHog = null; - private redactedCurrentLocation = null; private enabled = false; private static _instance = null; @@ -85,21 +84,20 @@ export class PosthogAnalytics { this.posthog = posthog; } - public async init(anonymity: Anonymity) { - this.anonymity = Boolean(navigator.doNotTrack === "1") ? Anonymity.Anonymous : anonymity; - + public init(anonymity: Anonymity) { const posthogConfig = SdkConfig.get()["posthog"]; if (posthogConfig) { - // Update the redacted current location before initialising posthog, as posthog.init triggers - // an immediate pageview event which calls the sanitize_properties callback - await this.updateRedactedCurrentLocation(); - this.posthog.init(posthogConfig.projectApiKey, { api_host: posthogConfig.apiHost, autocapture: false, mask_all_text: true, mask_all_element_attributes: true, + // this is disabled for now as its tricky to sanitize properties of the pageview + // event because sanitization requires async crypto calls and the sanitize_properties + // callback is synchronous. + capture_pageview: false, sanitize_properties: this.sanitizeProperties.bind(this), + respect_dnt: true, }); this.initialised = true; this.enabled = true; @@ -108,20 +106,20 @@ export class PosthogAnalytics { } } - private async updateRedactedCurrentLocation() { - // TODO only calculate this when the location changes as its expensive - const { origin, hash, pathname } = window.location; - this.redactedCurrentLocation = await getRedactedCurrentLocation( - origin, hash, pathname, this.anonymity); - } - private sanitizeProperties(properties: posthog.Properties, _: string): posthog.Properties { - // Sanitize posthog's built in properties which leak PII e.g. url reporting - // see utils.js _.info.properties in posthog-js + // Callback from posthog to sanitize properties before sending them to the server. + // + // Here we sanitize posthog's built in properties which leak PII e.g. url reporting. + // See utils.js _.info.properties in posthog-js. - // this.redactedCurrentLocation needs to have been updated prior to reaching this point as - // updating it involves async, which this callback is not - properties['$current_url'] = this.redactedCurrentLocation; + // Replace the $current_url with a redacted version. + // $redacted_current_url is injected by this class earlier in capture(), as its generation + // is async and can't be done in this non-async callback. + if (!properties['$redacted_current_url']) { + console.log("$redacted_current_url not set in sanitizeProperties, will drop $current_url entirely"); + } + properties['$current_url'] = properties['$redacted_current_url']; + delete properties['$redacted_current_url']; if (this.anonymity == Anonymity.Anonymous) { // drop referrer information for anonymous users @@ -172,7 +170,9 @@ export class PosthogAnalytics { if (!this.initialised) { throw Error("Tried to track event before PoshogAnalytics.init has completed"); } - await this.updateRedactedCurrentLocation(); + const { origin, hash, pathname } = window.location; + properties['$redacted_current_url'] = await getRedactedCurrentLocation( + origin, hash, pathname, this.anonymity); this.posthog.capture(eventName, properties); } diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts index 515d51b8e4..a1e54dc05d 100644 --- a/test/PosthogAnalytics-test.ts +++ b/test/PosthogAnalytics-test.ts @@ -49,7 +49,7 @@ describe("PosthogAnalytics", () => { describe("Initialisation", () => { it("Should not initialise if config is not set", async () => { jest.spyOn(SdkConfig, "get").mockReturnValue({}); - await analytics.init(Anonymity.Pseudonymous); + analytics.init(Anonymity.Pseudonymous); expect(analytics.isEnabled()).toBe(false); }); @@ -60,21 +60,14 @@ describe("PosthogAnalytics", () => { apiHost: "bar", }, }); - await analytics.init(Anonymity.Pseudonymous); + analytics.init(Anonymity.Pseudonymous); expect(analytics.isInitialised()).toBe(true); expect(analytics.isEnabled()).toBe(true); }); - - it("Should force anonymous if DNT is enabled", async () => { - navigator.doNotTrack = "1"; - await analytics.init(Anonymity.Pseudonymous); - expect(analytics.getAnonymity()).toBe(Anonymity.Anonymous); - }); }); describe("Tracking", () => { beforeEach(() => { - navigator.doNotTrack = null; jest.spyOn(SdkConfig, "get").mockReturnValue({ posthog: { projectApiKey: "foo", @@ -84,25 +77,24 @@ describe("PosthogAnalytics", () => { }); it("Should pass track() to posthog", async () => { - await analytics.init(Anonymity.Pseudonymous); + analytics.init(Anonymity.Pseudonymous); await analytics.trackAnonymousEvent("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" }); + expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); }); it("Should pass trackRoomEvent to posthog", async () => { - await analytics.init(Anonymity.Pseudonymous); + analytics.init(Anonymity.Pseudonymous); const roomId = "42"; await analytics.trackRoomEvent("jest_test_event", roomId, { foo: "bar", }); expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event"); - expect(fakePosthog.capture.mock.calls[0][1]).toEqual({ - foo: "bar", - hashedRoomId: "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049", - }); + expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar"); + expect(fakePosthog.capture.mock.calls[0][1]["hashedRoomId"]) + .toEqual("73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"); }); it("Should silently not track if not inititalised", async () => { @@ -113,7 +105,7 @@ describe("PosthogAnalytics", () => { }); it("Should not track non-anonymous messages if anonymous", async () => { - await analytics.init(Anonymity.Anonymous); + analytics.init(Anonymity.Anonymous); await analytics.trackPseudonymousEvent("jest_test_event", { foo: "bar", }); @@ -151,14 +143,14 @@ bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`); }); it("Should identify the user to posthog if pseudonymous", async () => { - await analytics.init(Anonymity.Pseudonymous); + analytics.init(Anonymity.Pseudonymous); await analytics.identifyUser("foo"); expect(fakePosthog.identify.mock.calls[0][0]) .toBe("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); }); it("Should not identify the user to posthog if anonymous", async () => { - await analytics.init(Anonymity.Anonymous); + analytics.init(Anonymity.Anonymous); await analytics.identifyUser("foo"); expect(fakePosthog.identify.mock.calls.length).toBe(0); });