5.5 KiB
Custom Emoji
Area: custom-emoji Status: Active Last updated: 2026-07-05
Overview
Custom emoji lets users upload small image emoji, use them in chat messages and reactions, and sync the image bytes to connected peers over the WebRTC data channel (and to sibling devices via account_sync). The signaling server never stores emoji assets.
Internal UI and NgRx wiring: toju-app/src/app/domains/custom-emoji/README.md. Chat composer integration: toju-app/src/app/domains/chat/README.md.
Responsibilities
- Validate uploads (size, MIME), persist image assets locally, and track per-user saved library membership.
- Rank shortcuts by local usage (not synced across devices).
- Sync assets P2P (
custom-emoji-*envelopes) and proactively push referenced emoji when sending messages. - Relay the same envelopes on
account_syncfor multi-device library convergence. - Expose
CustomEmojiPickerComponentfor composer and reactions.
This area does not own:
- Message send/edit transport → messaging.md.
- Profile avatar bytes →
toju-app/src/app/domains/profile-avatar/README.md. - Server-side storage (none).
Key concepts
- Custom emoji asset — image with
id,name,mime,size,hash,creatorUserId,dataUrl(or reconstructed from chunks). - Known emoji — synced for rendering; not necessarily in the picker.
- Saved emoji — in the active user's library (
metoyou_custom_emoji_saved:<userId>); shown in picker and shortcut row. - Token — stable wire form
:emoji[id](name)in message/reaction bodies. - Composer alias — draft form
:name:rewritten to a token on send when the name is known. - Shortcut row — seven most-used saved entries plus opener for full picker.
Peer envelope contract (P2P)
| type | Payload |
|---|---|
custom-emoji-summary |
{ customEmojiSummaries: [{ id, hash, updatedAt }] } |
custom-emoji-request |
{ ids: string[] } |
custom-emoji-full |
manifest (customEmojiTransfer) ± inline bytes |
custom-emoji-chunk |
{ customEmojiId, index, total, data } base64 |
Handshake: on peer connect both sides send summaries; receiver requests stale/missing ids; owner sends manifest then chunked payloads via buffered sends.
Proactive push: outgoing chat/DM messages scan for tokens and push assets to connected peers in parallel with the message event.
Inline threshold: assets ≤ CUSTOM_EMOJI_INLINE_MAX_JSON_BYTES (48 KiB) ship in one custom-emoji-full; larger assets use manifest + chunks.
Repair path: incoming messages and chat-sync-batch scan for tokens and request missing assets from the sender.
Multi-device (account_sync)
Relayable types (account-sync.rules.ts): custom-emoji-summary, custom-emoji-request, custom-emoji-full, custom-emoji-chunk. See signaling.md and authentication.md.
Business rules and invariants
- Max upload 1 MB; MIME: WebP, GIF, JPEG/JPG (same set as profile avatars).
- Library membership is per user id, not per device — second account on same machine does not inherit another user's saved set.
- Seeing an emoji in chat does not add it to the library; user must Add to emoji library from context menu.
- Remove from library hides picker entry but keeps asset for messages that already reference it.
- Chunks stay below SCTP-safe sizes; oversized single sends can trigger
RTCDataChannel.onerroreven when back-pressure is idle. - Persist only when reconstructed
dataUrlmatches manifest size and hash; corrupt rows are dropped before advertising summaries. - Placeholder rendering avoids flashing raw tokens while assets are in flight.
Storage
| Runtime | Asset bytes | Library membership |
|---|---|---|
| Browser | IndexedDB customEmojis (per-user DB scope) |
localStorage metoyou_custom_emoji_saved:<userId> |
| Electron | SQLite custom_emojis (shared desktop DB) |
same localStorage key |
| Capacitor | SQLite custom_emojis in metoyou__<userId> |
same localStorage key |
API: DatabaseService.saveCustomEmoji / getCustomEmojis / deleteCustomEmoji.
Technical implementation
- Rules:
domains/custom-emoji/domain/custom-emoji.rules.ts - Service:
CustomEmojiService; effects:CustomEmojiSyncEffects - Picker:
feature/custom-emoji-picker/ - Context menu:
data-custom-emoji/data-custom-emoji-libraryattributes on rendered hosts
Testing
custom-emoji.rules.spec.ts,custom-emoji.service.spec.ts,custom-emoji-picker.component.spec.tsaccount-sync.rules.spec.ts(relayable types)- E2E:
e2e/tests/chat/custom-emoji-user-binding.spec.ts
Security considerations
- Image-only, size-capped payloads before persist or broadcast.
- Assets reach only connected peers (or same-account devices via
account_sync); server never proxies bytes.
Known limitations
- Usage counts and shortcut ranking are local only.
- Electron asset table is shared across OS users on one desktop install; library keys remain per MetoYou user id.
Related features
- messaging.md — tokens in message bodies, proactive push on send
- signaling.md —
account_sync - mobile-capacitor.md — Capacitor SQLite path
Changelog
| Date | Change |
|---|---|
| 2026-07-05 | Restructured to match messaging doc style; fixed duplicate sections; Capacitor + account_sync |