* xftp: add PostgreSQL backend design spec
* update doc
* adjust styling
* add implementation plan
* refactor: move usedStorage from FileStore to XFTPEnv
* refactor: add getUsedStorage, getFileCount, expiredFiles store functions
* refactor: change file store operations from STM to IO
* refactor: extract FileStoreClass typeclass, move STM impl to Store.STM
* refactor: make XFTPEnv and server polymorphic over FileStoreClass
* feat: add PostgreSQL store skeleton with schema migration
* feat: implement PostgresFileStore operations
* feat: add PostgreSQL INI config, store dispatch, startup validation
* feat: add database import/export CLI commands
* test: add PostgreSQL backend tests
* fix: map ForeignKeyViolation to AUTH in addRecipient
When a file is concurrently deleted while addRecipient runs, the FK
constraint on recipients.sender_id raises ForeignKeyViolation. Previously
this propagated as INTERNAL; now it returns AUTH (file not found).
* fix: only decrement usedStorage for uploaded files on expiration
expireServerFiles unconditionally subtracted file_size from usedStorage
for every expired file, including files that were never uploaded (no
file_path). Since reserve only increments usedStorage during upload,
expiring never-uploaded files caused usedStorage to drift negative.
* fix: handle setFilePath error in receiveServerFile
setFilePath result was discarded with void. If it failed (file deleted
concurrently, or double-upload where file_path IS NULL guard rejected
the second write), the server still reported FROk, incremented stats,
and left usedStorage permanently inflated. Now the error is checked:
on failure, reserved storage is released and AUTH is returned.
* fix: escape double quotes in COPY CSV status field
The status field (e.g. "blocked,reason=spam,notice={...}") is quoted in
CSV for COPY protocol, but embedded double quotes from BlockingInfo
notice (JSON) were not escaped. This could break CSV parsing during
import. Now double quotes are escaped as "" per CSV spec.
* fix: reject upload to blocked file in Postgres setFilePath
In Postgres mode, getFile returns a snapshot TVar for fileStatus. If a
file is blocked between getFile and setFilePath, the stale status check
passes but the upload should be rejected. Added status = 'active' to
the UPDATE WHERE clause so blocked files cannot receive uploads.
* fix: add CHECK constraint on file_size > 0
Prevents negative or zero file_size values at the database level.
Without this, corrupted data from import or direct DB access could
cause incorrect storage accounting (getUsedStorage sums file_size,
and expiredFiles casts to Word32 which wraps negative values).
* fix: check for existing data before database import
importFileStore now checks if the target database already contains
files and aborts with an error. Previously, importing into a non-empty
database would fail mid-COPY on duplicate primary keys, leaving the
database in a partially imported state.
* fix: clean up disk file when setFilePath fails in receiveServerFile
When setFilePath fails (file deleted or blocked concurrently, or
duplicate upload), the uploaded file was left orphaned on disk with
no DB record pointing to it. Now the file is removed on failure,
matching the cleanup in the receiveChunk error path.
* fix: check storeAction result in deleteOrBlockServerFile_
The store action result (deleteFile/blockFile) was discarded with void.
If the DB row was already deleted by a concurrent operation, the
function still decremented usedStorage, causing drift. Now the error
propagates via ExceptT, skipping the usedStorage adjustment.
* fix: check deleteFile result in expireServerFiles
deleteFile result was discarded with void. If a concurrent delete
already removed the file, deleteFile returned AUTH but usedStorage
was still decremented — causing double-decrement drift. Now the
usedStorage adjustment and filesExpired stat only run on success.
* refactor: merge STM store into Store.hs, parameterize server tests
- Move STMFileStore and its FileStoreClass instance from Store/STM.hs
back into Store.hs — the separate file was unnecessary indirection
for the always-present default implementation.
- Parameterize xftpFileTests over store backend using HSpec SpecWith
pattern (following SMP's serverTests approach). The same 11 tests
now run against both memory and PostgreSQL backends via a bracket
parameter, eliminating all *Pg test duplicates.
- Extract shared run* functions (runTestFileChunkDeliveryAddRecipients,
runTestWrongChunkSize, runTestFileChunkExpiration, runTestFileStorageQuota)
from inlined test bodies.
* refactor: clean up per good-code review
- Remove internal helpers from Postgres.hs export list (withDB, withDB',
handleDuplicate, assertUpdated, withLog are not imported externally)
- Replace local isNothing_ with Data.Maybe.isNothing in Env.hs
- Consolidate duplicate/unused imports in XFTPStoreTests.hs
- Add file_path IS NULL and status guards to STM setFilePath, matching
the Postgres implementation semantics
* test: parameterize XFTP server, agent and CLI tests over store backend
- xftpTest/xftpTest2/xftpTest4/xftpTestN now take XFTPTestBracket as
first argument, enabling the same test to run against both memory
and PostgreSQL backends.
- xftpFileTests (server tests), xftpAgentFileTests (agent tests), and
xftpCLIFileTests (CLI tests) are SpecWith-parameterized suites that
receive the bracket from HSpec's before combinator.
- Test.hs runs each parameterized suite twice: once with
xftpMemoryBracket, once with xftpPostgresBracket (CPP-guarded).
- STM-specific tests (store log restore/replay) stay in memory-only
xftpAgentTests. SNI/CORS tests stay in memory-only xftpServerTests.
* refactor: remove dead test wrappers after parameterization
Remove old non-parameterized test wrapper functions that were
superseded by the store-backend-parameterized test suites.
All test bodies (run* and _ functions) are preserved and called
from the parameterized specs. Clean up unused imports.
* feat: add manual tests and guide
* refactor: merge file_size CHECK into initial migration
* refactor: extract rowToFileRec shared by getFile sender/recipient paths
* refactor: parameterize XFTPServerConfig over store type
Embed XFTPStoreConfig s as serverStoreCfg field, matching SMP's
ServerConfig. runXFTPServer and newXFTPServerEnv now take a single
XFTPServerConfig s. Restore verifyCmd local helper structure.
* refactor: minimize diff in tests
Restore xftpServerTests and xftpAgentTests bodies to match master
byte-for-byte (only type signatures change for XFTPTestBracket
parameterization); inline the runTestXXX helpers that were split
on this branch.
* refactor: restore getFile position to match master
* refactor: rename withSTMFile back to withFile
* refactor: close store log inside closeFileStore for STM backend
Move STM store log close responsibility into closeFileStore to
match PostgresFileStore, removing the asymmetry where only PG's
close was self-contained.
STMFileStore holds the log in a TVar populated by newXFTPServerEnv
after readWriteFileStore; stopServer no longer needs the explicit
withFileLog closeStoreLog call. Writes still go through XFTPEnv.storeLog
via withFileLog (unchanged).
* refactor: rename XFTPTestBracket to XFTPTestServer
* fix: move file_size check from PG schema to store log import
* refactor: use SQL-standard type names in XFTP schema
* perf: batch expired file deletions with deleteFiles
* refactor: stream export instead of loading recipients into memory
* refactor: parameterize XFTP store with FSType singleton dispatch
* refactor: minimize diff per review feedback
* refactor: use types over strings, deduplicate parser
* refactor: always parse database store type, fail at startup
* fix compilation without postgresql
* refactor: always parse database store type, fail at startup
* xftp: implementation of XFTP client as web page (rfc, low level functions)
* protocol, file descriptions, more cryptogrpahy, handshake encoding, etc.
* xftp server changes to support web slients: SNI-based certificate choice, CORS headers, OPTIONS request
* web handshake
* test for xftp web handshake
* xftp-web client functions, fix transmission encoding
* support description "redirect" in agent.ts and cross-platform compatibility tests (Haskell <> TypeScript)
* rfc: web transport
* client transport abstraction
* browser environment
* persistent client sessions
* move rfcs
* web page plan
* improve plan
* webpage implementation (not tested)
* fix test
* fix test 2
* fix test 3
* fixes and page test plan
* allow sending xftp client hello after handshake - for web clients that dont know if established connection exists
* page tests pass
* concurrent and padded hellos in the server
* update TS client to pad hellos
* fix tests
* preview:local
* local preview over https
* fixed https in the test page
* web test cert fixtures
* debug logging in web page and server
* remove debug logging in server/browser, run preview xftp server via cabal run to ensure the latest code is used
* debug logging for page sessions
* add plan
* improve error handling, handle browser reconnections/re-handshake
* fix
* debugging
* opfs fallback
* delete test screenshot
* xftp CLI to support link
* fix encoding for XFTPServerHandshake
* support redirect file descriptions in xftp CLI receive
* refactor CLI redirect
* xftp-web: fixes and multi-server upload (#1714)
* fix: await sodium.ready in crypto/keys.ts (+ digest.ts StateAddress cast)
* multi-server parallel upload, remove pickRandomServer
* fix worker message race: wait for ready signal before posting messages
* suppress vite build warnings: emptyOutDir, externals, chunkSizeWarningLimit
* fix Haskell web tests: use agent+server API, wrap server in array, suppress debug logs
* remove dead APIs: un-export connectXFTP, delete closeXFTP
* fix TypeScript errors in check:web (#1716)
- client.ts: cast globalThis.process to any for browser tsconfig,
suppress node:http2 import, use any for Buffer/chunks, cast fetch body
- crypto.worker.ts: cast sha512_init() return to StateAddress
* fix: serialize worker message processing to prevent OPFS handle race
async onmessage allows interleaved execution at await points.
When downloadFileRaw fetches chunks from multiple servers in parallel,
concurrent handleDecryptAndStore calls both see downloadWriteHandle
as null and race on createSyncAccessHandle for the same file,
causing intermittent NoModificationAllowedError.
Chain message handlers on a promise queue so each runs to completion
before the next starts.
* xftp-web: prepare for npm publishing (#1715)
* prepare package.json for npm publishing
Remove private flag, add description/license/repository/publishConfig,
rename postinstall to pretest, add prepublishOnly, set files and main.
* stable output filenames in production build
* fix repository url format, expand files array
* embeddable component: scoped CSS, dark mode, i18n, events, share
- worker output to assets/ for single-directory deployment
- scoped all CSS under #app, removed global resets
- dark mode via .dark ancestor class
- progress ring reads colors from CSS custom properties
- i18n via window.__XFTP_I18N__ with t() helper
- configurable mount element via data-xftp-app attribute
- optional hashchange listener (data-no-hashchange)
- completion events: xftp:upload-complete, xftp:download-complete
- enhanced file-too-large error mentioning SimpleX app
- native share button via navigator.share
* deferred init and runtime server configuration
- data-defer-init attribute skips auto-initialization
- window.__XFTP_SERVERS__ overrides baked-in server list
* use relative base path for relocatable build output
* xftp-web: retry resets to default state, use innerHTML for errors
* xftp-web: only enter download mode for valid XFTP URIs in hash
* xftp-web: render UI before WASM is ready
Move sodium.ready await after UI initialization so the upload/download
interface appears instantly. WASM is only needed when user triggers
an actual upload or download. Dispatch xftp:ready event once WASM loads.
* xftp-web: CLS placeholder HTML and embedder CSS selectors
Add placeholder HTML to index.html so the page renders a styled card
before JS executes, preventing layout shift. Use a <template> element
with an inline script to swap to the download placeholder when the URL
hash indicates a file download. Auto-compute CSP SHA-256 hashes for
inline scripts in the vite build plugin.
Change all CSS selectors from #app to :is(#app, [data-xftp-app]) so
styles apply when the widget is embedded with data-xftp-app attribute.
* xftp-web: progress ring overhaul
Rewrite progress ring with smooth lerp animation, green checkmark on
completion, theme reactivity via MutationObserver, and per-phase color
variables (encrypt/upload/download/decrypt).
Show honest per-phase progress: each phase animates 0-100% independently
with a ring color change between phases. Add decrypt progress callback
from the web worker so the decryption phase tracks real chunk processing
instead of showing an indeterminate spinner.
Snap immediately on phase reset (0) and completion (1) to avoid
lingering partial progress. Clean up animation and observers via
destroy() in finally blocks.
* xftp-web: single progress ring for upload, simplify ring color
* xftp-web: single progress ring for download
* feat(xftp-web): granular progress for encrypt/decrypt phases
Add byte-level progress callbacks to encryptFile, decryptChunks,
and sha512Streaming by processing data in 256KB segments. Worker
reports fine-grained progress across all phases (encrypt+hash+write
for upload, read+hash+decrypt for download). Progress ring gains
fillTo method for smooth ease-out animation during minimum display
delays. Encrypt/decrypt phases fill their weighted regions (0-15%
and 85-99%) with real callbacks, with fillTo covering remaining
time when work finishes under the 1s minimum for files >= 100KB.
* rename package
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
Co-authored-by: shum <github.shum@liber.li>
Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>
* smp server: fix server pages when source code is not specified
* servers: include git commit in version
* flexible alpn
* fix test
* fix ghc 8.10.7 build
* servers: blocking records for content moderation
* update
* encode BLOCKED as AUTH in old versions
* update
* unblock queue command
* test, status command
* agent: transport isolation mode "Session" (default) to use new SOCKS credentials when client restarts or SOCKS proxy configuration changes
* fix test
* put DRG state to IORef, split STM transaction of sending notification (#1288)
* put DRG state to IORef, split STM transaction of sending notification
* remove comment
* remove comment
* add comment
* revert version
* newtype for server entity IDs, fix TRcvQueues
* Revert "put DRG state to IORef, split STM transaction of sending notification (#1288)"
This reverts commit 517933d189.
* logServer
* smp: command authorization
* fix encoding, most tests
* remove old tests
* authorize via crypto_box
* extract authenticator to Crypto module
* make TransmissionAuth Maybe
* rfc
* support authenticators in NTF protocol, test matrix (no backwards compatibility yet from new clients to old servers)
* fix/add tests, add version config to "small" agent
* separate client and server versions for SMP protocol
* test batching SMP v7
* do not send session ID in each transmission
* refactor auth verification in the server, split tests
* server "warm up" fixes timing test
* uncomment SUB timing test
* comments, disable two timing tests
* rename version
* increase auth timing test failure threshold
* use different algorithms to authorize snd/rcv commands, use random correlation ID
* transport: fetch and store server certificate (#985)
* THandleParams (WIP, does not compile)
* transport: fetch and store server certificate
* smp: add getOnlinePubKey example to smpClientHandshake
* add server certs and sign authPub
* cleanup
* update
* style
* load server certs from test fixtures
* sign ntf authPubKey
* fix onServerCertificate
* increase delay before sending messages
* require certificate with key in SMP server handshake
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
* remove dhSecret from THandle
* remove v8, merge all changes to one version
* parameterize THandle
* rfc: transmission ecnryption
* Revert "parameterize THandle"
This reverts commit 75adfc94fb.
* use batch syntax for ntf server commands
* separate encodeTransmission when there is no key
* typo
Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
* rename
* diff
---------
Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com>
Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
* xftp: expire files on the server
* track/limit used storage
* support storage quota and disabling queue creation in CLI parameters
* fix ini file
* correction
---------
Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>