Skip to content

Commit 3182cec

Browse files
committed
feat(desktop): use datadogId from identities in Sentry
Sentry renderer init takes store and sets user id via datadogIdSelector. Anonymizer and tests updated.
1 parent d83323a commit 3182cec

File tree

3 files changed

+112
-11
lines changed

3 files changed

+112
-11
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/* eslint-disable @typescript-eslint/no-require-imports -- need require() after resetModules to get fresh module with env */
2+
describe("sentry anonymizer", () => {
3+
const origEnv = process.env;
4+
5+
beforeEach(() => {
6+
jest.resetModules();
7+
process.env = { ...origEnv };
8+
});
9+
10+
afterAll(() => {
11+
process.env = origEnv;
12+
});
13+
14+
describe("filepath", () => {
15+
it("should return path unchanged when env paths are not set", () => {
16+
process.env.LEDGER_CONFIG_DIRECTORY = "";
17+
process.env.HOME_DIRECTORY = "";
18+
const anonymizer = require("~/sentry/anonymizer").default;
19+
expect(anonymizer.filepath("/some/path")).toBe("");
20+
});
21+
22+
it("should return path unchanged for app:// URLs", () => {
23+
process.env.LEDGER_CONFIG_DIRECTORY = "/tmp/config";
24+
process.env.HOME_DIRECTORY = "/tmp/home";
25+
const anonymizer = require("~/sentry/anonymizer").default;
26+
expect(anonymizer.filepath("app://foo/bar")).toBe("app://foo/bar");
27+
});
28+
29+
it("should replace USER_DATA path when LEDGER_CONFIG_DIRECTORY is set", () => {
30+
process.env.LEDGER_CONFIG_DIRECTORY = "/tmp/user-data";
31+
process.env.HOME_DIRECTORY = "/tmp/home";
32+
const anonymizer = require("~/sentry/anonymizer").default;
33+
expect(anonymizer.filepath("/tmp/user-data/foo/log.txt")).toBe("$USER_DATA/foo/log.txt");
34+
});
35+
36+
it("should replace HOME path when HOME_DIRECTORY is set", () => {
37+
process.env.LEDGER_CONFIG_DIRECTORY = "/tmp/config";
38+
process.env.HOME_DIRECTORY = "/Users/test";
39+
const anonymizer = require("~/sentry/anonymizer").default;
40+
expect(anonymizer.filepath("/Users/test/file")).toBe("$HOME/file");
41+
});
42+
43+
it("should replace both paths when both are set", () => {
44+
process.env.LEDGER_CONFIG_DIRECTORY = "/tmp/config";
45+
process.env.HOME_DIRECTORY = "/tmp/home";
46+
const anonymizer = require("~/sentry/anonymizer").default;
47+
expect(anonymizer.filepath("/tmp/home/.config/app")).toBe("$HOME/.config/app");
48+
expect(anonymizer.filepath("/tmp/config/log")).toBe("$USER_DATA/log");
49+
});
50+
});
51+
52+
describe("filepathRecursiveReplacer", () => {
53+
beforeEach(() => {
54+
process.env.LEDGER_CONFIG_DIRECTORY = "/tmp/config";
55+
process.env.HOME_DIRECTORY = "/tmp/home";
56+
});
57+
58+
it("should replace path in plain object", () => {
59+
const anonymizer = require("~/sentry/anonymizer").default;
60+
const obj = { path: "/tmp/config/foo" };
61+
anonymizer.filepathRecursiveReplacer(obj);
62+
expect(obj.path).toBe("$USER_DATA/foo");
63+
});
64+
65+
it("should replace path in array", () => {
66+
const anonymizer = require("~/sentry/anonymizer").default;
67+
const arr = ["/tmp/home/x"];
68+
anonymizer.filepathRecursiveReplacer(arr);
69+
expect(arr[0]).toBe("$HOME/x");
70+
});
71+
72+
it("should replace path in Error message", () => {
73+
const anonymizer = require("~/sentry/anonymizer").default;
74+
const err = new Error("Failed at /tmp/config/file.ts");
75+
anonymizer.filepathRecursiveReplacer(err);
76+
expect(err.message).toBe("Failed at $USER_DATA/file.ts");
77+
});
78+
79+
it("should handle nested objects", () => {
80+
const anonymizer = require("~/sentry/anonymizer").default;
81+
const obj = { inner: { path: "/tmp/home/bar" } };
82+
anonymizer.filepathRecursiveReplacer(obj);
83+
expect(obj).toMatchObject({ inner: { path: "$HOME/bar" } });
84+
});
85+
86+
it("should not recurse into already-seen references (circular)", () => {
87+
const anonymizer = require("~/sentry/anonymizer").default;
88+
const obj: Record<string, unknown> = { a: "/tmp/config/x" };
89+
obj.self = obj;
90+
anonymizer.filepathRecursiveReplacer(obj);
91+
expect(obj.a).toBe("$USER_DATA/x");
92+
expect(obj.self).toBe(obj);
93+
});
94+
});
95+
});

