feat(p2p settings): add browser p2p stats

This commit is contained in:
Tommaso Casaburi
2026-05-07 16:56:29 +07:00
parent 2ebd9ecc30
commit 14a4790c8b
51 changed files with 1457 additions and 249 deletions
+70 -8
View File
@@ -1,6 +1,21 @@
import { describe, expect, it } from 'vitest';
import { configureP2PBrowserPkcOptions, isP2PBrowserHostname, P2P_BROWSER_PKC_OPTIONS } from '../p2p-browser-config';
import {
configureP2PBrowserPkcOptions,
getPureP2PBrowserPreference,
isP2PBrowserHostname,
P2P_BROWSER_PKC_OPTIONS,
PURE_P2P_BROWSER_SETTING_KEY,
setPureP2PBrowserPreference,
shouldUsePureP2PBrowser,
} from '../p2p-browser-config';
const createStorage = (values: Record<string, string | undefined> = {}) => ({
getItem: (key: string) => values[key] ?? null,
setItem: (key: string, value: string) => {
values[key] = value;
},
});
describe('p2p-browser-config', () => {
it('detects p2p subdomains', () => {
@@ -10,31 +25,78 @@ describe('p2p-browser-config', () => {
expect(isP2PBrowserHostname('www.p2p.5chan.app')).toBe(false);
});
it('configures browser PKC options for p2p hostnames', () => {
it('leaves browser PKC options untouched by default', () => {
const targetWindow = {
location: { hostname: 'p2p.5chan.app' },
location: { hostname: '5chan.app' },
localStorage: createStorage(),
defaultPkcOptions: {
ipfsGatewayUrls: ['https://gateway.example'],
},
};
expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(false);
expect(targetWindow.defaultPkcOptions).toEqual({
ipfsGatewayUrls: ['https://gateway.example'],
});
});
it('configures browser PKC options when pure p2p is enabled', () => {
const targetWindow = {
location: { hostname: '5chan.app' },
localStorage: createStorage({ [PURE_P2P_BROWSER_SETTING_KEY]: 'true' }),
defaultPkcOptions: {
ipfsGatewayUrls: ['https://gateway.example'],
},
};
expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(true);
expect(targetWindow.defaultPkcOptions).toEqual({
ipfsGatewayUrls: ['https://gateway.example'],
...P2P_BROWSER_PKC_OPTIONS,
});
expect(targetWindow.defaultPkcOptions).toEqual(P2P_BROWSER_PKC_OPTIONS);
});
it('leaves normal hostnames untouched', () => {
it('leaves browser PKC options untouched when pure p2p is disabled', () => {
const defaultPkcOptions = {
ipfsGatewayUrls: ['https://gateway.example'],
};
const targetWindow = {
location: { hostname: '5chan.app' },
localStorage: createStorage({ [PURE_P2P_BROWSER_SETTING_KEY]: 'false' }),
defaultPkcOptions,
};
expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(false);
expect(targetWindow.defaultPkcOptions).toBe(defaultPkcOptions);
});
it('leaves electron defaults untouched', () => {
const defaultPkcOptions = {
pkcRpcClientsOptions: ['ws://localhost:9138'],
};
const targetWindow = {
electronApi: { isElectron: true },
location: { hostname: 'localhost' },
localStorage: createStorage(),
defaultPkcOptions,
};
expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(false);
expect(targetWindow.defaultPkcOptions).toBe(defaultPkcOptions);
});
it('persists and reads the browser pure p2p preference', () => {
const targetWindow = {
location: { hostname: '5chan.app' },
localStorage: createStorage(),
};
expect(getPureP2PBrowserPreference(targetWindow)).toBeUndefined();
expect(shouldUsePureP2PBrowser(targetWindow)).toBe(false);
setPureP2PBrowserPreference(false, targetWindow);
expect(getPureP2PBrowserPreference(targetWindow)).toBe(false);
expect(shouldUsePureP2PBrowser(targetWindow)).toBe(false);
setPureP2PBrowserPreference(true, targetWindow);
expect(getPureP2PBrowserPreference(targetWindow)).toBe(true);
expect(shouldUsePureP2PBrowser(targetWindow)).toBe(true);
});
});
+63
View File
@@ -0,0 +1,63 @@
import { describe, expect, it } from 'vitest';
import {
getBrowserGatewayAccountOptions,
getBrowserPureP2PAccountOptions,
getP2PRuntimeMode,
isBrowserPureP2PEnabled,
shouldShowP2PSettingsSection,
} from '../p2p-runtime';
const browserWindow = {
electronApi: undefined,
isElectron: false,
localStorage: {
getItem: () => null,
setItem: () => undefined,
},
} as unknown as Window;
const electronWindow = {
electronApi: { isElectron: true },
isElectron: true,
} as unknown as Window;
describe('p2p-runtime', () => {
it('detects browser libp2p accounts from options and live clients', () => {
expect(getP2PRuntimeMode({ pkcOptions: { libp2pJsClientsOptions: [{ key: 'libp2pjs' }] } }, browserWindow)).toBe('browser-libp2p');
expect(getP2PRuntimeMode({ pkc: { clients: { libp2pJsClients: { libp2pjs: {} } } } }, browserWindow)).toBe('browser-libp2p');
});
it('detects electron Kubo RPC accounts only in electron runtime', () => {
const account = { pkcOptions: { pkcRpcClientsOptions: ['ws://localhost:9138'] } };
expect(getP2PRuntimeMode(account, electronWindow)).toBe('electron-kubo-rpc');
expect(getP2PRuntimeMode(account, browserWindow)).toBeNull();
});
it('shows p2p settings in browsers only when pure p2p is enabled or active', () => {
expect(shouldShowP2PSettingsSection(undefined, browserWindow)).toBe(false);
expect(shouldShowP2PSettingsSection({ pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } }, browserWindow)).toBe(false);
expect(isBrowserPureP2PEnabled({ pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } }, browserWindow)).toBe(false);
});
it('builds browser p2p and gateway account options without a direct pkc-js import', () => {
const account = {
pkcOptions: {
httpRoutersOptions: ['https://custom-router.example'],
ipfsGatewayUrls: ['https://gateway.example'],
pkcRpcClientsOptions: ['ws://remote.example'],
},
};
expect(getBrowserPureP2PAccountOptions(account)).toMatchObject({
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
ipfsGatewayUrls: undefined,
pkcRpcClientsOptions: undefined,
});
expect(getBrowserGatewayAccountOptions(account)).toMatchObject({
ipfsGatewayUrls: ['https://ipfsgateway.xyz', 'https://gateway.plebpubsub.xyz', 'https://gateway.forumindex.com'],
libp2pJsClientsOptions: undefined,
pkcRpcClientsOptions: undefined,
});
});
});
+64 -3
View File
@@ -1,24 +1,85 @@
export const PURE_P2P_BROWSER_SETTING_KEY = '5chan:pure-p2p-browser-enabled';
export const P2P_BROWSER_PKC_OPTIONS = {
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
ipfsGatewayUrls: undefined,
kuboRpcClientsOptions: undefined,
pubsubHttpClientsOptions: undefined,
pubsubKuboRpcClientsOptions: undefined,
httpRoutersOptions: ['https://peers.pleb.bot', 'https://peers.forumindex.com'],
};
const GATEWAY_BROWSER_PKC_OPTIONS = {
ipfsGatewayUrls: ['https://ipfsgateway.xyz', 'https://gateway.plebpubsub.xyz', 'https://gateway.forumindex.com'],
kuboRpcClientsOptions: undefined,
libp2pJsClientsOptions: undefined,
pubsubHttpClientsOptions: undefined,
pubsubKuboRpcClientsOptions: ['https://pubsubprovider.xyz/api/v0', 'https://plebpubsub.xyz/api/v0', 'https://rannithepleb.com/api/v0'],
httpRoutersOptions: ['https://routing.lol', 'https://peers.pleb.bot', 'https://peers.plebpubsub.xyz', 'https://peers.forumindex.com'],
};
type P2PBrowserConfigWindow = {
location: Pick<Location, 'hostname'>;
defaultPkcOptions?: Record<string, unknown>;
electronApi?: { isElectron?: boolean };
isElectron?: boolean;
localStorage?: Pick<Storage, 'getItem' | 'setItem'>;
};
export const isP2PBrowserHostname = (hostname: string) => hostname.toLowerCase().startsWith('p2p.');
export const getBrowserPureP2PPkcOptions = () => ({
...P2P_BROWSER_PKC_OPTIONS,
libp2pJsClientsOptions: P2P_BROWSER_PKC_OPTIONS.libp2pJsClientsOptions.map((options) => ({ ...options })),
httpRoutersOptions: [...P2P_BROWSER_PKC_OPTIONS.httpRoutersOptions],
});
export const getBrowserGatewayPkcOptions = () => ({
...GATEWAY_BROWSER_PKC_OPTIONS,
ipfsGatewayUrls: [...GATEWAY_BROWSER_PKC_OPTIONS.ipfsGatewayUrls],
pubsubKuboRpcClientsOptions: [...GATEWAY_BROWSER_PKC_OPTIONS.pubsubKuboRpcClientsOptions],
httpRoutersOptions: [...GATEWAY_BROWSER_PKC_OPTIONS.httpRoutersOptions],
});
export const getPureP2PBrowserPreference = (targetWindow: P2PBrowserConfigWindow = window) => {
try {
const storedValue = targetWindow.localStorage?.getItem(PURE_P2P_BROWSER_SETTING_KEY);
if (storedValue === 'true') return true;
if (storedValue === 'false') return false;
} catch {
return undefined;
}
return undefined;
};
export const setPureP2PBrowserPreference = (enabled: boolean, targetWindow: P2PBrowserConfigWindow = window) => {
try {
targetWindow.localStorage?.setItem(PURE_P2P_BROWSER_SETTING_KEY, String(enabled));
} catch {
return;
}
};
export const isElectronRuntime = (targetWindow: P2PBrowserConfigWindow = window) => targetWindow.electronApi?.isElectron === true || targetWindow.isElectron === true;
export const shouldUsePureP2PBrowser = (targetWindow: P2PBrowserConfigWindow = window) => {
if (isElectronRuntime(targetWindow)) return false;
const preference = getPureP2PBrowserPreference(targetWindow);
if (preference !== undefined) return preference;
return false;
};
export const configureP2PBrowserPkcOptions = (targetWindow: P2PBrowserConfigWindow = window) => {
if (!isP2PBrowserHostname(targetWindow.location.hostname)) {
if (!shouldUsePureP2PBrowser(targetWindow)) {
return false;
}
targetWindow.defaultPkcOptions = {
...targetWindow.defaultPkcOptions,
libp2pJsClientsOptions: P2P_BROWSER_PKC_OPTIONS.libp2pJsClientsOptions.map((options) => ({ ...options })),
httpRoutersOptions: [...P2P_BROWSER_PKC_OPTIONS.httpRoutersOptions],
...getBrowserPureP2PPkcOptions(),
};
return true;
+72
View File
@@ -0,0 +1,72 @@
import { getBrowserGatewayPkcOptions, getBrowserPureP2PPkcOptions, isElectronRuntime, shouldUsePureP2PBrowser } from './p2p-browser-config';
export const P2P_STATS_SECTION_ID = 'p2p-stats-settings';
export type P2PRuntimeMode = 'browser-libp2p' | 'electron-kubo-rpc';
type AccountProtocolOptions = {
httpRoutersOptions?: string[];
ipfsGatewayUrls?: string[];
kuboRpcClientsOptions?: unknown[];
libp2pJsClientsOptions?: unknown[];
pkcRpcClientsOptions?: string[];
pubsubHttpClientsOptions?: unknown[];
pubsubKuboRpcClientsOptions?: unknown[];
};
type AccountShape = {
pkc?: {
clients?: {
libp2pJsClients?: Record<string, unknown>;
pkcRpcClients?: Record<string, unknown>;
};
};
pkcOptions?: AccountProtocolOptions;
};
const toAccountShape = (account: unknown) => account as AccountShape | undefined;
const hasArrayItems = (value: unknown) => Array.isArray(value) && value.length > 0;
const hasObjectItems = (value: unknown) => !!value && typeof value === 'object' && Object.keys(value).length > 0;
export const getP2PRuntimeMode = (account?: unknown, targetWindow: Window = window): P2PRuntimeMode | null => {
const accountShape = toAccountShape(account);
const protocolOptions = accountShape?.pkcOptions;
const clients = accountShape?.pkc?.clients;
if (hasArrayItems(protocolOptions?.libp2pJsClientsOptions) || hasObjectItems(clients?.libp2pJsClients)) {
return 'browser-libp2p';
}
if (isElectronRuntime(targetWindow) && (hasArrayItems(protocolOptions?.pkcRpcClientsOptions) || hasObjectItems(clients?.pkcRpcClients))) {
return 'electron-kubo-rpc';
}
return null;
};
export const canConfigureBrowserPureP2P = (targetWindow: Window = window) => !isElectronRuntime(targetWindow);
export const shouldShowP2PSettingsSection = (account?: unknown, targetWindow: Window = window) =>
getP2PRuntimeMode(account, targetWindow) !== null || (canConfigureBrowserPureP2P(targetWindow) && isBrowserPureP2PEnabled(account, targetWindow));
export const isBrowserPureP2PEnabled = (account?: unknown, targetWindow: Window = window) => {
const accountShape = toAccountShape(account);
if (!canConfigureBrowserPureP2P(targetWindow)) return false;
if (getP2PRuntimeMode(account, targetWindow) === 'browser-libp2p') return true;
if (hasArrayItems(accountShape?.pkcOptions?.ipfsGatewayUrls) || hasArrayItems(accountShape?.pkcOptions?.pubsubKuboRpcClientsOptions)) return false;
return shouldUsePureP2PBrowser(targetWindow);
};
export const getBrowserPureP2PAccountOptions = (account?: unknown) => ({
...toAccountShape(account)?.pkcOptions,
...getBrowserPureP2PPkcOptions(),
pkcRpcClientsOptions: undefined,
});
export const getBrowserGatewayAccountOptions = (account?: unknown) => ({
...toAccountShape(account)?.pkcOptions,
...getBrowserGatewayPkcOptions(),
pkcRpcClientsOptions: undefined,
});