
Web Share Target 的配置与数据接收原理
要让 PWA 出现在系统分享菜单中,必须在 Web App Manifest 中声明 share_target 成员。这个成员描述了两个关键部分:action 指向接收分享请求的页面 URL,params 定义系统如何把分享数据编码成请求参数。最简单的配置只接收纯文本和链接:
{
"share_target": {
"action": "/share-receiver.html",
"method": "GET",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
}
}
当用户在另一个 App(如浏览器、相册)点击分享并选择该 PWA 时,浏览器会打开 /share-receiver.html?title=...&text=...&url=...,页面可以通过解析 URL 查询字符串拿到数据。但如果分享内容包含图片或文件,需要使用 POST 方法和 enctype="multipart/form-data",此时数据由 Service Worker 拦截并转发给页面。分享事件本质上是一个带特殊标记的 navigator.share 反向流程,PWA 开发者需要处理好不同分享来源(文本、链接、图片)对应的字段映射。
需要在 manifest.json 中完整定义 share_target 后,还必须确保 PWA 已被用户安装到设备上,否则分享目标不会出现在列表中。Service Worker 的 fetch 事件可以捕获 POST 方式的分享请求,此时 event.request 是一个包含表单数据的 Request 对象。常见的做法是在 Service Worker 中调用 event.respondWith() 来返回一个 Response,同时把数据保存到 IndexedDB 或通过 postMessage 传回页面。但由于我们后续要配合 SQLite 使用,最好的方式是将整个请求重定向到一个处理页面,并在页面中通过 navigator.serviceWorker.controller.postMessage 拿到数据,这样可以避免 Service Worker 中无法直接操作 DOM 和访问 sql.js 的限制。
使用 sql.js 在浏览器中初始化 SQLite 数据库
sql.js 是 SQLite 的 JavaScript 移植版,它通过 Emscripten 编译 C 代码为 WebAssembly,在浏览器内存中模拟完整的数据库引擎。安装方式很简单:npm install sql.js,或者在 Vanilla JS 项目中直接引入 CDN 脚本。初始化时,我们通常不希望每次都创建空数据库,而是尝试从持久化存储中加载已有的数据库文件。这里可以利用 File System Access API 或 Origin Private File System (OPFS),后者无需用户授权,最适合 PWA 场景。以下代码展示了初始化流程:
async function initDatabase() {
// 获取 OPFS 根目录
const opfsRoot = await navigator.storage.getDirectory();
let fileHandle;
try {
fileHandle = await opfsRoot.getFileHandle('share_data.db');
} catch {
// 文件不存在,创建一个新的
fileHandle = await opfsRoot.getFileHandle('share_data.db', { create: true });
}
const file = await fileHandle.getFile();
const buffer = await file.arrayBuffer();
const SQL = await initSqlJs();
// 如果有现有数据,使用 buffer 初始化,否则创建新库
const db = buffer.byteLength > 0 ? new SQL.Database(new Uint8Array(buffer)) : new SQL.Database();
// 建表(如果不存在)
db.run(`CREATE TABLE IF NOT EXISTS shared_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
url TEXT,
text_content TEXT,
shared_at DATETIME DEFAULT CURRENT_TIMESTAMP,
source_app TEXT
)`);
return { db, fileHandle, SQL };
}
上面代码中,navigator.storage.getDirectory() 返回 OPFS 的根目录,数据库文件就放在根下。每次打开页面时,读取整个文件字节,传递给 SQL.Database 构造函数,这样就能恢复之前保存的所有数据。执行 SQL 语句使用 db.run(),对于查询则用 db.exec() 获取结果数组。需要注意的是,每次写操作后必须把数据库重新序列化并回写到 OPFS,否则关闭页面后数据会丢失。
持久化保存可以封装成一个异步函数,每次 INSERT 或 DELETE 后调用:
async function saveDatabase(db, fileHandle) {
const data = db.export();
const writable = await fileHandle.createWritable();
await writable.write(data.buffer);
await writable.close();
}
这个 export() 方法返回 Uint8Array,表示整个 SQLite 数据库文件的二进制内容。利用 OPFS 可写流直接写入,保证了数据持久化。相比 IndexedDB,SQL 查询的优势在于处理复杂条件筛选、聚合统计时更加灵活。比如我们需要统计每个来源 App 分享了多少条记录,一行 SELECT source_app, COUNT(*) FROM shared_items GROUP BY source_app 就能完成,而用 IDB 需要手动遍历游标并维护计数对象。
完整数据流:从分享事件到 SQLite 存储
当用户分享一个链接时,系统会触发 PWA 的 share_target 流程。假设我们使用 GET 方式接收,那么直接通过 URL 参数获取值,然后存入 SQLite:
window.addEventListener('load', async () => {
const params = new URLSearchParams(location.search);
const title = params.get('title') || '';
const url = params.get('url') || '';
const text = params.get('text') || '';
if (title || url || text) {
const { db, fileHandle, SQL } = await initDatabase();
db.run('INSERT INTO shared_items (title, url, text_content, source_app) VALUES (?, ?, ?, ?)',
[title, url, text, 'Web Share']);
await saveDatabase(db, fileHandle);
// 重定向到主列表页,避免刷新后重复插入
window.location.href = '/index.html';
}
});
对于 POST 方式接收文件分享的场景,需要在 Service Worker 中拦截 fetch 事件,然后通过 Client 发消息给页面:
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/share-receiver') && event.request.method === 'POST') {
event.respondWith(
(async () => {
const formData = await event.request.formData();
const clients = await self.clients.matchAll({ type: 'window' });
if (clients.length > 0) {
clients[0].postMessage({
type: 'SHARE_DATA',
payload: {
title: formData.get('title'),
text: formData.get('text'),
url: formData.get('url'),
file: formData.get('file')
}
});
}
return Response.redirect('/share-receiver.html', 303);
})()
);
}
});
页面监听 message 事件,提取数据并写入 SQLite 即可。注意 redirect 可以防止浏览器将 POST 页面加入历史记录,避免重复提交。这个模式将 Service Worker 作为中间层,既不破坏原有数据流向,又能将分享内容安全地传递给前端页面。
为了提升查询性能,我们可以在数据库初始化阶段为 shared_at 和 source_app 字段创建索引:CREATE INDEX IF NOT EXISTS idx_shared_at ON shared_items(shared_at);,这样按时间排序取出最新条目时速度会快很多。当数据库体积较大(比如存了数万条文字记录)时,使用索引可以显著降低读取延迟。
构建完整 PWA 与调试注意事项
要让整个方案真正可用,还需要补充 Service Worker 的安装和激活逻辑、静态资源缓存策略以及一个基本的前端列表界面。注册 Service Worker 时注意作用域必须包含 share_target 的 action 路径,否则无法拦截分享请求。在列表页,可以循环执行 SELECT * FROM shared_items ORDER BY shared_at DESC LIMIT 50 并渲染成卡片。还可以加入简单的文本搜索功能,利用 SQLite 的 LIKE 或 FTS5 扩展实现全文检索,不过 FTS5 需要额外编译,默认的 sql.js 不包含此模块。
调试 Web Share Target 时,可以使用 Chrome DevTools 的 Application 面板模拟分享:在 Manifest 测试区域可以触发 share 动作。对于真实设备测试,务必在手机 Chrome 中访问已安装的 PWA(通过“添加到主屏幕”)。如果分享目标没有出现,检查 manifest 是否正确设置了 start_url、scope,以及是否配置了正确的 share_target 结构。另外,用 Android 调试时,Chrome 的“添加到主屏幕”会创建标准 PWA 快捷方式,而某些浏览器(如小米自带浏览器)可能不支持 Web Share Target,实际使用应以 Chrome 或 Edge 等主流浏览器为准。
最后,记得在页面卸载或关闭前调用 db.close() 释放内存,尽管 sql.js 的库在页面销毁时会自动回收,但显式关闭可以避免一些竞态问题。整个项目可以扩展为多标签页同步、通过 BroadcastChannel 实时更新列表等功能,这些都与 SQLite 本身无关,但会让用户感受到原生应用的流畅度。通过本文介绍的架构,你已经拥有一个完全离线、零成本、基于 SQL 的分享内容管理器,可以轻松部署到静态服务器,甚至可以打包成安卓 APK 借助 Trusted Web Activity 上架应用商店。
SQLiteWeb_Share_TargetPWA修改时间:2026-08-12 13:55:30