Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 4 min read

Telling devices apart without accounts: 576 animal + adjective names for a Socket.IO clipboard app

When several devices share a room with no login, the device list has a problem: nobody knows which row is "my laptop" and who just sent that file. That came up while building Clipboard Share, a no-signup tool for moving

When several devices share a room with no login, the device list has a problem: nobody knows which row is "my laptop" and who just sent that file. That came up while building Clipboard Share, a no-signup tool for moving files and text between devices on the same network. My fix was a small naming scheme: every device gets a tag like "Sleepy Panda". I built the app with Claude Code: https://www.clipboardapp.org

The rooms themselves come from a hashed-IP design that I covered in an earlier post (in Korean). This one is about what happens inside the room.

Why a name at all

With no account there is nothing to point at. With only a device type and a browser name, two phones of the same model look identical in the list, so the server gives each device a human-friendly label. The first implementation landed on 2026-10-01 in commit a6ebd2b ("device identity (animal + adjective) assignment and reroll, sender stamping").

How it works

Two integers

The server never stores a name. It only handles two indexes: 24 adjectives times 24 animals, so 576 combinations.

/** 24 adjectives x 24 animals = 576 combos. Index order must match the frontend */
export const ADJ_COUNT = 24;
export const ANIMAL_COUNT = 24;
export const IDENTITY_COMBOS = ADJ_COUNT * ANIMAL_COUNT;

The frontend turns indexes into text in the viewer's language through i18n keys (identity.adj[n], identity.animal[n]). The same device is "μ‘Έλ¦° νŒλ‹€" in Korean and "Sleepy Panda" in English.

No duplicates in a room

On connect, the server collects the combos already used in the socket's rooms (the global room and the network room) and picks around them. The fallback order, from the source comments:

 * 1) preferred combo (saved value) if it is free
 * 2) on conflict, keep the animal and change only the adjective
 * 3) otherwise any free combo
 * 4) if all 576 are taken, append a suffix number (2, 3, ...)

The "preferred" combo is what the browser remembered. The client keeps it in localStorage under clipboard-identity for 30 days:

const STORAGE_KEY = 'clipboard-identity'
const STORAGE_TTL_MS = 30 * 24 * 60 * 60 * 1000

The server does not trust it. sanitizeIdentity checks that both values are integers in range and drops anything else.

Reroll with a cooldown

The "reroll" button sits on your own row in DeviceList.vue. The server passes the current combo as avoid, so you never get the same name twice in a row, and each socket has a 3-second cooldown:

export const REROLL_COOLDOWN_MS = 3000;
...
if (now - last < REROLL_COOLDOWN_MS) {
    if (typeof ack === 'function') ack({ ok: false, error: 'cooldown', retryAfterMs: REROLL_COOLDOWN_MS - (now - last) });
    return;
}

After a reroll the server re-broadcasts room-users to both rooms, and the button stays disabled until rerollAvailableAt.

The server stamps the sender

Who sent a message is decided server-side. Any sender field from the client is thrown away and replaced:

/**
 * Attach the sender info to a publish message. The client's own sender field
 * is always discarded and overwritten (anti-spoofing).
 */
export const stampSender = (msg: unknown, device: DeviceInfo | undefined) => {

The stamp carries socketId, identity, device label, browser, and model (toSender). The receiving side uses it to tell "sent by me" from "sent by another device".

Icons

The 24 animals are Microsoft Fluent Emoji (Flat) SVGs under the MIT license. The license text and source link live next to them in frontend/src/assets/animals/LICENSE. They are exported as separate hashed files (import.meta.glob with ?no-inline), not inlined into the JS bundle.

Where else the name shows up

  • Arrival indicators and notifications use it as the sender name (senderName in useArrivalBadge.js), falling back to model, browser, then a generic string. The arrival notifications PR (#74) merged today, 2026-11-12.
  • The design doc for group rooms (#75, merged the same day) sets the default room name to "'s room", using this label.
  • 1:1 pairing refers to the other device by its name (usePairFlow.js).

What is still weak

  • Only 576 combos per room. Past that the code appends a suffix number.
  • Uniqueness is checked when you connect or reroll. The name is a label, not authentication.
  • The label is not covered by the password lock. The lock protects the file (and text), but the sender's label, file size, and send time stay visible in the list.
  • Files uploaded through the quick-send API have no label, so the arrival notification falls back to a model name like "iPhone" (routes/quickSend.ts).
  • The reroll cooldown lives in server memory per socket, so a new socket starts a fresh cooldown.

Try it

Open https://www.clipboardapp.org on two devices and the names show up right away. If something feels off, there is a "κ±΄μ˜ν•˜κΈ°" (feedback) link at the bottom of the site, and I would like to hear it.

Originally published in Korean on velog: velog

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.