diff --git a/apps/desktop/extensions/ai-sidebar/moshpit-drift.test.js b/apps/desktop/extensions/ai-sidebar/moshpit-drift.test.js index fc2bc1a..bd46064 100644 --- a/apps/desktop/extensions/ai-sidebar/moshpit-drift.test.js +++ b/apps/desktop/extensions/ai-sidebar/moshpit-drift.test.js @@ -23,6 +23,12 @@ import * as pkg from '@moshcoder/moshpit-resolve'; const HOSTNAMES = [ 'blue.eggs', 'a.b.c', '1.2.3.4', 'localhost', '', 'eggs', 'mosh.eggs', 'blue.420', '420.blue', '1.420', '192.168', 'x.y', 'A.EGGS.', + // Dashes. The list carried none, so a divergence on whether a dash may + // appear inside a label read as green across all three implementations. + 'lazy-loaded', + 'blue.lazy-loaded', + 'register-me.eggs', + 'a-b.c-d', ]; const LOOKUPS = [ diff --git a/apps/desktop/extensions/ai-sidebar/moshpit.js b/apps/desktop/extensions/ai-sidebar/moshpit.js index 4a2078a..eb4233d 100644 --- a/apps/desktop/extensions/ai-sidebar/moshpit.js +++ b/apps/desktop/extensions/ai-sidebar/moshpit.js @@ -128,7 +128,14 @@ export function parseRegistryName(hostname) { const parts = host.split('.'); if (parts.length !== 2) return null; const [label, tld] = parts; - const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/; + // Letters and digits only, no dashes — the same rule the registry enforces. + // A dash is the cheapest way to mint a near-miss of an ending someone else + // holds (`.cryp-to` beside `.crypto`), and this namespace is one level deep + // and first come first served, so there is nothing to appeal into. Keeping + // this identical to the registry matters more than the rule itself: a name + // this accepts and the registry rejects forwards to a page saying it does + // not exist. + const LABEL = /^[a-z0-9]{1,63}$/; if (!LABEL.test(label) || !LABEL.test(tld)) return null; return { label, tld }; } diff --git a/apps/desktop/extensions/ai-sidebar/moshpit.test.js b/apps/desktop/extensions/ai-sidebar/moshpit.test.js index 8b74628..ec51a7f 100644 --- a/apps/desktop/extensions/ai-sidebar/moshpit.test.js +++ b/apps/desktop/extensions/ai-sidebar/moshpit.test.js @@ -23,6 +23,15 @@ const HOSTNAMES = [ 'mosh', '', 'x.y', + // Dashes. The list carried none, so a change to whether a dash may appear in + // a label could land in one implementation and not the other and every + // assertion below would still agree. + 'lazy-loaded', + 'blue.lazy-loaded', + 'register-me.eggs', + 'a-b.c-d', + '-bad.eggs', + 'bad-.eggs', ]; describe('moshpit.js is faithful to moshpit-resolve.ts', () => { diff --git a/apps/desktop/src/moshpit-resolve.test.ts b/apps/desktop/src/moshpit-resolve.test.ts index 8177e96..c0a9e46 100644 --- a/apps/desktop/src/moshpit-resolve.test.ts +++ b/apps/desktop/src/moshpit-resolve.test.ts @@ -307,3 +307,28 @@ describe('decideResolution — parking unpointed names', () => { expect(d.url).toBe('https://my.park/n/california.oranges'); }); }); + +describe('dashes are not part of a Moshpit name', () => { + // Kept identical to the registry on purpose. A name this accepts and the + // registry rejects forwards the tab to a page saying it does not exist, + // which is a worse failure than refusing it here. + it('refuses a dash anywhere in either half', () => { + for (const host of [ + 'lazy-loaded.eggs', + 'blue.lazy-loaded', + 'register-me.eggs', + 'a-b.c-d', + '-bad.eggs', + 'bad-.eggs', + ]) { + expect(parseRegistryName(host), host).toBeNull(); + } + }); + + it('still accepts what the registry accepts', () => { + expect(parseRegistryName('california.oranges')).toEqual({ label: 'california', tld: 'oranges' }); + expect(parseRegistryName('blue.420')).toEqual({ label: 'blue', tld: '420' }); + expect(parseRegistryName(`${'a'.repeat(63)}.eggs`)).toEqual({ label: 'a'.repeat(63), tld: 'eggs' }); + expect(parseRegistryName(`${'a'.repeat(64)}.eggs`)).toBeNull(); + }); +}); diff --git a/apps/desktop/src/moshpit-resolve.ts b/apps/desktop/src/moshpit-resolve.ts index 9041097..8c27e83 100644 --- a/apps/desktop/src/moshpit-resolve.ts +++ b/apps/desktop/src/moshpit-resolve.ts @@ -237,7 +237,14 @@ export function parseRegistryName(hostname: string): { label: string; tld: strin // narrow an array to a 2-tuple from a length check. An empty label would // fail LABEL.test() anyway, so this guard changes no behavior. if (!label || !tld) return null; - const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/; + // Letters and digits only, no dashes — the same rule the registry enforces. + // A dash is the cheapest way to mint a near-miss of an ending someone else + // holds (`.cryp-to` beside `.crypto`), and this namespace is one level deep + // and first come first served, so there is nothing to appeal into. Keeping + // this identical to the registry matters more than the rule itself: a name + // this accepts and the registry rejects forwards to a page saying it does + // not exist. + const LABEL = /^[a-z0-9]{1,63}$/; if (!LABEL.test(label) || !LABEL.test(tld)) return null; return { label, tld }; }