Pass analyticsID to the elementCall iFrame (#9637)

Co-authored-by: Robin <robin@robin.town>
Co-authored-by: Timo K <timok@element.io>
This commit is contained in:
Timo 2022-12-22 13:09:57 +01:00 committed by GitHub
parent b81582d045
commit ce75d3381f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 3 deletions

View file

@ -134,7 +134,7 @@ export class PosthogAnalytics {
private readonly enabled: boolean = false; private readonly enabled: boolean = false;
private static _instance = null; private static _instance = null;
private platformSuperProperties = {}; private platformSuperProperties = {};
private static ANALYTICS_EVENT_TYPE = "im.vector.analytics"; public static readonly ANALYTICS_EVENT_TYPE = "im.vector.analytics";
private propertiesForNextEvent: Partial<Record<"$set" | "$set_once", UserProperties>> = {}; private propertiesForNextEvent: Partial<Record<"$set" | "$set_once", UserProperties>> = {};
private userPropertyCache: UserProperties = {}; private userPropertyCache: UserProperties = {};
private authenticationType: Signup["authenticationType"] = "Other"; private authenticationType: Signup["authenticationType"] = "Other";

View file

@ -53,6 +53,7 @@ import { getCurrentLanguage } from "../languageHandler";
import DesktopCapturerSourcePicker from "../components/views/elements/DesktopCapturerSourcePicker"; import DesktopCapturerSourcePicker from "../components/views/elements/DesktopCapturerSourcePicker";
import Modal from "../Modal"; import Modal from "../Modal";
import { FontWatcher } from "../settings/watchers/FontWatcher"; import { FontWatcher } from "../settings/watchers/FontWatcher";
import { PosthogAnalytics } from "../PosthogAnalytics";
const TIMEOUT_MS = 16000; const TIMEOUT_MS = 16000;
@ -626,6 +627,15 @@ export class ElementCall extends Call {
} }
private constructor(public readonly groupCall: GroupCall, client: MatrixClient) { private constructor(public readonly groupCall: GroupCall, client: MatrixClient) {
const accountAnalyticsData = client.getAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE);
// The analyticsID is passed directly to element call (EC) since this codepath is only for EC and no other widget.
// We really don't want the same analyticID's for the EC and EW posthog instances (Data on posthog should be limited/anonymized as much as possible).
// This is prohibited in EC where a hashed version of the analyticsID is used for the actual posthog identification.
// We can pass the raw EW analyticsID here since we need to trust EC with not sending sensitive data to posthog (EC has access to more sensible data than the analyticsID e.g. the username)
const analyticsID: string = accountAnalyticsData?.getContent().pseudonymousAnalyticsOptIn
? accountAnalyticsData?.getContent().id
: "";
// Splice together the Element Call URL for this call // Splice together the Element Call URL for this call
const params = new URLSearchParams({ const params = new URLSearchParams({
embed: "", embed: "",
@ -637,6 +647,7 @@ export class ElementCall extends Call {
baseUrl: client.baseUrl, baseUrl: client.baseUrl,
lang: getCurrentLanguage().replace("_", "-"), lang: getCurrentLanguage().replace("_", "-"),
fontScale: `${SettingsStore.getValue("baseFontSize") / FontWatcher.DEFAULT_SIZE}`, fontScale: `${SettingsStore.getValue("baseFontSize") / FontWatcher.DEFAULT_SIZE}`,
analyticsID,
}); });
// Set custom fonts // Set custom fonts

View file

@ -23,6 +23,7 @@ import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { Widget } from "matrix-widget-api"; import { Widget } from "matrix-widget-api";
import { GroupCallIntent } from "matrix-js-sdk/src/webrtc/groupCall"; import { GroupCallIntent } from "matrix-js-sdk/src/webrtc/groupCall";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import type { Mocked } from "jest-mock"; import type { Mocked } from "jest-mock";
import type { MatrixClient, IMyDevice } from "matrix-js-sdk/src/client"; import type { MatrixClient, IMyDevice } from "matrix-js-sdk/src/client";
@ -40,6 +41,7 @@ import { ElementWidgetActions } from "../../src/stores/widgets/ElementWidgetActi
import SettingsStore from "../../src/settings/SettingsStore"; import SettingsStore from "../../src/settings/SettingsStore";
import Modal, { IHandle } from "../../src/Modal"; import Modal, { IHandle } from "../../src/Modal";
import PlatformPeg from "../../src/PlatformPeg"; import PlatformPeg from "../../src/PlatformPeg";
import { PosthogAnalytics } from "../../src/PosthogAnalytics";
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({ jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
[MediaDeviceKindEnum.AudioInput]: [ [MediaDeviceKindEnum.AudioInput]: [
@ -622,6 +624,53 @@ describe("ElementCall", () => {
SettingsStore.getValue = originalGetValue; SettingsStore.getValue = originalGetValue;
}); });
it("passes analyticsID through widget URL", async () => {
client.getAccountData.mockImplementation((eventType: string) => {
if (eventType === PosthogAnalytics.ANALYTICS_EVENT_TYPE) {
return new MatrixEvent({ content: { id: "123456789987654321", pseudonymousAnalyticsOptIn: true } });
}
return undefined;
});
await ElementCall.create(room);
const call = Call.get(room);
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
expect(urlParams.get("analyticsID")).toBe("123456789987654321");
});
it("does not pass analyticsID if `pseudonymousAnalyticsOptIn` set to false", async () => {
client.getAccountData.mockImplementation((eventType: string) => {
if (eventType === PosthogAnalytics.ANALYTICS_EVENT_TYPE) {
return new MatrixEvent({
content: { id: "123456789987654321", pseudonymousAnalyticsOptIn: false },
});
}
return undefined;
});
await ElementCall.create(room);
const call = Call.get(room);
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
expect(urlParams.get("analyticsID")).toBe("");
});
it("passes empty analyticsID if the id is not in the account data", async () => {
client.getAccountData.mockImplementation((eventType: string) => {
if (eventType === PosthogAnalytics.ANALYTICS_EVENT_TYPE) {
return new MatrixEvent({ content: {} });
}
return undefined;
});
await ElementCall.create(room);
const call = Call.get(room);
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1));
expect(urlParams.get("analyticsID")).toBe("");
});
}); });
describe("instance in a non-video room", () => { describe("instance in a non-video room", () => {

View file

@ -136,7 +136,7 @@ export function createTestClient(): MatrixClient {
getTurnServers: jest.fn().mockReturnValue([]), getTurnServers: jest.fn().mockReturnValue([]),
getTurnServersExpiry: jest.fn().mockReturnValue(2 ^ 32), getTurnServersExpiry: jest.fn().mockReturnValue(2 ^ 32),
getThirdpartyUser: jest.fn().mockResolvedValue([]), getThirdpartyUser: jest.fn().mockResolvedValue([]),
getAccountData: (type) => { getAccountData: jest.fn().mockImplementation((type) => {
return mkEvent({ return mkEvent({
user: undefined, user: undefined,
room: undefined, room: undefined,
@ -144,7 +144,7 @@ export function createTestClient(): MatrixClient {
event: true, event: true,
content: {}, content: {},
}); });
}, }),
mxcUrlToHttp: (mxc) => `http://this.is.a.url/${mxc.substring(6)}`, mxcUrlToHttp: (mxc) => `http://this.is.a.url/${mxc.substring(6)}`,
setAccountData: jest.fn(), setAccountData: jest.fn(),
setRoomAccountData: jest.fn(), setRoomAccountData: jest.fn(),