Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/components/views/settings/tabs/user/Libp2pSecurityTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2024 New Vector Ltd.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

import React from "react";

import AccessibleButton from "../../../elements/AccessibleButton";
import SettingsTab from "../SettingsTab";
import { SettingsSection } from "../../shared/SettingsSection";
import { SettingsSubsection } from "../../shared/SettingsSubsection";
import { exportIdentity, importIdentity } from "../../../../../utils/libp2p/IdentityBackup";

export default function Libp2pSecurityTab(): JSX.Element {
const onExport = (): void => {
// TODO: prompt user for password then call exportIdentity
void exportIdentity("password");
};

const onImport = (): void => {
// TODO: prompt user for file & password then call importIdentity
void importIdentity("{}", "password");
};

return (
<SettingsTab>
<SettingsSection heading="P2P Identity Management">
<SettingsSubsection heading="Backup & Recovery">
<AccessibleButton onClick={onExport}>Export Identity (Encrypted)</AccessibleButton>
<AccessibleButton onClick={onImport}>Import Identity</AccessibleButton>
</SettingsSubsection>
</SettingsSection>
</SettingsTab>
);
}
15 changes: 15 additions & 0 deletions src/components/views/toasts/ConnectivityToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ToastStore from "../../../stores/ToastStore";
import GenericToast from "./GenericToast";

export function showConnectivityToast(isOnline: boolean): void {
ToastStore.sharedInstance().addOrReplaceToast({
key: "connectivity",
title: isOnline ? "P2P Connected" : "P2P Disconnected",
props: {
description: isOnline ? "Peers reachable" : "Reconnecting...",
primaryLabel: "OK",
onPrimaryClick: () => ToastStore.sharedInstance().dismissToast("connectivity"),
},
component: GenericToast as any,
});
}
19 changes: 19 additions & 0 deletions src/libp2p/config/BootstrapPeers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import SettingsStore from "../../settings/SettingsStore";

interface BootstrapConfig {
alpha: string[];
beta: string[];
production: string[];
}

const BOOTSTRAP_CONFIG: BootstrapConfig = {
alpha: [],
beta: [],
production: [],
};

export function getBootstrapPeers(): string[] {
const phase = (SettingsStore.getValue("libp2p_rollout_phase") as string) || "alpha";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (BOOTSTRAP_CONFIG as any)[phase] || BOOTSTRAP_CONFIG.alpha;
}
12 changes: 12 additions & 0 deletions src/libp2p/monitoring/PosthogEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PosthogAnalytics } from "../../PosthogAnalytics";

export const LibP2PEvents = {
BOOTSTRAP_SUCCESS: "p2p_bootstrap_success",
MESSAGE_LATENCY: "p2p_message_latency",
RECONNECT_COUNT: "p2p_reconnect_count",
PEER_DISCOVERY: "p2p_peer_discovery",
} as const;

export function trackBootstrapSuccess(peerCount: number): void {
PosthogAnalytics.instance.trackEvent(LibP2PEvents.BOOTSTRAP_SUCCESS, { peerCount });
}
7 changes: 7 additions & 0 deletions src/libp2p/monitoring/SentryIntegration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as Sentry from "@sentry/browser";

export function initLibp2pSentry(): void {
// Add libp2p-specific error tracking
Sentry.addBreadcrumb({ category: "libp2p", message: "libp2p monitoring enabled" });
// Track bootstrap failures, connection issues, etc. via captureException elsewhere
}
31 changes: 31 additions & 0 deletions src/utils/libp2p/IdentityBackup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2024 New Vector Ltd.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

import { encodeUnpaddedBase64 } from "matrix-js-sdk/src/base64";

import * as Libp2pKeyService from "./Libp2pKeyService";

export async function exportIdentity(password: string): Promise<string> {
const priv = await Libp2pKeyService.loadLibp2pPrivateKey();
const peerId = await Libp2pKeyService.getPeerId();
if (!priv || !peerId) {
throw new Error("Missing libp2p identity");
}

const backup = {
peerId,
privKey: encodeUnpaddedBase64(priv),
// TODO: encrypt with password
};
return JSON.stringify(backup);
}

export async function importIdentity(encryptedJson: string, password: string): Promise<void> {
// TODO: decrypt using password
const data = JSON.parse(encryptedJson) as { peerId: string; privKey: string };
await Libp2pKeyService.store(data.privKey, data.peerId, undefined);
}
28 changes: 28 additions & 0 deletions test/unit-tests/utils/libp2p/IdentityBackup-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as Libp2pKeyService from "../../../../src/utils/libp2p/Libp2pKeyService";
import { exportIdentity, importIdentity } from "../../../../src/utils/libp2p/IdentityBackup";

jest.mock("../../../../src/utils/libp2p/Libp2pKeyService");

const mockService = Libp2pKeyService as jest.Mocked<typeof Libp2pKeyService>;

describe("IdentityBackup", () => {
beforeEach(() => {
jest.clearAllMocks();
});

it("exports identity to JSON", async () => {
mockService.loadLibp2pPrivateKey.mockResolvedValue(new Uint8Array([1]));
mockService.getPeerId.mockResolvedValue("peer");

const json = await exportIdentity("pwd");
const data = JSON.parse(json);
expect(data.peerId).toBe("peer");
expect(data.privKey).toBeDefined();
});

it("imports identity from JSON", async () => {
const json = JSON.stringify({ peerId: "p", privKey: "AA==" });
await importIdentity(json, "pwd");
expect(mockService.store).toHaveBeenCalledWith("AA==", "p", undefined);
});
});
13 changes: 13 additions & 0 deletions test/unit-tests/utils/libp2p/PosthogEvents-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { PosthogAnalytics } from "../../../../src/PosthogAnalytics";
import { trackBootstrapSuccess, LibP2PEvents } from "../../../../src/libp2p/monitoring/PosthogEvents";

jest.mock("../../../../src/PosthogAnalytics", () => ({
PosthogAnalytics: { instance: { trackEvent: jest.fn() } },
}));

describe("PosthogEvents", () => {
it("tracks bootstrap success", () => {
trackBootstrapSuccess(5);
expect(PosthogAnalytics.instance.trackEvent).toHaveBeenCalledWith(LibP2PEvents.BOOTSTRAP_SUCCESS, { peerCount: 5 });
});
});
Loading