diff --git a/package-lock.json b/package-lock.json index 3bc6802..7a32cad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "qortium-node", - "version": "1.4.3", + "version": "1.4.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "qortium-node", - "version": "1.4.3", + "version": "1.4.4", "license": "0BSD", "dependencies": { "@vitejs/plugin-react": "^6.0.4", diff --git a/package.json b/package.json index c7ae93d..fecc2f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qortium-node", - "version": "1.4.3", + "version": "1.4.4", "private": true, "license": "0BSD", "description": "A QDN app for inspecting a Qortium node.", diff --git a/src/settingsEditor.test.ts b/src/settingsEditor.test.ts index 36091b4..16d99a4 100644 --- a/src/settingsEditor.test.ts +++ b/src/settingsEditor.test.ts @@ -27,6 +27,7 @@ describe('settings editor helpers', () => { minPeerVersion: { restartRequired: true, type: 'PEER_VERSION' }, maxStorageCapacity: { restartRequired: false, type: 'LONG' }, storagePolicy: { restartRequired: false, type: 'STORAGE_POLICY' }, + uPnPEnabled: { restartRequired: true, type: 'BOOLEAN' }, }, }; @@ -40,6 +41,8 @@ describe('settings editor helpers', () => { expect(normalizeEditableSettingValue('autoUpdateMode', 'NOTIFY')).toBe('NOTIFY'); expect(normalizeEditableSettingValue('autoUpdateMode', 'BAD')).toBe('OFF'); expect(normalizeEditableSettingValue('autoRestartEnabled', undefined)).toBe(false); + expect(normalizeEditableSettingValue('uPnPEnabled', undefined)).toBe(true); + expect(normalizeEditableSettingValue('uPnPEnabled', false)).toBe(false); expect(normalizeEditableSettingValue('listenPort', '25000')).toBe(25000); expect(normalizeEditableSettingValue('listenPort', '65536')).toBe(null); expect(normalizeEditableSettingValue('minOutboundPeers', '0')).toBe(0); @@ -53,12 +56,16 @@ describe('settings editor helpers', () => { expect(normalizeEditableSettingValue('maxStorageCapacity', '123.5')).toBe(null); }); - it('maps transport dropdown selections to ordered Core transport arrays', () => { + it('maps transport capability selections regardless of Core array order', () => { expect(getTransportSelectionValue(['IP'])).toBe('IP'); expect(getTransportSelectionValue(['IP', 'I2P'])).toBe('IP+I2P'); - expect(getTransportSelectionValue(['I2P', 'IP'])).toBe('I2P+IP'); + // Legacy I2P-preferred ordering still selects the combined option + expect(getTransportSelectionValue(['I2P', 'IP'])).toBe('IP+I2P'); expect(getTransportSelectionValue(['I2P'])).toBe('I2P'); - expect(getTransportSelectionTransports('I2P+IP')).toEqual(['I2P', 'IP']); + expect(getTransportSelectionTransports('IP+I2P')).toEqual(['IP', 'I2P']); + expect(getTransportSelectionTransports('I2P')).toEqual(['I2P']); + // Retired ordering variant falls back to the combined option + expect(getTransportSelectionTransports('I2P+IP')).toEqual(['IP', 'I2P']); }); it('formats and parses user-facing storage and chat retention units', () => { @@ -127,6 +134,9 @@ describe('settings editor helpers', () => { expect(canEditSetting('minDataPeers', metadata, true, false)).toBe(true); expect(canEditSetting('minPeerVersion', metadata, true, false)).toBe(true); expect(canEditSetting('maxStorageCapacity', metadata, true, false)).toBe(true); + expect(canEditSetting('uPnPEnabled', metadata, true, false)).toBe(true); + // Older Cores without uPnPEnabled in writable metadata stay read-only + expect(canEditSetting('uPnPEnabled', { writable: {} }, true, false)).toBe(false); expect(canEditSetting('storagePolicy', metadata, false, false)).toBe(false); expect(canEditSetting('storagePolicy', metadata, true, true)).toBe(false); expect(canEditSetting('qdnEnabled', metadata, true, false)).toBe(false); @@ -138,5 +148,6 @@ describe('settings editor helpers', () => { it('detects restart-required settings in a patch', () => { expect(patchHasRestartRequiredSettings({ storagePolicy: 'NONE' }, metadata)).toBe(false); expect(patchHasRestartRequiredSettings({ allowedTransports: ['IP'] }, metadata)).toBe(true); + expect(patchHasRestartRequiredSettings({ uPnPEnabled: false }, metadata)).toBe(true); }); }); diff --git a/src/settingsEditor.ts b/src/settingsEditor.ts index c377ca8..668a8eb 100644 --- a/src/settingsEditor.ts +++ b/src/settingsEditor.ts @@ -9,11 +9,14 @@ export const STORAGE_POLICY_OPTIONS = [ ] as const; export const TRANSPORT_OPTIONS = ['IP', 'I2P'] as const; +// Capability set only: Core treats transport order as a soft preference with +// reserved dial slots for the other transport (core PR #191), so the old +// "IP + I2P" vs "I2P + IP" ordering choice no longer changes behaviour enough +// to be worth the confusion. export const TRANSPORT_SELECTION_OPTIONS = [ - { label: 'IP', transports: ['IP'] }, + { label: 'IP only', transports: ['IP'] }, { label: 'IP + I2P', transports: ['IP', 'I2P'] }, - { label: 'I2P + IP', transports: ['I2P', 'IP'] }, - { label: 'I2P', transports: ['I2P'] }, + { label: 'I2P only', transports: ['I2P'] }, ] as const; export const AUTO_UPDATE_MODE_OPTIONS = ['OFF', 'CHECK_ONLY', 'NOTIFY', 'INSTALL'] as const; export const STORAGE_CAPACITY_GIGABYTE_BYTES = 1_000_000_000; @@ -47,6 +50,7 @@ export const PHASE_1_EDITABLE_SETTING_KEYS = [ 'minDataPeers', 'minPeerVersion', 'allowConnectionsWithOlderPeerVersions', + 'uPnPEnabled', 'qdnEnabled', 'storagePolicy', 'maxStorageCapacity', @@ -89,6 +93,7 @@ const BOOLEAN_DEFAULTS: Record = { publicDataEnabled: true, qdnEnabled: true, qdnPushOnPublishEnabled: true, + uPnPEnabled: true, }; export function isPhase1EditableSetting(key: string): key is Phase1EditableSettingKey { @@ -180,8 +185,12 @@ export function patchHasRestartRequiredSettings(patch: SettingsPatch, metadata: } export function getTransportSelectionValue(value: unknown) { + // Order-insensitive: ['I2P', 'IP'] (the legacy I2P-preferred ordering) still + // selects the combined option instead of falling back. const transports = normalizeTransportList(value); - const serializedTransports = transports.join('+'); + const serializedTransports = [...TRANSPORT_OPTIONS] + .filter((transport) => transports.includes(transport)) + .join('+'); return TRANSPORT_SELECTION_OPTIONS.some((option) => option.transports.join('+') === serializedTransports) ? serializedTransports diff --git a/src/settingsView.test.ts b/src/settingsView.test.ts index 9cf2969..2874fd3 100644 --- a/src/settingsView.test.ts +++ b/src/settingsView.test.ts @@ -63,6 +63,7 @@ describe('settings view helpers', () => { publicDataEnabled: true, qdnEnabled: true, storagePolicy: 'FOLLOWED_OR_VIEWED', + uPnPEnabled: true, zUnknownSetting: true, }, ); @@ -81,6 +82,7 @@ describe('settings view helpers', () => { 'minDataPeers', 'minPeerVersion', 'allowConnectionsWithOlderPeerVersions', + 'uPnPEnabled', 'qdnEnabled', 'storagePolicy', 'maxStorageCapacity',