north-star / commit
changing how default names are calculated
src/app.js | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/app.js b/src/app.js index 2ede552..ddab320 100644 --- a/src/app.js +++ b/src/app.js @@ -510,7 +510,7 @@ if (calibrateHeadingLink) { }); } -const NAME_STORAGE_KEY = 'north-star-self-name'; +const NAME_STORAGE_KEY = 'north-star-self-name-v2'; const COLOR_STORAGE_KEY = 'north-star-self-color'; const PARTICIPANT_VISIBILITY_STORAGE_KEY = 'north-star-participant-visibility'; const PARTICIPANTS_PANEL_VISIBLE_STORAGE_KEY = 'north-star-participants-panel-visible'; @@ -533,14 +533,47 @@ const getReadableTextColor = (hexColor) => { return luminance > 145 ? '#0f172a' : '#f8fafc'; }; -const createRandomUserName = () => `User ${Math.floor(10000 + Math.random() * 90000)}`; + +const getDeviceHint = async () => { + // Try high-entropy UA data first (Chromium only). + if (navigator.userAgentData) { + try { + const data = await navigator.userAgentData.getHighEntropyValues(['model']); + if (data.model) return data.model; + } catch { + // Fall through to UA string parsing. + } + } + + const ua = navigator.userAgent; + // iPhone / iPad + const iosMatch = ua.match(/\(([^)]+)\)/); + if (/iPhone/.test(ua)) return 'iPhone'; + if (/iPad/.test(ua)) return 'iPad'; + // Named Android device (e.g. "SM-G991B Build/..." or "Pixel 7 Build/...") + const androidModel = ua.match(/;\s*([^;]+?)\s+Build\//); + if (androidModel) return androidModel[1]; + // OS family fallback + if (/Android/.test(ua)) return 'Android'; + if (/Mac/.test(ua)) return 'Mac'; + if (/Windows/.test(ua)) return 'PC'; + if (/Linux/.test(ua)) return 'Linux'; + return null; +}; + +const createRandomUserName = async () => { + const hint = await getDeviceHint(); + const suffix = Math.floor(1000 + Math.random() * 9000); + return hint ? `${hint} ${suffix}` : `User ${suffix}`; +}; + const createRandomColor = () => `#${Math.floor(Math.random() * 0x1000000).toString(16).padStart(6, '0')}`; -const resolveInitialSelfName = () => { +const resolveInitialSelfName = async () => { const savedName = normalizeName(localStorage.getItem(NAME_STORAGE_KEY)); if (savedName) return savedName; - const generatedName = createRandomUserName(); + const generatedName = await createRandomUserName(); localStorage.setItem(NAME_STORAGE_KEY, generatedName); return generatedName; }; @@ -554,7 +587,7 @@ const resolveInitialSelfColor = () => { return generatedColor; }; -const initialSelfName = resolveInitialSelfName(); +const initialSelfName = await resolveInitialSelfName(); const initialSelfColor = resolveInitialSelfColor(); const participantVisibility = (() => { try { @@ -844,7 +877,7 @@ if (selfColorInput) { } const applySelfName = async (rawName) => { - const nextName = normalizeName(rawName) || createRandomUserName(); + const nextName = normalizeName(rawName) || await createRandomUserName(); if (nextName === selfParticipant.name) { if (selfNameInput) selfNameInput.value = nextName;