mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(p2p): force browser mode on p2p subdomains
This commit is contained in:
@@ -87,6 +87,17 @@ const getSaveAdvancedSettingsButton = () => {
|
|||||||
return button as HTMLButtonElement;
|
return button as HTMLButtonElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setTestHostname = (hostname: string) => {
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
...originalLocation,
|
||||||
|
hostname,
|
||||||
|
reload: reloadMock,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
describe('AdvancedSettings', () => {
|
describe('AdvancedSettings', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -120,6 +131,7 @@ describe('AdvancedSettings', () => {
|
|||||||
configurable: true,
|
configurable: true,
|
||||||
value: {
|
value: {
|
||||||
...originalLocation,
|
...originalLocation,
|
||||||
|
hostname: '5chan.app',
|
||||||
reload: reloadMock,
|
reload: reloadMock,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -227,6 +239,31 @@ describe('AdvancedSettings', () => {
|
|||||||
expect(reloadMock).toHaveBeenCalledOnce();
|
expect(reloadMock).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('forces the browser pure p2p toggle on p2p subdomains', async () => {
|
||||||
|
localStorage.setItem('5chan:pure-p2p-browser-enabled', 'false');
|
||||||
|
setTestHostname('p2p.5chan.app');
|
||||||
|
|
||||||
|
await renderSettings(false);
|
||||||
|
|
||||||
|
const checkbox = container.querySelector<HTMLInputElement>('input[type="checkbox"]');
|
||||||
|
expect(checkbox?.checked).toBe(true);
|
||||||
|
expect(checkbox?.disabled).toBe(true);
|
||||||
|
|
||||||
|
await clickButton('save_advanced_settings');
|
||||||
|
|
||||||
|
expect(testState.setAccountMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
pkcOptions: expect.objectContaining({
|
||||||
|
ipfsGatewayUrls: undefined,
|
||||||
|
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
|
||||||
|
pkcRpcClientsOptions: undefined,
|
||||||
|
pubsubKuboRpcClientsOptions: undefined,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(localStorage.getItem('5chan:pure-p2p-browser-enabled')).toBe('true');
|
||||||
|
});
|
||||||
|
|
||||||
it('saves gateway mode defaults when browser pure p2p is disabled', async () => {
|
it('saves gateway mode defaults when browser pure p2p is disabled', async () => {
|
||||||
testState.account = {
|
testState.account = {
|
||||||
mediaIpfsGatewayUrl: 'https://media.old.example',
|
mediaIpfsGatewayUrl: 'https://media.old.example',
|
||||||
|
|||||||
@@ -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, setPureP2PBrowserPreference } from '../../../lib/p2p-browser-config';
|
import { getBrowserGatewayPkcOptions, getBrowserPureP2PPkcOptions, isPureP2PBrowserForced, 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,11 +205,18 @@ 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 className={styles.pureP2PCheckbox} type='checkbox' defaultChecked={isBrowserPureP2PEnabled(account)} ref={pureP2PBrowserRef} />
|
<input
|
||||||
|
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>
|
||||||
@@ -254,7 +261,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() ? pureP2PBrowserRef.current?.checked : undefined;
|
const pureP2PBrowserPreference = canConfigureBrowserPureP2P() ? isPureP2PBrowserForced() || 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) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
configureP2PBrowserPkcOptions,
|
configureP2PBrowserPkcOptions,
|
||||||
getPureP2PBrowserPreference,
|
getPureP2PBrowserPreference,
|
||||||
isP2PBrowserHostname,
|
isP2PBrowserHostname,
|
||||||
|
isPureP2PBrowserForced,
|
||||||
P2P_BROWSER_PKC_OPTIONS,
|
P2P_BROWSER_PKC_OPTIONS,
|
||||||
PURE_P2P_BROWSER_SETTING_KEY,
|
PURE_P2P_BROWSER_SETTING_KEY,
|
||||||
setPureP2PBrowserPreference,
|
setPureP2PBrowserPreference,
|
||||||
@@ -40,6 +41,21 @@ describe('p2p-browser-config', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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(configureP2PBrowserPkcOptions(targetWindow)).toBe(true);
|
||||||
|
expect(targetWindow.defaultPkcOptions).toEqual(P2P_BROWSER_PKC_OPTIONS);
|
||||||
|
});
|
||||||
|
|
||||||
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' },
|
||||||
@@ -80,6 +96,7 @@ 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', () => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
const browserWindow = {
|
const browserWindow = {
|
||||||
electronApi: undefined,
|
electronApi: undefined,
|
||||||
isElectron: false,
|
isElectron: false,
|
||||||
|
location: { hostname: '5chan.app' },
|
||||||
localStorage: {
|
localStorage: {
|
||||||
getItem: () => null,
|
getItem: () => null,
|
||||||
setItem: () => undefined,
|
setItem: () => undefined,
|
||||||
@@ -19,6 +20,17 @@ const browserWindow = {
|
|||||||
const electronWindow = {
|
const electronWindow = {
|
||||||
electronApi: { isElectron: true },
|
electronApi: { isElectron: true },
|
||||||
isElectron: true,
|
isElectron: true,
|
||||||
|
location: { hostname: 'localhost' },
|
||||||
|
} as unknown as Window;
|
||||||
|
|
||||||
|
const p2pBrowserWindow = {
|
||||||
|
electronApi: undefined,
|
||||||
|
isElectron: false,
|
||||||
|
location: { hostname: 'p2p.5chan.app' },
|
||||||
|
localStorage: {
|
||||||
|
getItem: () => 'false',
|
||||||
|
setItem: () => undefined,
|
||||||
|
},
|
||||||
} as unknown as Window;
|
} as unknown as Window;
|
||||||
|
|
||||||
describe('p2p-runtime', () => {
|
describe('p2p-runtime', () => {
|
||||||
@@ -40,6 +52,13 @@ describe('p2p-runtime', () => {
|
|||||||
expect(isBrowserPureP2PEnabled({ pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } }, browserWindow)).toBe(false);
|
expect(isBrowserPureP2PEnabled({ pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } }, browserWindow)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('forces browser p2p on p2p subdomains even with gateway account options', () => {
|
||||||
|
const gatewayAccount = { pkcOptions: { ipfsGatewayUrls: ['https://gateway.example'] } };
|
||||||
|
|
||||||
|
expect(isBrowserPureP2PEnabled(gatewayAccount, p2pBrowserWindow)).toBe(true);
|
||||||
|
expect(shouldShowP2PSettingsSection(gatewayAccount, p2pBrowserWindow)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
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', () => {
|
||||||
const account = {
|
const account = {
|
||||||
pkcOptions: {
|
pkcOptions: {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const GATEWAY_BROWSER_PKC_OPTIONS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type P2PBrowserConfigWindow = {
|
type P2PBrowserConfigWindow = {
|
||||||
location: Pick<Location, 'hostname'>;
|
location?: Pick<Location, 'hostname'>;
|
||||||
defaultPkcOptions?: Record<string, unknown>;
|
defaultPkcOptions?: Record<string, unknown>;
|
||||||
electronApi?: { isElectron?: boolean };
|
electronApi?: { isElectron?: boolean };
|
||||||
isElectron?: boolean;
|
isElectron?: boolean;
|
||||||
@@ -63,8 +63,12 @@ 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;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getBrowserGatewayPkcOptions, getBrowserPureP2PPkcOptions, isElectronRuntime, shouldUsePureP2PBrowser } from './p2p-browser-config';
|
import { getBrowserGatewayPkcOptions, getBrowserPureP2PPkcOptions, isElectronRuntime, isPureP2PBrowserForced, shouldUsePureP2PBrowser } from './p2p-browser-config';
|
||||||
|
|
||||||
export const P2P_STATS_SECTION_ID = 'p2p-stats-settings';
|
export const P2P_STATS_SECTION_ID = 'p2p-stats-settings';
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ export const isBrowserPureP2PEnabled = (account?: unknown, targetWindow: Window
|
|||||||
const accountShape = toAccountShape(account);
|
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;
|
if (hasArrayItems(accountShape?.pkcOptions?.ipfsGatewayUrls) || hasArrayItems(accountShape?.pkcOptions?.pubsubKuboRpcClientsOptions)) return false;
|
||||||
return shouldUsePureP2PBrowser(targetWindow);
|
return shouldUsePureP2PBrowser(targetWindow);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user