feat: refresh xdrop positioning

This commit is contained in:
xixu-me committed 2026-03-22 15:48:18 +08:00
1 parent 2f3a5daafc
commit d5f6e392a5
18 files changed
+127 -123

No files matched your search

+2 -1
View File
@@ -8,7 +8,8 @@ unlikely to land.
## Project focus
Xdrop is focused on private file transfer with browser-side encryption.
Xdrop is focused on open source end-to-end encrypted file transfer for humans and agents, keeping
plaintext file names, contents, and keys off the server.
Changes are most likely to be accepted when they improve one or more of these areas:
+38 -45
View File
@@ -7,49 +7,44 @@ metadata, and social profiles aligned.
Canonical one-liner:
`Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.`
`Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server.`
This is the default introduction for Xdrop when a surface only gets one sentence.
Positioning framework:
- **Category:** Open source encrypted file transfer.
- **Category:** Open source end-to-end encrypted file transfer.
- **Primary promise:** Plaintext file names, contents, and keys stay off the server.
- **Default experience:** Browser-first for normal sharing flows.
- **Extended workflow:** Also usable from agent-driven terminal environments such as Codex, remote
servers, dev containers, and CI-adjacent workflows.
- **Supported users:** Humans and agents are both supported users of Xdrop.
- **Common environments:** Includes browser-based sharing for humans and agent workflows in
remote servers, dev containers, and CI-adjacent environments.
Short positioning summary:
`Browser-first encrypted file transfer, with agent-ready terminal workflows.`
`End-to-end encrypted file transfer for humans and agents.`
## Canonical Copy By Surface
README first sentence:
Some surfaces intentionally reuse the canonical one-liner to keep product-facing copy tightly
aligned.
`Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.`
README first sentence, homepage body, and meta description:
`Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server.`
Homepage H1:
`Encrypted file transfer.`
Homepage body:
`Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.`
`End-to-end encrypted file transfer.`
Homepage and SEO title:
`Open Source Encrypted File Transfer for Browsers and Agents | Xdrop`
Meta description:
`Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.`
`Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop`
OG card headline:
`Open source encrypted`
`Open source end-to-end encrypted`
`file transfer for browsers and agents.`
`file transfer for humans and agents.`
OG card support line:
@@ -57,62 +52,60 @@ OG card support line:
Short social bio:
`Open source encrypted file transfer for browsers and agents. Plaintext file names, contents, and keys stay off the server.`
`Open source end-to-end encrypted file transfer for humans and agents.`
Short technical summary:
`Browser-first encrypted file transfer with agent-ready terminal workflows and plaintext kept off the server.`
`End-to-end encrypted file transfer with plaintext file names, contents, and keys kept off the server.`
Terminal and agent support blurb:
`Xdrop can also be used from agent-driven terminal workflows to upload files, return encrypted share links, and download full Xdrop links for local decryption.`
`Agents can use Xdrop to upload files, return end-to-end encrypted share links, and use Xdrop links for local decryption.`
Use-case summary:
`Use Xdrop in the browser for normal sharing, or through an agent when you need to move files out of a cloud server, remote container, or automated terminal workflow.`
`Humans can use Xdrop in the browser for direct sharing, and agents can use Xdrop in cloud servers, remote containers, and automated terminal workflows.`
Chinese reference copy:
- Canonical one-liner:
`Xdrop 是一个开源加密文件传输应用,以浏览器为主体验,也支持智能体驱动的终端工作流,并确保服务端拿不到明文文件名、文件内容和密钥。`
`Xdrop 是一款面向人类与智能体的开源端到端加密文件传输应用,它能确保明文的文件名、文件内容以及密钥都不会留存在服务器上。`
- Short positioning summary:
`以浏览器为主体验的加密文件传输,也支持智能体终端工作流。`
`专为人类与智能体打造的端到端加密文件传输。`
- Agent support blurb:
`日常分享可直接使用浏览器;如果你需要将文件从云服务器、远程容器或自动化终端流程中传出来,也可以通过智能体使用 Xdrop。`
`智能体可以使用 Xdrop 上传文件,返回端到端加密的分享链接,并使用 Xdrop 链接进行本地解密。`
## Messaging Priorities
When space is limited, keep these ideas in this order:
1. Xdrop is open source.
2. Xdrop is encrypted file transfer, not generic file sharing.
2. Xdrop is end-to-end encrypted file transfer, not generic file sharing.
3. Plaintext file names, contents, and keys stay off the server.
4. The product is browser-first, but not browser-only.
4. Humans and agents are both supported users of Xdrop.
5. `No account required` is a useful supporting point, but not the main definition.
## Preferred Language
- Prefer `encrypted file transfer` as the main category label.
- Prefer `browser-first` when you need to signal the main UX without implying the browser is the
only supported way to use Xdrop.
- Prefer `agent-driven terminal workflows` or `use Xdrop via an agent` when describing the skill
and CLI-style experience.
- Prefer `encrypts files in your browser` when the copy is specifically about the web app flow.
- Prefer `end-to-end encrypted file transfer` as the main category label.
- Prefer `for humans and agents` as the default phrasing when you need a
product-level line that names both supported users.
- Prefer `agents` or `use Xdrop via an agent` when describing the skill and agent-driven
experience.
- Prefer `keeps plaintext ... off the server` over `ciphertext-only storage` unless the audience is technical.
- Prefer `open source encrypted file transfer app` when introducing Xdrop for the first time.
- Prefer `in-browser encryption` as a compact technical benefit, not as the whole product category.
- Prefer `open source end-to-end encrypted file transfer app` when introducing Xdrop for the first time.
- Use `AES-256-GCM` in technical docs, threat-model explanations, and implementation notes, not as the default marketing hook.
## Avoid
- Avoid using `private file transfer` as the only product summary.
- Avoid presenting Xdrop as browser-only now that agent workflows are a supported entry point.
- Avoid mixing `private`, `secure`, `browser-side`, and `ciphertext-only` as interchangeable main taglines.
- Avoid presenting Xdrop as only for humans or only for agents; both are supported users.
- Avoid mixing `private`, `secure`, and `ciphertext-only` as interchangeable main taglines.
- Avoid making `no account required` the primary headline. It is a benefit, not the core definition.
- Avoid shortening the promise to just `secure uploads` because it removes the architecture and
server-trust model that make Xdrop distinct.
- Avoid making `agents` or `CLI` the only headline unless the surface is explicitly about the skill
or terminal workflow.
- Avoid making `agents` the only headline unless the surface is explicitly about the skill or
terminal workflow.
## Tone
@@ -127,10 +120,10 @@ When space is limited, keep these ideas in this order:
Before shipping new product-facing copy, check:
- Does it describe Xdrop as open source?
- Does it clearly frame Xdrop as encrypted file transfer?
- Does it clearly frame Xdrop as end-to-end encrypted file transfer?
- Does it make clear that plaintext names, contents, and keys stay off the server?
- If it mentions the main UX, does it say browser-first rather than implying browser-only?
- If it mentions agent usage, does it describe it as an additional workflow rather than a separate
product?
- If it mentions both supported users, does it present humans and agents clearly and evenly?
- If it mentions either humans or agents alone, is that because the surface is
specifically about that workflow rather than a product-level summary?
- Is `no account required` used as support rather than the core identity?
- Does it avoid introducing a new summary line that conflicts with the canonical one-liner?
+25 -25
View File
@@ -37,20 +37,23 @@
English | <a href="./README.zh.md">汉语</a>
</p>
Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal
workflows, keeping plaintext file names, contents, and keys off the server.
Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping
plaintext file names, contents, and keys off the server.
## Highlights
- End-to-end encryption in the browser before upload.
- End-to-end encrypted file transfer for direct browser sharing and agent-driven handoffs.
- Single-file and folder transfers, including local ZIP downloads for received folders.
- Resumable uploads with browser-local state for interrupted transfers.
- Resumable uploads with local staged state for interrupted web transfers.
- Expiring links, sender-side management, and optional privacy mode after upload.
- S3-compatible object storage support with PostgreSQL and Redis on the backend.
## Use Via Agents
You can use Xdrop through an agent by installing the bundled skill:
Agents can use Xdrop to upload files, return end-to-end encrypted share links, and use Xdrop
links for local decryption.
Install the bundled skill:
```bash
bunx skills add https://github.com/xixu-me/xdrop/tree/main/skills/xdrop
@@ -60,16 +63,7 @@ After that, the agent can use Xdrop from the terminal to:
- Upload local files or directories and return an encrypted share link.
- Download a full Xdrop share link, including `#k=...`, and decrypt it locally.
- Automate repeatable handoff flows without switching to the browser UI.
Useful cases:
- On a cloud server, ask the agent to upload build artifacts, logs, or backups to your Xdrop
instance and send back a temporary link.
- In a remote dev container or CI-like environment, ask the agent to package a directory and move
it through Xdrop instead of setting up ad hoc SCP or public object storage access.
- On your local machine, hand the agent a full Xdrop link and ask it to download the files into a
specific directory.
- Automate repeatable handoff flows without relying on the browser UI.
Example prompts:
@@ -79,6 +73,9 @@ Example prompts:
## How It Works
For browser-based sharing, the core lifecycle works like this. Agents use the same encrypted
transfer format and full share links for terminal uploads and local decryption.
1. A sender creates a transfer in the browser. Xdrop generates a random transfer root key and a
separate link key, optionally strips removable image metadata, and prepares resumable local
state before upload begins.
@@ -101,17 +98,17 @@ sizes, and rate-limit identifiers.
Key technical details:
- **Crypto model:** The browser generates 32-byte random secrets for the transfer root key and the
share-link key. HKDF-SHA-256 derives separate AES-256-GCM keys for the manifest and for each
file, and chunk encryption binds `transferId`, `fileId`, `chunkIndex`, size, and protocol
version as authenticated data.
- **Crypto model:** The client generates 32-byte random secrets for the transfer
root key and the share-link key. HKDF-SHA-256 derives separate AES-256-GCM keys for the
manifest and for each file, and chunk encryption binds `transferId`, `fileId`, `chunkIndex`,
size, and protocol version as authenticated data.
- **Chunked uploads:** The server advertises chunk size, file-count, and transfer-size limits to
the browser. This repo defaults to 8 MiB chunks, up to 100 files, and a 256 MiB encrypted
transfer size cap.
the upload client. This repo defaults to 8 MiB chunks, up to 100 files, and a 256 MiB
encrypted transfer size cap.
- **Resume behavior:** Xdrop persists source files locally in OPFS when available and falls back
to IndexedDB-backed blobs when the staged data is still within the fallback storage limit.
Resume requests ask the API which chunks already exist so the browser only uploads missing work
after a refresh or reopen.
Resume requests ask the API which chunks already exist so the active client only uploads missing
work after a refresh or reopen.
- **Sender controls:** The manage token is returned once on creation and stored as a SHA-256 hash
on the server. Privacy mode can scrub sender-side local controls after upload.
- **Backend responsibilities:** The API never decrypts payloads. It validates transfer state,
@@ -120,9 +117,12 @@ Key technical details:
## System Architecture
The default deployment below shows the human browser flow. Agents interact with the same API and
share-link format for terminal uploads and local decryption.
```mermaid
flowchart LR
subgraph Sender["Sender browser"]
subgraph Sender["Human sender browser"]
Select["Choose files or a folder"]
Worker["Crypto worker<br/>AES-256-GCM + HKDF-SHA-256"]
Local["OPFS / IndexedDB<br/>resume state and local controls"]
@@ -142,7 +142,7 @@ flowchart LR
Postgres["PostgreSQL<br/>transfers, files, chunks, hashed manage tokens"]
Redis["Redis<br/>rate limiting"]
Storage["S3-compatible storage<br/>encrypted manifest and chunk objects"]
Receiver["Receiver browser<br/>opens /t/:id#k=..."]
Receiver["Human receiver browser<br/>opens /t/:id#k=..."]
Browser -->|create/register/finalize| nginx
Browser -->|presigned PUT uploads| nginx
+16 -16
View File
@@ -37,19 +37,21 @@
<a href="./README.md">English</a> | 汉语
</p>
Xdrop 是一个开源加密文件传输应用,默认适用于浏览器,也支持智能体驱动的终端工作流,并确保服务端拿不到明文文件名、文件内容和密钥。
Xdrop 是一款面向人类与智能体的开源端到端加密文件传输应用,它能确保明文的文件名、文件内容以及密钥都不会留存在服务器上。
## 亮点
- 在浏览器中完成端到端加密后再上传。
- 面向直接浏览器分享与智能体交接流程的端到端加密文件传输。
- 支持单文件和文件夹传输,接收文件夹时也可在本地重新打包为 ZIP 下载。
- 上传中断后可依赖浏览器本地状态继续断点续传。
- Web 上传中断后可依赖本地暂存状态继续断点续传。
- 支持到期失效链接、发送方管理,以及上传后的可选隐私模式。
- 后端支持兼容 S3 的对象存储,并使用 PostgreSQL 和 Redis。
## 通过智能体使用
你可以通过安装存储库附带的 skill 将 Xdrop 作为智能体技能来用:
智能体可以使用 Xdrop 上传文件,返回端到端加密的分享链接,并使用 Xdrop 链接进行本地解密。
安装存储库附带的 skill:
```bash
bunx skills add https://github.com/xixu-me/xdrop/tree/main/skills/xdrop
@@ -59,13 +61,7 @@ bunx skills add https://github.com/xixu-me/xdrop/tree/main/skills/xdrop
- 上传本地文件或目录,并返回加密分享链接。
- 接收完整的 Xdrop 分享链接(包含 `#k=...`)后,在本地下载并解密文件。
- 将文件交接流程自动化,而不用每次都切换到浏览器界面操作。
适合的场景包括:
- 在云服务器上让智能体将构建产物、日志或备份上传到你的 Xdrop 实例,并返回一个临时链接给你。
- 在远程开发容器或类似 CI 的环境里,让智能体将某个目录打包后通过 Xdrop 传出来,而不是临时配置 SCP 或公开对象存储权限。
- 在本地机器上直接将完整的 Xdrop 链接交给智能体,让它下载到指定目录并完成解密。
- 将重复性的文件交接流程自动化,而不必依赖浏览器界面操作。
示例指令:
@@ -75,6 +71,8 @@ bunx skills add https://github.com/xixu-me/xdrop/tree/main/skills/xdrop
## 工作原理
对于基于浏览器的分享流程,核心生命周期如下。智能体会复用同一套加密传输格式和完整分享链接来完成终端上传与本地解密。
1. 发送方在浏览器中创建一次传输。Xdrop 会生成随机的传输根密钥和独立的链接密钥,可选地移除图片中可删除的元数据,并在上传开始前准备好可恢复的本地状态。
2. API 创建传输记录,并返回管理令牌和上传限制。浏览器先注册加密后的文件元数据,再分批请求分块上传所需的预签名 URL。PostgreSQL 保存传输、文件和分块元数据,Redis 负责限流,兼容 S3 的对象存储只保存密文对象。
3. 发送方分享完整链接,例如 `/t/:transferId#k=...`。其中 `#k=...` 片段只保留在浏览器端,用于在本地解开传输根密钥。
@@ -86,17 +84,19 @@ Xdrop 不会让服务端接触到明文文件名、路径、内容或解密密
关键技术细节:
- **加密模型:** 浏览器会为传输根密钥和分享链接密钥各生成一个 32 字节随机密钥。随后使用 HKDF-SHA-256 派生出清单和每个文件各自的 AES-256-GCM 密钥,并在分块加密时将 `transferId`、`fileId`、`chunkIndex`、大小和协议版本作为认证附加数据绑定进去。
- **分块上传:** 服务端会向浏览器声明分块大小、文件数限制和传输总大小限制。当前存储库默认使用 8 MiB 分块,最多 100 个文件,加密后的传输总大小上限为 256 MiB。
- **断点续传:** 当浏览器支持 OPFS 时,Xdrop 会将源文件持久化到本地;如果不可用,则在回退存储限制范围内使用基于 IndexedDB 的 Blob 存储。恢复上传时,浏览器会先询问 API 哪些分块已存在,因此即使刷新页面或重新打开浏览器,也只会补传缺失的部分。
- **加密模型:** 客户端会为传输根密钥和分享链接密钥各生成一个 32 字节随机密钥。随后使用 HKDF-SHA-256 派生出清单和每个文件各自的 AES-256-GCM 密钥,并在分块加密时将 `transferId`、`fileId`、`chunkIndex`、大小和协议版本作为认证附加数据绑定进去。
- **分块上传:** 服务端会向上传客户端声明分块大小、文件数限制和传输总大小限制。当前存储库默认使用 8 MiB 分块,最多 100 个文件,加密后的传输总大小上限为 256 MiB。
- **断点续传:** 当浏览器支持 OPFS 时,Xdrop 会将源文件持久化到本地;如果不可用,则在回退存储限制范围内使用基于 IndexedDB 的 Blob 存储。恢复上传时,客户端会先询问 API 哪些分块已存在,因此即使刷新页面或重新打开浏览器,也只会补传缺失的部分。
- **发送方控制:** 管理令牌只会在创建时返回一次,服务端保存的是它的 SHA-256 哈希。开启隐私模式后,发送方本地控制信息可在上传完成后被清除。
- **后端职责:** API 从不解密载荷。它负责校验传输状态、限制速率、签发预签名 URL、存储元数据,并清理已过期或已删除的对象。
## 系统架构
下图展示的是面向人类用户的浏览器流程。智能体会针对同一套 API 与分享链接格式执行终端上传和本地解密。
```mermaid
flowchart LR
subgraph Sender["发送方浏览器"]
subgraph Sender["人类发送方浏览器"]
Select["选择文件或文件夹"]
Worker["加密 Worker<br/>AES-256-GCM + HKDF-SHA-256"]
Local["OPFS / IndexedDB<br/>断点状态与本地控制信息"]
@@ -116,7 +116,7 @@ flowchart LR
Postgres["PostgreSQL<br/>transfers、files、chunks、管理令牌哈希"]
Redis["Redis<br/>限流"]
Storage["兼容 S3 的对象存储<br/>加密清单与分块对象"]
Receiver["接收方浏览器<br/>打开 /t/:id#k=..."]
Receiver["人类接收方浏览器<br/>打开 /t/:id#k=..."]
Browser -->|create/register/finalize| nginx
Browser -->|presigned PUT uploads| nginx
+10 -7
View File
@@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server."
content="Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server."
/>
<meta
name="robots"
@@ -20,26 +20,29 @@
<meta property="og:type" content="website" />
<meta
property="og:title"
content="Open Source Encrypted File Transfer for Browsers and Agents | Xdrop"
content="Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop"
/>
<meta
property="og:description"
content="Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server."
content="Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server."
/>
<meta property="og:image" content="/brand-lockup-horizontal.png" />
<meta property="og:image:alt" content="Xdrop horizontal brand lockup" />
<meta
property="og:image:alt"
content="Open source end-to-end encrypted file transfer for humans and agents. Plaintext file names, contents, and keys stay off the server."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="Open Source Encrypted File Transfer for Browsers and Agents | Xdrop"
content="Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop"
/>
<meta
name="twitter:description"
content="Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server."
content="Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server."
/>
<meta name="twitter:image" content="/brand-lockup-horizontal.png" />
<meta name="theme-color" content="#12140f" />
<title>Open Source Encrypted File Transfer for Browsers and Agents | Xdrop</title>
<title>Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop</title>
</head>
<body>
<div id="root"></div>
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "Xdrop",
"short_name": "Xdrop",
"description": "Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.",
"description": "Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server.",
"theme_color": "#12140f",
"background_color": "#f7f2e8",
"display": "standalone",
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE_NAME = 'xdrop-static-v4'
const CACHE_NAME = 'xdrop-static-v5'
const STATIC_ASSETS = [
'/manifest.webmanifest',
'/brand-symbol.svg',
+4 -3
View File
@@ -10,7 +10,8 @@ const siteUrl = process.env.VITE_SITE_URL?.trim().replace(/\/+$/u, '') || ''
const SITE_NAME = 'Xdrop'
const DEFAULT_OG_IMAGE_PATH = '/brand-lockup-horizontal.png'
const DEFAULT_LOGO_PATH = '/brand-symbol-512.png'
const DEFAULT_OG_IMAGE_ALT = 'Xdrop horizontal brand lockup'
const DEFAULT_OG_IMAGE_ALT =
'Open source end-to-end encrypted file transfer for humans and agents. Plaintext file names, contents, and keys stay off the server.'
const DEFAULT_OG_TYPE = 'website'
const REPOSITORY_URL = 'https://github.com/xixu-me/xdrop'
const DEFAULT_ROBOTS =
@@ -18,9 +19,9 @@ const DEFAULT_ROBOTS =
const PRIVATE_ROBOTS = 'noindex, nofollow, noarchive, nosnippet'
const STRUCTURED_DATA_ID = 'xdrop-structured-data-static'
const HOME_TITLE = 'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop'
const HOME_TITLE = 'Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop'
const HOME_DESCRIPTION =
'Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.'
'Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server.'
const HISTORY_PAGE_TITLE = 'Manage Transfers on This Device | Xdrop'
const HISTORY_PAGE_DESCRIPTION =
'Manage encrypted transfers stored in this browser on this device. There is no account or cross-device history.'
+1
View File
@@ -21,6 +21,7 @@ describe('Shell', () => {
expect(screen.getByRole('link', { name: 'Xdrop' })).toHaveAttribute('href', '/')
expect(screen.getByRole('link', { name: 'Send' })).toHaveAttribute('href', '/')
expect(screen.getByRole('link', { name: 'Transfers' })).toHaveAttribute('href', '/transfers')
expect(screen.getByText('End-to-end encrypted file transfer')).toBeInTheDocument()
expect(screen.getByText('Transfers content')).toBeInTheDocument()
expect(screen.getByRole('link', { name: 'View the license' }).closest('p')).toHaveTextContent(
`Developed by ${AUTHOR_NAME} and released under the GNU Affero General Public License v3.0 only.`,
+1 -1
View File
@@ -20,7 +20,7 @@ export function Shell() {
/>
<span className="brand-copy">
<span className="brand-mark">Xdrop</span>
<span className="brand-note">Encrypted file transfer</span>
<span className="brand-note">End-to-end encrypted file transfer</span>
</span>
</NavLink>
<nav className="nav">
+1 -1
View File
@@ -27,7 +27,7 @@ describe('HomePage', () => {
expect(usePageMetadataMock).toHaveBeenCalledWith(
expect.objectContaining({
title: 'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop',
title: 'Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop',
structuredData: expect.arrayContaining([
expect.objectContaining({
'@type': 'Organization',
@@ -160,16 +160,20 @@ describe('UploadStudio', () => {
draftState.sources = []
})
it('shows browser-first positioning with agent workflow support copy', async () => {
it('shows messaging-aligned positioning for humans and agents', async () => {
renderStudio()
expect(await screen.findByText('Encrypted file transfer.')).toBeInTheDocument()
expect(await screen.findByText('End-to-end encrypted file transfer.')).toBeInTheDocument()
expect(
screen.getByText(
/agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server\./,
/end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server\./,
),
).toBeInTheDocument()
expect(
screen.getByText(
/Humans can use Xdrop in the browser for direct sharing, and agents can use Xdrop/i,
),
).toBeInTheDocument()
expect(screen.getByText(/Use Xdrop in the browser for normal sharing, or/i)).toBeInTheDocument()
expect(screen.getByRole('link', { name: 'through an agent' })).toHaveAttribute(
'href',
'https://github.com/xixu-me/xdrop?tab=readme-ov-file#use-via-agents',
@@ -244,10 +244,10 @@ export function UploadStudio() {
<div className="upload-hero-shell">
<div className="page-hero-copy upload-hero-copy">
<p className="eyebrow">End-to-end encryption</p>
<h1 className="page-hero-title">Encrypted file transfer.</h1>
<h1 className="page-hero-title">End-to-end encrypted file transfer.</h1>
<p className="lead">{PROJECT_ONE_LINER}</p>
<p className="muted">
Use Xdrop in the browser for normal sharing, or{' '}
Humans can use Xdrop in the browser for direct sharing, and agents can use Xdrop{' '}
<a
className="inline-link"
href="https://github.com/xixu-me/xdrop?tab=readme-ov-file#use-via-agents"
@@ -256,8 +256,7 @@ export function UploadStudio() {
>
through an agent
</a>{' '}
when you need to move files out of a cloud server, remote container, or automated
terminal workflow.
in cloud servers, remote containers, and automated terminal workflows.
</p>
</div>
</div>
+6 -6
View File
@@ -4,13 +4,12 @@
export const SITE_NAME = 'Xdrop'
export const PROJECT_ONE_LINER =
'Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server.'
export const SHORT_POSITIONING_SUMMARY =
'Browser-first encrypted file transfer, with agent-ready terminal workflows.'
'Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server.'
export const SHORT_POSITIONING_SUMMARY = 'End-to-end encrypted file transfer for humans and agents.'
export const TERMINAL_SUPPORT_BLURB =
'Use Xdrop in the browser for normal sharing, or through an agent when you need to move files out of a cloud server, remote container, or automated terminal workflow.'
'Agents can use Xdrop to upload files, return end-to-end encrypted share links, and use Xdrop links for local decryption.'
export const DEFAULT_SEO_TITLE =
'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop'
'Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop'
export const DEFAULT_SEO_DESCRIPTION = PROJECT_ONE_LINER
export const HISTORY_PAGE_TITLE = 'Manage Transfers on This Device | Xdrop'
export const HISTORY_PAGE_DESCRIPTION =
@@ -25,7 +24,8 @@ export const NOT_FOUND_PAGE_TITLE = 'Page Not Found | Xdrop'
export const NOT_FOUND_PAGE_DESCRIPTION =
'The address does not map to a page in Xdrop. If this came from a shared transfer, ask the sender to resend the complete share details.'
export const DEFAULT_OG_IMAGE_PATH = '/brand-lockup-horizontal.png'
export const DEFAULT_OG_IMAGE_ALT = 'Xdrop horizontal brand lockup'
export const DEFAULT_OG_IMAGE_ALT =
'Open source end-to-end encrypted file transfer for humans and agents. Plaintext file names, contents, and keys stay off the server.'
export const DEFAULT_LOGO_PATH = '/brand-symbol-512.png'
export const REPOSITORY_URL = 'https://github.com/xixu-me/xdrop'
export const AUTHOR_NAME = 'Xi Xu'
@@ -27,7 +27,7 @@ describe('usePageMetadata', () => {
render(
<MemoryRouter initialEntries={['/']}>
<MetadataProbe
title="Open Source Encrypted File Transfer for Browsers and Agents | Xdrop"
title="Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop"
description="SEO test description"
structuredData={{
'@context': 'https://schema.org',
@@ -39,12 +39,12 @@ describe('usePageMetadata', () => {
)
expect(document.title).toBe(
'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop',
'Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop',
)
expect(readMetaByName('description')).toBe('SEO test description')
expect(readMetaByName('robots')).toContain('index, follow')
expect(readMetaByProperty('og:title')).toBe(
'Open Source Encrypted File Transfer for Browsers and Agents | Xdrop',
'Open Source End-to-End Encrypted File Transfer for Humans and Agents | Xdrop',
)
expect(readMetaByProperty('og:url')).toBe(new URL('/', window.location.origin).toString())
expect(readCanonical()).toBe(new URL('/', window.location.origin).toString())
@@ -91,7 +91,7 @@ describe('usePageMetadata', () => {
expect(document.head.querySelectorAll('meta[name="description"]')).toHaveLength(1)
expect(document.head.querySelectorAll('link[rel="canonical"]')).toHaveLength(1)
expect(readMetaByName('description')).toContain(
'Xdrop is an open source encrypted file transfer app for browsers and agent-driven terminal workflows',
'Xdrop is an open source end-to-end encrypted file transfer app for humans and agents',
)
expect(readMetaByName('robots')).toContain('index, follow')
expect(readMetaByProperty('og:type')).toBe('website')
+2 -2
View File
@@ -26,8 +26,8 @@ export default defineConfig({
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
globals: true,
// Coverage runs can stall with forked workers on Windows, so prefer threads there.
pool: process.platform === 'win32' ? 'threads' : 'forks',
// Threads worker startup is flaky on Windows in Vitest 4, and forks also match CI.
pool: 'forks',
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xdrop",
"description": "Open source encrypted file transfer app for browsers and agent-driven terminal workflows, keeping plaintext file names, contents, and keys off the server",
"description": "Xdrop is an open source end-to-end encrypted file transfer app for humans and agents, keeping plaintext file names, contents, and keys off the server.",
"private": true,
"license": "AGPL-3.0-only",
"homepage": "https://github.com/xixu-me/xdrop",
+3 -1
View File
@@ -119,7 +119,8 @@ test('preserves folder paths, downloads a zip, and manages transfers', async ({
const descriptorBefore = await getPublicDescriptor(request, transferId)
const expiryBefore = Date.parse(descriptorBefore.expiresAt)
await card.getByRole('button', { name: 'Set expiry to 1 week from now' }).click()
await card.getByRole('link', { name: 'Open share page' }).click()
await page.getByRole('button', { name: 'Set expiry to 1 week from now' }).click()
await expect
.poll(async () => Date.parse((await getPublicDescriptor(request, transferId)).expiresAt))
.toBeGreaterThan(expiryBefore)
@@ -127,6 +128,7 @@ test('preserves folder paths, downloads a zip, and manages transfers', async ({
await receiver.reload({ waitUntil: 'networkidle' })
await expect(receiver.getByText(transferName).first()).toBeVisible()
await page.goto(`${BASE_URL}/transfers`, { waitUntil: 'networkidle' })
await card.getByRole('button', { name: 'Delete' }).click()
await card.getByRole('button', { name: 'Confirm delete' }).click()
await expect(page.locator('section').filter({ hasText: transferName })).toHaveCount(0)