diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index f01246ec00..e68cae5390 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -19,6 +19,7 @@ import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; import SettingsStore from './settings/SettingsStore'; import { MatrixClientPeg } from "./MatrixClientPeg"; +import { MatrixClient } from "../../matrix-js-sdk"; /* Posthog analytics tracking. * @@ -282,23 +283,28 @@ export class PosthogAnalytics { ); } - public async identifyUser(): Promise { + public async identifyUser(client: MatrixClientPeg, analyticsIdGenerator: () => string): Promise { if (this.anonymity == Anonymity.Pseudonymous) { // Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows // different devices to send the same ID. - const client = MatrixClientPeg.get(); - const accountData = await client.getAccountDataFromServer("im.vector.web.analytics_id"); - let analyticsID = accountData?.id; - if (!analyticsID) { - // Couldn't retrieve an analytics ID from user settings, so create one and set it on the server. - // Note there's a race condition here - if two devices do these steps at the same time, last write - // wins, and the first writer will send tracking with an ID that doesn't match the one on the server - // until the next time account data is refreshed and this function is called (most likely on next - // page load). This will happen pretty infrequently, so we can tolerate the possibility. - analyticsID = PosthogAnalytics.getUUIDv4(); - await client.setAccountData("im.vector.web.analytics_id", { id: analyticsID }); + try { + const accountData = await client.getAccountDataFromServer("im.vector.web.analytics_id"); + let analyticsID = accountData?.id; + if (!analyticsID) { + // Couldn't retrieve an analytics ID from user settings, so create one and set it on the server. + // Note there's a race condition here - if two devices do these steps at the same time, last write + // wins, and the first writer will send tracking with an ID that doesn't match the one on the server + // until the next time account data is refreshed and this function is called (most likely on next + // page load). This will happen pretty infrequently, so we can tolerate the possibility. + analyticsID = analyticsIdGenerator(); + await client.setAccountData("im.vector.web.analytics_id", { id: analyticsID }); + } + this.posthog.identify(analyticsID); + } catch (e) { + // The above could fail due to network requests, but not essential to starting the application, + // so swallow it. + console.log("Unable to identify user for tracking" + e.toString()); } - this.posthog.identify(analyticsID); } } @@ -371,7 +377,7 @@ export class PosthogAnalytics { // Identify the user (via hashed user ID) to posthog if anonymity is pseudonmyous this.setAnonymity(PosthogAnalytics.getAnonymityFromSettings()); if (userId && this.getAnonymity() == Anonymity.Pseudonymous) { - await this.identifyUser(userId); + await this.identifyUser(MatrixClientPeg.get(), PosthogAnalytics.getUUIDv4); } } } diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts index 6cb1743051..10dec617fc 100644 --- a/test/PosthogAnalytics-test.ts +++ b/test/PosthogAnalytics-test.ts @@ -24,6 +24,7 @@ import { } from '../src/PosthogAnalytics'; import SdkConfig from '../src/SdkConfig'; +import { MatrixClientPeg } from "../src/MatrixClientPeg"; class FakePosthog { public capture; @@ -218,15 +219,28 @@ bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`); it("Should identify the user to posthog if pseudonymous", async () => { analytics.setAnonymity(Anonymity.Pseudonymous); - await analytics.identifyUser("foo"); - expect(fakePosthog.identify.mock.calls[0][0]) - .toBe("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); + class FakeClient { + getAccountDataFromServer = jest.fn().mockResolvedValue(null); + setAccountData = jest.fn().mockResolvedValue({}); + } + await analytics.identifyUser(new FakeClient(), () => "analytics_id" ); + expect(fakePosthog.identify.mock.calls[0][0]).toBe("analytics_id"); }); it("Should not identify the user to posthog if anonymous", async () => { analytics.setAnonymity(Anonymity.Anonymous); - await analytics.identifyUser("foo"); + await analytics.identifyUser(null); expect(fakePosthog.identify.mock.calls.length).toBe(0); }); + + it("Should identify using the server's analytics id if present", async () => { + analytics.setAnonymity(Anonymity.Pseudonymous); + class FakeClient { + getAccountDataFromServer = jest.fn().mockResolvedValue({ id: "existing_analytics_id" }); + setAccountData = jest.fn().mockResolvedValue({}); + } + await analytics.identifyUser(new FakeClient(), () => "new_analytics_id" ); + expect(fakePosthog.identify.mock.calls[0][0]).toBe("existing_analytics_id"); + }); }); });