diff --git a/shared-libs/ts-modules/start-core/lib/test/util.filledAddress.test.ts b/shared-libs/ts-modules/start-core/lib/test/util.filledAddress.test.ts new file mode 100644 index 000000000..35305217c --- /dev/null +++ b/shared-libs/ts-modules/start-core/lib/test/util.filledAddress.test.ts @@ -0,0 +1,105 @@ +import { Host } from '../osBindings' +import { deepEqual } from '../util' +import { fillHost } from '../util/filledAddress' + +const host = (fingerprint: string): Host => ({ + bindings: { + 5223: { + enabled: true, + options: { preferredExternalPort: 5223, addSsl: null, secure: null }, + net: { assignedPort: null, assignedSslPort: 5223 }, + addresses: { + enabled: [], + disabled: [], + guaWan: [], + available: [ + { + ssl: true, + public: false, + hostname: 'relay.onion', + port: 5223, + metadata: { + kind: 'plugin', + packageId: 'tor', + removeAction: null, + overflowActions: [], + info: null, + }, + }, + { + ssl: true, + public: false, + hostname: 'relay.local', + port: 5223, + metadata: { kind: 'mdns', gateways: [] }, + }, + ], + }, + interfaces: { + smp: { + id: 'smp', + name: 'SMP', + description: '', + masked: true, + type: 'api', + addressInfo: { + username: fingerprint, + hostId: 'main', + internalPort: 5223, + scheme: 'smp', + sslScheme: 'smp', + suffix: '', + }, + }, + }, + }, + }, + bindingRanges: {}, + publicDomains: {}, + privateDomains: {}, + portForwards: [], +}) + +const addressOf = (h: Host) => + fillHost(h).bindings[5223].interfaces['smp'].addressInfo + +describe('fillHost', () => { + test('a filled host is deep-equal to itself', () => { + const filled = fillHost(host('AAAA=')) + expect(deepEqual(filled, filled)).toBe(true) + }) + + test('two fills of the same host are deep-equal', () => { + expect(deepEqual(fillHost(host('AAAA=')), fillHost(host('AAAA=')))).toBe( + true, + ) + }) + + test('a changed address is not deep-equal', () => { + expect(deepEqual(fillHost(host('AAAA=')), fillHost(host('BBBB=')))).toBe( + false, + ) + }) + + test('helpers are hidden from enumeration', () => { + expect(Object.keys(addressOf(host('AAAA=')))).toEqual([ + 'username', + 'hostId', + 'internalPort', + 'scheme', + 'sslScheme', + 'suffix', + 'hostnames', + ]) + }) + + test('helpers still resolve', () => { + const address = addressOf(host('AAAA=')) + expect(address.filter({ kind: 'plugin' }).format('urlstring')).toEqual([ + 'smp://AAAA=@relay.onion:5223', + ]) + expect(address.nonLocal.hostnames.map(h => h.hostname)).toEqual([ + 'relay.onion', + ]) + }) +}) diff --git a/shared-libs/ts-modules/start-core/lib/util/Watchable.ts b/shared-libs/ts-modules/start-core/lib/util/Watchable.ts index ea70f1f5b..d55c21607 100644 --- a/shared-libs/ts-modules/start-core/lib/util/Watchable.ts +++ b/shared-libs/ts-modules/start-core/lib/util/Watchable.ts @@ -90,9 +90,13 @@ export abstract class Watchable { constRetry() } }, - () => { + e => { abort.abort() cleanup?.() + console.error( + `watch aborted, no longer reacting to changes @ ${this.label}.const`, + e, + ) }, ) } else { diff --git a/shared-libs/ts-modules/start-core/lib/util/deepEqual.ts b/shared-libs/ts-modules/start-core/lib/util/deepEqual.ts index 1900a6a78..9b1f87bb4 100644 --- a/shared-libs/ts-modules/start-core/lib/util/deepEqual.ts +++ b/shared-libs/ts-modules/start-core/lib/util/deepEqual.ts @@ -17,6 +17,7 @@ * ``` */ export function deepEqual(...args: unknown[]) { + if (args.every(x => x === args[0])) return true const objects = args.filter( (x): x is object => typeof x === 'object' && x !== null, ) diff --git a/shared-libs/ts-modules/start-core/lib/util/filledAddress.ts b/shared-libs/ts-modules/start-core/lib/util/filledAddress.ts index fadfa97a1..ab6f89fa0 100644 --- a/shared-libs/ts-modules/start-core/lib/util/filledAddress.ts +++ b/shared-libs/ts-modules/start-core/lib/util/filledAddress.ts @@ -374,6 +374,16 @@ export function filterNonLocal(hostnames: HostnameInfo[]): HostnameInfo[] { return filterRec(hostnames, nonLocalFilter, false) } +const HELPER_KEYS = [ + 'toUrl', + 'format', + 'filter', + 'matchesAny', + 'nonLocal', + 'public', + 'bridge', +] as const + export const filledAddress = ( host: Host, addressInfo: AddressInfo, @@ -400,7 +410,7 @@ export const filledAddress = ( filterRec(hostnames, bridgeFilter, false), ), ) - return { + const filled = { ...addressInfo, hostnames, toUrl, @@ -440,6 +450,14 @@ export const filledAddress = ( return getBridge() }, } + + // Non-enumerable so a filled address compares and serializes as the data + // it wraps: the derived getters are otherwise an endless walk for deepEqual. + for (const key of HELPER_KEYS) { + Object.defineProperty(filled, key, { enumerable: false }) + } + + return filled } return filledAddressFromHostnames<{}>(hostnames)