5.0 KiB
Attachments
Area: attachments Status: Active Last updated: 2026-07-05
Overview
Attachments move file bytes peer-to-peer over the WebRTC ordered data channel using a announce → request → chunk protocol. Chat and DMs attach metadata to messages; the signaling server does not store or relay file payloads. Sibling devices learn attachment metadata via account_sync chat-sync-batch but must still download bytes from a peer that has them.
Domain internals: toju-app/src/app/domains/attachment/README.md.
Responsibilities
- Chunked P2P transfer with flow control and cancel semantics.
- Auto-download when policy allows; disk streaming on Electron/Capacitor.
- Ownership vs "shared from your device" UI rules.
- Persist attachment rows + filesystem paths on desktop/mobile.
This area does not own:
- Message envelopes or delivery states → messaging.md.
- WebRTC negotiation → voice-webrtc.md.
Key concepts
- Announce — sender advertises
fileId, name, size, mime without sending bytes. - Mirror host — peer that holds a complete copy and can serve chunks.
- Buffered send — waits for data-channel back-pressure (4 MB high / 1 MB low water marks on chat channel).
P2P protocol
| type | Purpose |
|---|---|
file-announce |
Metadata only |
file-request |
Receiver starts download |
file-chunk |
Base64 chunk (index, total, data) |
file-chunk-ack |
Per-chunk flow control |
file-cancel |
Abort in flight |
file-not-found |
Host lacks bytes |
Chunk size: FILE_CHUNK_SIZE_BYTES = 64 KB (attachment-transfer.constants.ts).
Electron send path: reads one chunk at a time from disk via IPC (append-file-bytes / read chunk) to avoid loading whole files into renderer memory.
Persistence
| Runtime | Metadata | Bytes |
|---|---|---|
| Browser | In-memory / optional save | Below 10 MB auto-save cap (MAX_AUTO_SAVE_SIZE_BYTES) |
| Electron | SQLite attachments + CQRS |
user/<username>/… via AttachmentStorageService / IPC |
| Capacitor | SQLite | App-private attachment directory — mobile-capacitor.md |
Download / export to user location
AttachmentDownloadService.downloadToUserLocation picks the runtime-appropriate export path:
| Runtime | Behavior |
|---|---|
| Electron | saveExistingFileAs (disk-backed) or saveFileAs (blob) native save dialog |
| Browser | Anchor download click on the object URL |
| Capacitor | CapacitorAttachmentExportService.exportToDevice: copies the disk file from Directory.Data into Directory.Documents (or fetches the object URL and writes base64) using buildAttachmentExportFileName (timestamp suffix so exports never collide — Android 11+ rejects overwrites of files the app did not create). Anchor downloads do nothing in the Android WebView. |
Multi-device
chat-sync-batch in account_sync carries an attachments map (local paths stripped). Sibling devices discover files exist; P2P file-request still required for bytes.
Business rules and invariants
- Transfers are between connected peers only (no server CDN).
- Receive strategy is decided once at request time by
canReceiveAttachment(attachment.logic.ts): ≤ 10 MB assembles in memory everywhere; > 10 MB streams to disk on Electron/Capacitor, assembles in memory on the browser up to its 50 MB persist cap, and is rejected with a visiblefileTooLargeerror beyond that.handleFileChunkmust accept whatever the request gate admitted — a stricter chunk-time size cap silently drops chunks and stalls the transfer. - Visibility-based blob lifecycle on desktop: revoke
blob:URLs when messages scroll off-screen if disk can rehydrate. - "Shared from your device" badge only when bytes are local to the viewing user.
Technical implementation
- Facade:
AttachmentFacade→AttachmentManagerService - Protocol:
AttachmentTransferService+AttachmentTransferTransportService - Electron IPC:
read-file-chunk,append-file-bytes,write-file,delete-file, etc.
Testing
- Domain logic specs under
attachment/ - E2E:
e2e/tests/chat/chat-message-features.spec.ts,local-attachment-persistence.spec.ts,multi-device-attachment-sharing.spec.ts,large-generic-file-transfer.spec.ts(browser receiver, generic file above the 10 MB auto-save cap)
Security considerations
- No server-side virus scanning; peers trust senders they are connected to.
- Files stay in user data directories (Electron path jail).
Related features
- messaging.md — message + attachment metadata coupling
- authentication.md —
account_syncbatches - mobile-capacitor.md — mobile storage
Changelog
| Date | Change |
|---|---|
| 2026-07-13 | Capacitor download/export to public Documents via CapacitorAttachmentExportService |
| 2026-07-05 | Expanded to full contract style |