feat(p2p): enable pure browser p2p by default

This commit is contained in:
Tommaso Casaburi
2026-05-22 17:31:28 +07:00
parent 99eb49cdc5
commit ca13e97dd4
7 changed files with 55 additions and 65 deletions
@@ -211,6 +211,8 @@ describe('AdvancedSettings', () => {
}); });
it('saves the browser pure p2p toggle through advanced settings', async () => { it('saves the browser pure p2p toggle through advanced settings', async () => {
localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false');
await renderSettings(false); await renderSettings(false);
const checkbox = container.querySelector<HTMLInputElement>('input[type="checkbox"]'); const checkbox = container.querySelector<HTMLInputElement>('input[type="checkbox"]');
@@ -239,29 +241,29 @@ describe('AdvancedSettings', () => {
expect(reloadMock).toHaveBeenCalledOnce(); expect(reloadMock).toHaveBeenCalledOnce();
}); });
it('forces the browser pure p2p toggle on p2p subdomains', async () => { it('allows browser pure p2p to be disabled on p2p subdomains', async () => {
localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false'); localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false');
setTestHostname('p2p.5chan.app'); setTestHostname('p2p.5chan.app');
await renderSettings(false); await renderSettings(false);
const checkbox = container.querySelector<HTMLInputElement>('input[type="checkbox"]'); const checkbox = container.querySelector<HTMLInputElement>('input[type="checkbox"]');
expect(checkbox?.checked).toBe(true); expect(checkbox?.checked).toBe(false);
expect(checkbox?.disabled).toBe(true); expect(checkbox?.disabled).toBe(false);
await clickButton('save_advanced_settings'); await clickButton('save_advanced_settings');
expect(testState.setAccountMock).toHaveBeenCalledWith( expect(testState.setAccountMock).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
pkcOptions: expect.objectContaining({ pkcOptions: expect.objectContaining({
ipfsGatewayUrls: undefined, ipfsGatewayUrls: ['https://ipfs.old.example'],
libp2pJsClientsOptions: [{ key: 'libp2pjs' }], libp2pJsClientsOptions: undefined,
pkcRpcClientsOptions: undefined, pkcRpcClientsOptions: ['ws://old.example/key'],
pubsubKuboRpcClientsOptions: undefined, pubsubKuboRpcClientsOptions: ['https://pubsub.old.example'],
}), }),
}), }),
); );
expect(localStorage.getItem('5chan:pure-p2p-browser-enabled')).toBe('true'); expect(localStorage.getItem('5chan:pure-p2p-browser-enabled')).toBe('false');
}); });
it('saves gateway mode defaults when browser pure p2p is disabled', async () => { it('saves gateway mode defaults when browser pure p2p is disabled', async () => {
@@ -1,7 +1,7 @@
import { memo, RefObject, useRef, useState } from 'react'; import { memo, RefObject, useRef, useState } from 'react';
import { setAccount, useAccount, usePkcRpcSettings } from '@bitsocial/bitsocial-react-hooks'; import { setAccount, useAccount, usePkcRpcSettings } from '@bitsocial/bitsocial-react-hooks';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { getBrowserGatewayPkcOptions, getBrowserPureP2PPkcOptions, isPureP2PBrowserForced, setPureP2PBrowserPreference } from '../../../lib/p2p-browser-config'; import { getBrowserGatewayPkcOptions, getBrowserPureP2PPkcOptions, setPureP2PBrowserPreference } from '../../../lib/p2p-browser-config';
import { canConfigureBrowserPureP2P, isBrowserPureP2PEnabled } from '../../../lib/p2p-runtime'; import { canConfigureBrowserPureP2P, isBrowserPureP2PEnabled } from '../../../lib/p2p-runtime';
import styles from './advanced-settings.module.css'; import styles from './advanced-settings.module.css';
@@ -205,18 +205,11 @@ const P2pDataPathSettings = ({ p2pDataPathRef }: SettingsProps) => {
const PureP2PBrowserSettings = ({ pureP2PBrowserRef }: SettingsProps) => { const PureP2PBrowserSettings = ({ pureP2PBrowserRef }: SettingsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const account = useAccount() as AccountShape | undefined; const account = useAccount() as AccountShape | undefined;
const isForced = isPureP2PBrowserForced();
return ( return (
<div className={styles.pureP2PSettings}> <div className={styles.pureP2PSettings}>
<label> <label>
<input <input className={styles.pureP2PCheckbox} type='checkbox' defaultChecked={isBrowserPureP2PEnabled(account)} ref={pureP2PBrowserRef} />
className={styles.pureP2PCheckbox}
type='checkbox'
defaultChecked={isForced || isBrowserPureP2PEnabled(account)}
disabled={isForced}
ref={pureP2PBrowserRef}
/>
{t('enable_pure_p2p')} {t('enable_pure_p2p')}
</label> </label>
<div className={styles.settingTip}>{t('enable_pure_p2p_tip')}</div> <div className={styles.settingTip}>{t('enable_pure_p2p_tip')}</div>
@@ -261,7 +254,7 @@ const AdvancedSettings = () => {
const pkcRpcClientsOptions = p2pRpcRef.current?.value.trim() ? [p2pRpcRef.current.value.trim()] : undefined; const pkcRpcClientsOptions = p2pRpcRef.current?.value.trim() ? [p2pRpcRef.current.value.trim()] : undefined;
const dataPath = p2pDataPathRef.current?.value.trim() || undefined; const dataPath = p2pDataPathRef.current?.value.trim() || undefined;
const pureP2PBrowserPreference = canConfigureBrowserPureP2P() ? isPureP2PBrowserForced() || pureP2PBrowserRef.current?.checked : undefined; const pureP2PBrowserPreference = canConfigureBrowserPureP2P() ? pureP2PBrowserRef.current?.checked : undefined;
const chainProviders: Record<string, { urls: string[] | undefined; chainId: number }> = {}; const chainProviders: Record<string, { urls: string[] | undefined; chainId: number }> = {};
if (ethRpcUrls && ethRpcUrls.length > 0) { if (ethRpcUrls && ethRpcUrls.length > 0) {
@@ -113,6 +113,8 @@ describe('use-state-string', () => {
}); });
it('falls back to publishing and updating states when no client states are available', () => { it('falls back to publishing and updating states when no client states are available', () => {
localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false');
act(() => { act(() => {
root.render(createElement(StateStringHarness, { value: { publishingState: 'fetching-ipfs', state: 'publishing' } })); root.render(createElement(StateStringHarness, { value: { publishingState: 'fetching-ipfs', state: 'publishing' } }));
}); });
@@ -130,6 +132,7 @@ describe('use-state-string', () => {
}); });
it('formats raw community loading states when no client or update states are available', () => { it('formats raw community loading states when no client or update states are available', () => {
localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false');
testState.community = { testState.community = {
state: 'fetching-community-ipfs', state: 'fetching-community-ipfs',
}; };
@@ -156,6 +159,7 @@ describe('use-state-string', () => {
}); });
it('sanitizes single-board feed state strings to board wording', () => { it('sanitizes single-board feed state strings to board wording', () => {
localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false');
testState.community = { testState.community = {
state: 'updating', state: 'updating',
updatingState: 'fetching-ipfs', updatingState: 'fetching-ipfs',
+17 -28
View File
@@ -3,8 +3,6 @@ import { describe, expect, it } from 'vitest';
import { import {
configureP2PBrowserPkcOptions, configureP2PBrowserPkcOptions,
getPureP2PBrowserPreference, getPureP2PBrowserPreference,
isP2PBrowserHostname,
isPureP2PBrowserForced,
P2P_BROWSER_PKC_OPTIONS, P2P_BROWSER_PKC_OPTIONS,
PURE_P2P_BROWSER_SETTING_KEY, PURE_P2P_BROWSER_SETTING_KEY,
setPureP2PBrowserPreference, setPureP2PBrowserPreference,
@@ -19,14 +17,7 @@ const createStorage = (values: Record<string, string | undefined> = {}) => ({
}); });
describe('p2p-browser-config', () => { describe('p2p-browser-config', () => {
it('detects p2p subdomains', () => { it('configures browser PKC options for pure p2p by default', () => {
expect(isP2PBrowserHostname('p2p.5chan.app')).toBe(true);
expect(isP2PBrowserHostname('P2P.5chan.app')).toBe(true);
expect(isP2PBrowserHostname('5chan.app')).toBe(false);
expect(isP2PBrowserHostname('www.p2p.5chan.app')).toBe(false);
});
it('leaves browser PKC options untouched by default', () => {
const targetWindow = { const targetWindow = {
location: { hostname: '5chan.app' }, location: { hostname: '5chan.app' },
localStorage: createStorage(), localStorage: createStorage(),
@@ -35,27 +26,26 @@ describe('p2p-browser-config', () => {
}, },
}; };
expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(false);
expect(targetWindow.defaultPkcOptions).toEqual({
ipfsGatewayUrls: ['https://gateway.example'],
});
});
it('forces browser PKC options on p2p subdomains', () => {
const targetWindow = {
location: { hostname: 'p2p.5chan.app' },
localStorage: createStorage({ [PURE_P2P_BROWSER_SETTING_KEY]: 'false' }),
defaultPkcOptions: {
ipfsGatewayUrls: ['https://gateway.example'],
},
};
expect(isPureP2PBrowserForced(targetWindow)).toBe(true);
expect(shouldUsePureP2PBrowser(targetWindow)).toBe(true); expect(shouldUsePureP2PBrowser(targetWindow)).toBe(true);
expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(true); expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(true);
expect(targetWindow.defaultPkcOptions).toEqual(P2P_BROWSER_PKC_OPTIONS); expect(targetWindow.defaultPkcOptions).toEqual(P2P_BROWSER_PKC_OPTIONS);
}); });
it('respects disabled pure p2p preference on p2p subdomains', () => {
const defaultPkcOptions = {
ipfsGatewayUrls: ['https://gateway.example'],
};
const targetWindow = {
location: { hostname: 'p2p.5chan.app' },
localStorage: createStorage({ [PURE_P2P_BROWSER_SETTING_KEY]: 'false' }),
defaultPkcOptions,
};
expect(shouldUsePureP2PBrowser(targetWindow)).toBe(false);
expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(false);
expect(targetWindow.defaultPkcOptions).toBe(defaultPkcOptions);
});
it('configures browser PKC options when pure p2p is enabled', () => { it('configures browser PKC options when pure p2p is enabled', () => {
const targetWindow = { const targetWindow = {
location: { hostname: '5chan.app' }, location: { hostname: '5chan.app' },
@@ -96,7 +86,6 @@ describe('p2p-browser-config', () => {
expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(false); expect(configureP2PBrowserPkcOptions(targetWindow)).toBe(false);
expect(targetWindow.defaultPkcOptions).toBe(defaultPkcOptions); expect(targetWindow.defaultPkcOptions).toBe(defaultPkcOptions);
expect(isPureP2PBrowserForced({ ...targetWindow, location: { hostname: 'p2p.5chan.app' } })).toBe(false);
}); });
it('persists and reads the browser pure p2p preference', () => { it('persists and reads the browser pure p2p preference', () => {
@@ -106,7 +95,7 @@ describe('p2p-browser-config', () => {
}; };
expect(getPureP2PBrowserPreference(targetWindow)).toBeUndefined(); expect(getPureP2PBrowserPreference(targetWindow)).toBeUndefined();
expect(shouldUsePureP2PBrowser(targetWindow)).toBe(false); expect(shouldUsePureP2PBrowser(targetWindow)).toBe(true);
setPureP2PBrowserPreference(false, targetWindow); setPureP2PBrowserPreference(false, targetWindow);
expect(getPureP2PBrowserPreference(targetWindow)).toBe(false); expect(getPureP2PBrowserPreference(targetWindow)).toBe(false);
+19 -8
View File
@@ -17,13 +17,23 @@ const browserWindow = {
}, },
} as unknown as Window; } as unknown as Window;
const browserWindowWithDisabledPureP2P = {
electronApi: undefined,
isElectron: false,
location: { hostname: '5chan.app' },
localStorage: {
getItem: () => 'false',
setItem: () => undefined,
},
} as unknown as Window;
const electronWindow = { const electronWindow = {
electronApi: { isElectron: true }, electronApi: { isElectron: true },
isElectron: true, isElectron: true,
location: { hostname: 'localhost' }, location: { hostname: 'localhost' },
} as unknown as Window; } as unknown as Window;
const p2pBrowserWindow = { const p2pBrowserWindowWithDisabledPureP2P = {
electronApi: undefined, electronApi: undefined,
isElectron: false, isElectron: false,
location: { hostname: 'p2p.5chan.app' }, location: { hostname: 'p2p.5chan.app' },
@@ -46,17 +56,18 @@ describe('p2p-runtime', () => {
expect(getP2PRuntimeMode(account, browserWindow)).toBeNull(); expect(getP2PRuntimeMode(account, browserWindow)).toBeNull();
}); });
it('shows p2p settings in browsers only when pure p2p is enabled or active', () => { it('shows p2p settings in browsers when pure p2p is enabled by default', () => {
expect(shouldShowP2PSettingsSection(undefined, browserWindow)).toBe(false); expect(shouldShowP2PSettingsSection(undefined, browserWindow)).toBe(true);
expect(shouldShowP2PSettingsSection({ pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } }, browserWindow)).toBe(false); expect(shouldShowP2PSettingsSection({ pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } }, browserWindow)).toBe(true);
expect(isBrowserPureP2PEnabled({ pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } }, browserWindow)).toBe(false); expect(isBrowserPureP2PEnabled({ pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } }, browserWindow)).toBe(true);
}); });
it('forces browser p2p on p2p subdomains even with gateway account options', () => { it('allows browser gateway mode when pure p2p is disabled', () => {
const gatewayAccount = { pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } }; const gatewayAccount = { pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } };
expect(isBrowserPureP2PEnabled(gatewayAccount, p2pBrowserWindow)).toBe(true); expect(isBrowserPureP2PEnabled(gatewayAccount, browserWindowWithDisabledPureP2P)).toBe(false);
expect(shouldShowP2PSettingsSection(gatewayAccount, p2pBrowserWindow)).toBe(true); expect(shouldShowP2PSettingsSection(gatewayAccount, browserWindowWithDisabledPureP2P)).toBe(false);
expect(isBrowserPureP2PEnabled(gatewayAccount, p2pBrowserWindowWithDisabledPureP2P)).toBe(false);
}); });
it('builds browser p2p and gateway account options without a direct pkc-js import', () => { it('builds browser p2p and gateway account options without a direct pkc-js import', () => {
+1 -7
View File
@@ -26,8 +26,6 @@ type P2PBrowserConfigWindow = {
localStorage?: Pick<Storage, 'getItem' | 'setItem'>; localStorage?: Pick<Storage, 'getItem' | 'setItem'>;
}; };
export const isP2PBrowserHostname = (hostname: string) => hostname.toLowerCase().startsWith('p2p.');
export const getBrowserPureP2PPkcOptions = () => ({ export const getBrowserPureP2PPkcOptions = () => ({
...P2P_BROWSER_PKC_OPTIONS, ...P2P_BROWSER_PKC_OPTIONS,
libp2pJsClientsOptions: P2P_BROWSER_PKC_OPTIONS.libp2pJsClientsOptions.map((options) => ({ ...options })), libp2pJsClientsOptions: P2P_BROWSER_PKC_OPTIONS.libp2pJsClientsOptions.map((options) => ({ ...options })),
@@ -63,17 +61,13 @@ export const setPureP2PBrowserPreference = (enabled: boolean, targetWindow: P2PB
export const isElectronRuntime = (targetWindow: P2PBrowserConfigWindow = window) => targetWindow.electronApi?.isElectron === true || targetWindow.isElectron === true; export const isElectronRuntime = (targetWindow: P2PBrowserConfigWindow = window) => targetWindow.electronApi?.isElectron === true || targetWindow.isElectron === true;
export const isPureP2PBrowserForced = (targetWindow: P2PBrowserConfigWindow = window) =>
!isElectronRuntime(targetWindow) && isP2PBrowserHostname(targetWindow.location?.hostname ?? '');
export const shouldUsePureP2PBrowser = (targetWindow: P2PBrowserConfigWindow = window) => { export const shouldUsePureP2PBrowser = (targetWindow: P2PBrowserConfigWindow = window) => {
if (isElectronRuntime(targetWindow)) return false; if (isElectronRuntime(targetWindow)) return false;
if (isPureP2PBrowserForced(targetWindow)) return true;
const preference = getPureP2PBrowserPreference(targetWindow); const preference = getPureP2PBrowserPreference(targetWindow);
if (preference !== undefined) return preference; if (preference !== undefined) return preference;
return false; return true;
}; };
export const configureP2PBrowserPkcOptions = (targetWindow: P2PBrowserConfigWindow = window) => { export const configureP2PBrowserPkcOptions = (targetWindow: P2PBrowserConfigWindow = window) => {
+1 -4
View File
@@ -1,4 +1,4 @@
import { getBrowserGatewayPkcOptions, getBrowserPureP2PPkcOptions, isElectronRuntime, isPureP2PBrowserForced, shouldUsePureP2PBrowser } from './p2p-browser-config'; import { getBrowserGatewayPkcOptions, getBrowserPureP2PPkcOptions, isElectronRuntime, shouldUsePureP2PBrowser } from './p2p-browser-config';
export const P2P_STATS_SECTION_ID = 'p2p-stats-settings'; export const P2P_STATS_SECTION_ID = 'p2p-stats-settings';
@@ -52,11 +52,8 @@ export const shouldShowP2PSettingsSection = (account?: unknown, targetWindow: Wi
getP2PRuntimeMode(account, targetWindow) !== null || (canConfigureBrowserPureP2P(targetWindow) && isBrowserPureP2PEnabled(account, targetWindow)); getP2PRuntimeMode(account, targetWindow) !== null || (canConfigureBrowserPureP2P(targetWindow) && isBrowserPureP2PEnabled(account, targetWindow));
export const isBrowserPureP2PEnabled = (account?: unknown, targetWindow: Window = window) => { export const isBrowserPureP2PEnabled = (account?: unknown, targetWindow: Window = window) => {
const accountShape = toAccountShape(account);
if (!canConfigureBrowserPureP2P(targetWindow)) return false; if (!canConfigureBrowserPureP2P(targetWindow)) return false;
if (getP2PRuntimeMode(account, targetWindow) === 'browser-libp2p') return true; if (getP2PRuntimeMode(account, targetWindow) === 'browser-libp2p') return true;
if (isPureP2PBrowserForced(targetWindow)) return true;
if (hasArrayItems(accountShape?.pkcOptions?.ipfsGatewayUrls) || hasArrayItems(accountShape?.pkcOptions?.pubsubKuboRpcClientsOptions)) return false;
return shouldUsePureP2PBrowser(targetWindow); return shouldUsePureP2PBrowser(targetWindow);
}; };