apps/ledger-live-desktop/src/sentry/anonymizer.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ let configDir = (() => {
99

1010
// we load in async the user data. there is a short period where this will be "" but then it becomes the real path
1111
// eslint-disable-next-line @typescript-eslint/no-var-requires
12-
require("electron")
13-
.ipcRenderer.invoke("getPathUserData")
14-
.then((path: string) => {
12+
const invoke = require("electron")?.ipcRenderer?.invoke;
13+
if (typeof invoke === "function") {
14+
invoke("getPathUserData").then((path: string) => {
1515
configDir = path;
1616
});
17+
}
1718
return "";
1819
})();
1920

@@ -27,11 +28,12 @@ let homeDir = (() => {
2728
}
2829

2930
// eslint-disable-next-line @typescript-eslint/no-var-requires
30-
require("electron")
31-
.ipcRenderer.invoke("getPathHome")
32-
.then((path: string) => {
31+
const invoke = require("electron")?.ipcRenderer?.invoke;
32+
if (typeof invoke === "function") {
33+
invoke("getPathHome").then((path: string) => {
3334
homeDir = path;
3435
});
36+
}
3537
return "";
3638
})();
3739

@@ -49,7 +51,7 @@ function filepathReplace(path: string): string {
4951
const p: string = basePaths[name];
5052
return path
5153
.replaceAll(p, name) // normal replace of the path
52-
.replaceAll(encodeURI(p.replace(/\\/g, "/")), name); // replace of the URI version of the path (that are in file:///)
54+
.replaceAll(encodeURI(p.replaceAll("\\", "/")), name); // replace of the URI version of the path (that are in file:///)
5355
}, path);
5456
return replaced;
5557
}

apps/ledger-live-desktop/src/sentry/renderer.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import * as Sentry from "@sentry/electron/renderer";
2-
import user from "./../helpers/user";
2+
import { datadogIdSelector } from "@ledgerhq/client-ids/store";
33
import { init, setShouldSendCallback } from "./install";
44
import { Primitive } from "@sentry/types";
5+
import type { Store } from "redux";
6+
import type { State } from "~/renderer/reducers";
7+
58
// @ts-expect-error The right type would be SentryMainModule from "@sentry/electron/main"…
69
// …but we should avoid importing the whole module and blow the bundle size
710
const available = init(Sentry, {
811
integrations: [Sentry.browserTracingIntegration()],
912
});
10-
export default async (shouldSendCallback: () => boolean) => {
13+
14+
export default async (shouldSendCallback: () => boolean, store: Store<State>) => {
1115
if (!available) return;
1216
setShouldSendCallback(shouldSendCallback);
13-
const u = await user();
17+
const datadogId = datadogIdSelector(store.getState());
1418
Sentry.setUser({
15-
id: u.id,
19+
id: datadogId.exportDatadogIdForSentry(),
1620
ip_address: undefined,
1721
});
1822
};

0 commit comments

Comments
 (0)