feat: 统一 Commilitia Drop 全客户端命名与分发
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
import SwiftUI
|
||||
|
||||
// 原生 macOS 登录:以 Commilitia 账户(Auth Broker)登录为主,免相机——ASWebAuthenticationSession
|
||||
// 弹系统授权窗,回调经 cdrop:// 回到 app(见 BrokerLogin / AuthManager.startBrokerLogin)。不复用
|
||||
// 弹系统授权窗,回调经 commilitia-drop:// 回到 app(见 BrokerLogin /
|
||||
// AuthManager.startBrokerLogin)。不复用
|
||||
// iOS 的 LoginView(其二维码 / UIImage 路径为 iOS 专属,且没人扫 Mac 屏幕来登录 Mac 自己)。
|
||||
struct MacLoginView: View
|
||||
{
|
||||
|
||||
+31
-9
@@ -1,6 +1,9 @@
|
||||
# cdrop iOS 真机分发手册
|
||||
# Commilitia Drop iPhone/iPad 真机分发手册
|
||||
|
||||
把 cdrop 装到你自己的 iPhone(开发签名)。代码侧全部就绪并经模拟器验证;真机只差**账号 + 签名**这一层。本手册走 **ASC API Key 自动 provisioning + 全 CLI** 路线——一把 App Store Connect API Key,`-allowProvisioningUpdates` 自动登记连接的设备 + 创建/更新 App ID / App Group / Push / profile / 开发证书,构建装机一条命令,**全程不碰 Xcode GUI、不必手动建 profile**(绕过手动 profile「设备列表只剩 Mac」的坑)。
|
||||
把 Commilitia Drop 覆盖安装到已登记的 iPhone 与 iPad(开发签名)。本手册走
|
||||
**既有 Apple Development 证书 + ASC API Key provisioning + 全 CLI** 路线:签名身份固定为
|
||||
钥匙串内既有证书的 SHA-1;ASC Key 只用于登记设备、更新 App ID/App Group/Push 能力和 profile。
|
||||
构建装机一条命令,**全程不碰 Xcode GUI、不新建开发证书、不修改私钥 ACL**。
|
||||
|
||||
## 包名与标识
|
||||
|
||||
@@ -15,13 +18,24 @@
|
||||
## 前置
|
||||
- 付费 Apple Developer Program($99/年)。
|
||||
- 一台 iPhone(iOS 26)+ Mac(Xcode 26 命令行工具)。
|
||||
- 一个已登录的 cdrop(网页 / 桌面)——用来扫码批准本机登录。
|
||||
- 一个已登录的 Commilitia Drop(网页 / 桌面)——用来扫码批准本机登录。
|
||||
|
||||
---
|
||||
|
||||
## A. ASC API Key + .env(核心,一次性)
|
||||
## A. 既有签名身份 + ASC API Key + .env
|
||||
|
||||
`-allowProvisioningUpdates` 凭一把 App Store Connect API Key 替你在门户自动登记设备、建/改 App ID(含 App Groups + Push 能力)、建 App Group、建 profile、建/下开发证书——**不必手动建 profile**。
|
||||
先用 CLI 列出当前钥匙串中可用的代码签名身份:
|
||||
|
||||
```sh
|
||||
security find-identity -v -p codesigning
|
||||
```
|
||||
|
||||
找到必须复用的 `Apple Development` 证书,记录行首 40 位 SHA-1。若身份不存在或私钥不可用,停止
|
||||
构建并解锁当前登录钥匙串;不要让自动签名生成替代证书。
|
||||
|
||||
`-allowProvisioningUpdates` 凭 App Store Connect API Key 自动登记设备、建/改 App ID(含 App
|
||||
Groups + Push 能力)、更新 App Group 与 profile,免去手动 profile 的设备选择问题。签名证书由
|
||||
`CDROP_APPLE_DEVELOPMENT_IDENTITY` 固定,不由 provisioning 自行选择。
|
||||
|
||||
**生成 API Key**:appstoreconnect.apple.com → **用户和访问** → **集成** → **App Store Connect API** → **团队密钥** → 「+」→ 角色 **Admin**(要能管设备 / profile / 标识符)→ 命名 → 生成 → 下载 `AuthKey_XXXXX.p8`(**仅一次**)。记下三样:
|
||||
|
||||
@@ -36,11 +50,14 @@ CDROP_TEAM_ID=ABCDE12345
|
||||
CDROP_ASC_KEY_PATH=/绝对路径/AuthKey_XXXXX.p8
|
||||
CDROP_ASC_KEY_ID=XXXXXXXXXX
|
||||
CDROP_ASC_ISSUER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
CDROP_APPLE_DEVELOPMENT_IDENTITY=<既有证书的40位SHA-1>
|
||||
```
|
||||
|
||||
`.p8` 放仓库外即可,`CDROP_ASC_KEY_PATH` 指它绝对路径(落本目录则被 `*.p8` 忽略)。
|
||||
|
||||
> 你若已在门户手动建过 App ID / App Group / 证书,自动 provisioning 会复用、不冲突。**「手动建 profile 时设备列表只剩 Mac」**=那台 iPhone 在 **Devices** 里被登记成了 macOS 平台(登记表单 Platform 默认 macOS);自动 provisioning 直接按连接的真机重登记,绕过此坑。
|
||||
> 你若已在门户手动建过 App ID / App Group,自动 provisioning 会复用或更新。**「手动建
|
||||
> profile 时设备列表只剩 Mac」**=那台 iPhone 在 **Devices** 里被登记成了 macOS 平台(登记
|
||||
> 表单 Platform 默认 macOS);自动 provisioning 按连接的真机登记,绕过此坑。
|
||||
|
||||
**顺手:APNs Auth Key**(发推送用,与上面 ASC Key 是两码事):门户 **Keys** → + → 勾 **Apple Push Notifications service** → 下载 `AuthKey_YYYYY.p8`(仅一次),记 Key ID + Issuer ID(服务端配,见 C)。
|
||||
|
||||
@@ -53,14 +70,19 @@ just ios-devices # 手机插 USB,读 UDID
|
||||
just ios-device <你的设备UDID> # 自动 provisioning 构建 + devicectl 装机
|
||||
```
|
||||
|
||||
- `just ios-device` = `xcodegen generate` → `xcodebuild -allowProvisioningUpdates`(凭 .env 的 ASC Key 自动签名 + 登记设备 + 建 profile)→ `xcrun devicectl device install app`。无 Xcode GUI。
|
||||
- `just ios-device` 先确认 `.env` 指定的既有证书仍在钥匙串,再执行 `xcodegen generate` →
|
||||
`xcodebuild -allowProvisioningUpdates`(固定该 SHA-1 签名,ASC Key 更新设备与 profile)→
|
||||
`xcrun devicectl device install app`。无 Xcode GUI。
|
||||
- 首次装机后,iPhone 上首启该开发者 app 即可直接跑(付费 ADP 开发证书,无需手动「信任开发者」)。
|
||||
|
||||
若出现 `errSecInternalComponent`,优先在当前用户的普通 Terminal 解锁登录钥匙串,然后重试原证书。
|
||||
不要创建新证书、修改钥匙串 ACL,或改用 Xcode GUI。
|
||||
|
||||
---
|
||||
|
||||
## C. 服务端前置
|
||||
|
||||
1. **引擎可达**:真机上引擎 WebView 加载 `https://drop.commilitia.net/engine.html`——该文件随 cdrop 二进制部署到 prod(`//go:embed` 进 binary,`just docker-image` 含最新 `dist`)。手机要用,prod 须是含本轮改动的最新部署。
|
||||
1. **引擎可达**:真机上引擎 WebView 加载 `https://drop.commilitia.net/engine.html`——该文件随 Commilitia Drop 服务端二进制部署到 prod(`//go:embed` 进 binary,`just docker-image` 含最新 `dist`)。手机要用,prod 须是含本轮改动的最新部署。
|
||||
2. **APNs 真发**:把 A 里那把 **APNs Auth Key** `.p8` 放到服务器,给后端容器配 `CDROP_APNS_KEY_PATH` / `CDROP_APNS_KEY_ID` / `CDROP_APNS_TEAM_ID` / `CDROP_APNS_TOPIC=net.commilitia.Commilitia-Drop` / `CDROP_APNS_ENV`(Xcode 开发构建的 device token 属 **sandbox**,故联调填 `sandbox`)。缺配置则推送惰性关闭,其余功能照常。
|
||||
3. 本机联调可选:环境变量 `CDROP_ENGINE_URL` 指向可达引擎。
|
||||
|
||||
@@ -71,7 +93,7 @@ just ios-device <你的设备UDID> # 自动 provisioning 构建 + devicectl
|
||||
代码已实现,下列是真机才能验的点:
|
||||
|
||||
- [ ] 液态玻璃真机渲染(陀螺仪高光 / 动效)。
|
||||
- [ ] 扫码登录:本机显码 → 已登录 cdrop 扫码批准 → 进主界面(强制 full/persist)。
|
||||
- [ ] 扫码登录:本机显码 → 已登录 Commilitia Drop 扫码批准 → 进主界面(强制 full/persist)。
|
||||
- [ ] **本地网络权限 + 同内网直连**(R-iOS-6):首次同内网传输弹 `NSLocalNetworkUsageDescription`;授予后 ICE 收 host 候选、走真直连;拒绝则回退中继仍可传。
|
||||
- [ ] 发送 / 接收:选文件 → 选设备 → 对端收到;对端发来落 Files(Documents)。
|
||||
- [ ] **大文件流式发送**(R-iOS-4 已实现):发大文件,WebView 内存应有界(按 Range 块拉取,不整文件入内存)。
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<string>Commilitia Drop</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
||||
@@ -59,7 +59,7 @@ final class ShareViewController: UIViewController
|
||||
// (立即 completeRequest 会拆掉扩展进程、可能取消尚未真正开始的唤起)。文件留在收件箱供主 app 接手。
|
||||
private func openAndFinish()
|
||||
{
|
||||
if let url = URL(string: "cdrop://share") { openViaResponder(url) }
|
||||
if let url = URL(string: "commilitia-drop://share") { openViaResponder(url) }
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35)
|
||||
{ [weak self] in
|
||||
self?.extensionContext?.completeRequest(returningItems: nil)
|
||||
|
||||
@@ -135,7 +135,7 @@ final class AuthManager
|
||||
let verifier = PKCE.randomToken(64)
|
||||
let challenge = PKCE.challenge(for: verifier)
|
||||
let state = PKCE.randomToken(24)
|
||||
let redirectURI = "cdrop://auth-callback"
|
||||
let redirectURI = "commilitia-drop://auth-callback"
|
||||
|
||||
var comp = URLComponents(string: cfg.brokerURL.trimmingTrailingSlash() + "/device/authorize")!
|
||||
comp.queryItems = [
|
||||
@@ -144,13 +144,13 @@ final class AuthManager
|
||||
URLQueryItem(name: "state", value: state),
|
||||
URLQueryItem(name: "code_challenge", value: challenge),
|
||||
URLQueryItem(name: "code_challenge_method", value: "S256"),
|
||||
URLQueryItem(name: "description", value: "Commilitia Drop iOS"),
|
||||
URLQueryItem(name: "description", value: "Commilitia Drop"),
|
||||
]
|
||||
guard let authURL = comp.url else { throw BrokerLoginError.incompleteConfig }
|
||||
|
||||
let flow = BrokerAuthFlow()
|
||||
brokerFlow = flow
|
||||
let callback = try await flow.run(url: authURL, callbackScheme: "cdrop")
|
||||
let callback = try await flow.run(url: authURL, callbackScheme: "commilitia-drop")
|
||||
brokerFlow = nil
|
||||
if gen != loginGeneration { return }
|
||||
|
||||
|
||||
@@ -8,13 +8,15 @@ import AppKit
|
||||
#endif
|
||||
|
||||
// 应用内 Broker 登录(device-authorization + PKCE,对齐桌面 desktop/platform/oauth.go)。iOS 无法
|
||||
// 跑 loopback 监听,故用 ASWebAuthenticationSession + 自定义 scheme 回调(cdrop://auth-callback):
|
||||
// 1. GET {broker}/device/authorize?app=cdrop&redirect_uri=cdrop://auth-callback&state&code_challenge&S256
|
||||
// 2. 用户在 broker SSO 授权 → 回调 cdrop://auth-callback?code=..&state=..
|
||||
// 跑 loopback 监听,故用 ASWebAuthenticationSession + 自定义 scheme 回调
|
||||
// (commilitia-drop://auth-callback):
|
||||
// 1. GET {broker}/device/authorize?app=commilitia-drop&redirect_uri=commilitia-drop://auth-callback&...
|
||||
// 2. 用户在 broker SSO 授权 → 回调 commilitia-drop://auth-callback?code=..&state=..
|
||||
// 3. POST {broker}/device/token {code, code_verifier, redirect_uri} → bootstrap 机器令牌
|
||||
// 4. POST {api}/api/auth/device-session(Bearer bootstrap)→ cdrop 设备会话(带真名 / device_id)
|
||||
// 这样手机上直接输账号登录,无需另一台设备扫码批准。
|
||||
// 前置:broker 须为 cdrop 应用注册 redirect_uri cdrop://auth-callback(同桌面 loopback 须注册)。
|
||||
// 前置:broker 须为 Commilitia Drop 应用注册 redirect_uri
|
||||
// commilitia-drop://auth-callback(同桌面 loopback 须注册)。
|
||||
|
||||
enum BrokerLoginError: Error
|
||||
{
|
||||
@@ -35,7 +37,8 @@ final class BrokerAuthFlow: NSObject, ASWebAuthenticationPresentationContextProv
|
||||
{
|
||||
private var session: ASWebAuthenticationSession?
|
||||
|
||||
// 打开授权页,等回调 URL(cdrop://auth-callback?code=..&state=..)。用户取消 / 失败抛错。
|
||||
// 打开授权页,等回调 URL(commilitia-drop://auth-callback?code=..&state=..)。
|
||||
// 用户取消 / 失败抛错。
|
||||
func run(url: URL, callbackScheme: String) async throws -> URL
|
||||
{
|
||||
try await withCheckedThrowingContinuation
|
||||
|
||||
@@ -72,11 +72,11 @@ struct AppRoot: View
|
||||
{ _, uid in
|
||||
if let uid { engine.activateRecords(userId: uid) }
|
||||
}
|
||||
// Share Extension 深链:cdrop://share 唤起 → 导入收件箱待发文件(RootView 据此弹选设备)。
|
||||
// Share Extension 深链:commilitia-drop://share 唤起 → 导入收件箱待发文件。
|
||||
// 放在 AppRoot 以便登录 / 未登录任一界面都接得到;未登录时文件留收件箱,登录后 onAppear 补扫。
|
||||
.onOpenURL
|
||||
{ url in
|
||||
guard url.scheme == "cdrop", url.host == "share" else { return }
|
||||
guard url.scheme == "commilitia-drop", url.host == "share" else { return }
|
||||
engine.loadPendingShares()
|
||||
}
|
||||
// 被其他设备登出的推送到达(AppDelegate 发):前台即时清会话 + 复位引擎 → 回登录页。
|
||||
|
||||
@@ -193,7 +193,7 @@ final class EngineController: NSObject
|
||||
private var webView: WKWebView?
|
||||
private let downloads = DownloadManager()
|
||||
|
||||
// 待发文件暂存:id → 安全作用域 URL(文件选择器选中的文件)。引擎经 cdrop-file://<id>
|
||||
// 待发文件暂存:id → 安全作用域 URL。引擎经 commilitia-drop-file://<id>
|
||||
// 回取字节(下方 WKURLSchemeHandler 供给)。
|
||||
private var outgoing: [String: URL] = [:]
|
||||
|
||||
@@ -271,7 +271,7 @@ final class EngineController: NSObject
|
||||
}
|
||||
|
||||
// makeWebView:构建离屏 WebView——注入 __CDROP_BOOT__(device_type:"ios")、注册消息
|
||||
// 处理器、注册 cdrop-file 自定义 scheme(发送侧给 JS 喂文件字节)。幂等。
|
||||
// 处理器、注册 commilitia-drop-file 自定义 scheme(发送侧给 JS 喂文件字节)。幂等。
|
||||
func makeWebView() -> WKWebView
|
||||
{
|
||||
if let existing = webView { return existing }
|
||||
@@ -297,7 +297,7 @@ final class EngineController: NSObject
|
||||
|
||||
let cfg = WKWebViewConfiguration()
|
||||
cfg.userContentController = ucc
|
||||
cfg.setURLSchemeHandler(self, forURLScheme: "cdrop-file")
|
||||
cfg.setURLSchemeHandler(self, forURLScheme: "commilitia-drop-file")
|
||||
|
||||
// 迁移到 Auth Broker 后引擎自刷走注入的 refresh_token(POST /api/auth/refresh 代理
|
||||
// broker),不再依赖 cdrop_session cookie,故无需把 URLSession 的 cookie 同步进 WebView。
|
||||
@@ -672,7 +672,7 @@ final class EngineController: NSObject
|
||||
webView?.evaluateJavaScript("window.__cdropEngineEvent(\(jsString(name)), \(json));")
|
||||
}
|
||||
|
||||
// sendFile:暂存选中文件、把 cdrop-file://<id> 引用 + 目标设备交给引擎发起传输。
|
||||
// sendFile:暂存选中文件、把 commilitia-drop-file://<id> 引用 + 目标设备交给引擎发起传输。
|
||||
func sendFile(to target: String, fileURL: URL)
|
||||
{
|
||||
let ref = stageOutgoingFile(fileURL)
|
||||
@@ -687,7 +687,7 @@ final class EngineController: NSObject
|
||||
])
|
||||
}
|
||||
|
||||
// Share Extension 经 App Group 收件箱交接来的待发文件(经 cdrop://share 唤起后导入)。
|
||||
// Share Extension 经 App Group 收件箱交接来的待发文件(经 commilitia-drop://share 唤起后导入)。
|
||||
// 非空即触发主界面的「选设备发送」弹窗。
|
||||
var pendingShareFiles: [URL] = []
|
||||
|
||||
@@ -730,7 +730,7 @@ final class EngineController: NSObject
|
||||
}
|
||||
|
||||
// 图库选择(#14):PhotosPicker 给的是 PhotosPickerItem,须先 loadTransferable 取字节,再落
|
||||
// 一个临时文件,才能复用既有 cdrop-file scheme 发送链路(按 Range 读盘、整文件不入内存)。
|
||||
// 一个临时文件,才能复用既有 commilitia-drop-file scheme 发送链路。
|
||||
// 返回临时文件 URL(在 temporaryDirectory,系统回收);写盘失败回 nil。
|
||||
func stagePhotoData(_ data: Data, suggestedName: String) -> URL?
|
||||
{
|
||||
@@ -743,18 +743,18 @@ final class EngineController: NSObject
|
||||
catch { return nil }
|
||||
}
|
||||
|
||||
// 暂存待发文件,返回引擎用的 cdrop-file://<id> 引用。文件选择器给的 URL 是安全作用域
|
||||
// 暂存待发文件,返回引擎用的 commilitia-drop-file://<id> 引用。
|
||||
// 资源,需 start...Access 才能读。
|
||||
private func stageOutgoingFile(_ url: URL) -> String
|
||||
{
|
||||
let id = UUID().uuidString
|
||||
_ = url.startAccessingSecurityScopedResource()
|
||||
outgoing[id] = url
|
||||
return "cdrop-file://\(id)"
|
||||
return "commilitia-drop-file://\(id)"
|
||||
}
|
||||
|
||||
// 发送侧按 [start,end) seek 读暂存的待发文件、回 base64(供 bridgeFileSource 经桥取片)。
|
||||
// url 形如 cdrop-file://<id>,id 即 stageOutgoingFile 的暂存键。无该文件 / 读失败抛错。
|
||||
// url 形如 commilitia-drop-file://<id>,id 即 stageOutgoingFile 的暂存键。
|
||||
fileprivate func readOutgoingSlice(url: String, start: Int, end: Int) throws -> String
|
||||
{
|
||||
guard let id = URL(string: url)?.host, let fileURL = outgoing[id]
|
||||
@@ -787,7 +787,7 @@ final class EngineController: NSObject
|
||||
return e
|
||||
}
|
||||
|
||||
// 把 cdrop-file://<id> 引用解析回暂存文件的沙盒绝对路径(Go 据此直接读盘发送)。安全作用域
|
||||
// 把 commilitia-drop-file://<id> 引用解析回暂存文件的沙盒绝对路径。
|
||||
// 访问已在 stageOutgoingFile 开启、持续到 reset,故同进程的 Go 可直接 os.Open 该路径。无对应
|
||||
// 暂存项返回空串。
|
||||
private func resolveOutgoingPath(_ ref: String) -> String
|
||||
@@ -871,7 +871,7 @@ extension EngineController: WKScriptMessageHandler
|
||||
end: Self.intOf(payload["end"]))
|
||||
resolve(id: id, ok: true, value: b64)
|
||||
case "p2pStartOutgoing":
|
||||
// 发起原生发送:filePath 为 cdrop-file://<id>,解析回沙盒绝对路径交引擎直接读盘。引擎
|
||||
// 发起原生发送:filePath 为 commilitia-drop-file://<id>,解析回沙盒绝对路径。
|
||||
// 调用立即返回(libwebrtc 异步),completion 在 offer 已铸+发出后回,经 resolveOnMain 回交。
|
||||
let engine = nativeTransferEngine()
|
||||
let path = resolveOutgoingPath(payload["filePath"] as? String ?? "")
|
||||
@@ -1133,11 +1133,11 @@ extension EngineController: WKScriptMessageHandler
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 发送侧自定义 scheme(cdrop-file)
|
||||
// MARK: - 发送侧自定义 scheme(commilitia-drop-file)
|
||||
|
||||
extension EngineController: WKURLSchemeHandler
|
||||
{
|
||||
// 发送侧:原生把暂存的待发文件经 cdrop-file://<id> 喂给引擎(JS fetch 它得到字节,
|
||||
// 发送侧:原生把暂存文件经 commilitia-drop-file://<id> 喂给引擎。
|
||||
// 见 PLAN §2 / R-iOS-4)。引擎(rangeSource)按块带 `Range` 头拉取——这里 seek 文件
|
||||
// 仅读该区间、回 206 Partial Content,整文件永不入内存,避大文件 jetsam。无 Range
|
||||
// 头时回退整文件(防御路径;引擎正常总带 Range)。
|
||||
|
||||
@@ -6,7 +6,7 @@ import WebRTC
|
||||
// 集成(WebKit/libwebrtc 才集成),真机确证架构性不可靠(见 desktop/NATIVE-TRANSFER.md §7)。
|
||||
//
|
||||
// 线协议与 desktop/engine(pion)及 web/src/features/transfer/p2p.ts 逐字节一致,故可与桌面 pion
|
||||
// 引擎、浏览器 / 旧 iOS 的 JS 引擎互通:DataChannel "cdrop-file"(ordered),控制帧 meta/done/ack 走
|
||||
// 引擎、浏览器 / 旧 iOS 的 JS 引擎互通:DataChannel "commilitia-drop-file"(ordered),控制帧
|
||||
// 文本帧、文件分片走二进制帧(64KB),信令 payload 形如 {type, sdp?, candidate?}。本类是 session.go +
|
||||
// engine.go 的忠实 Swift 端口——逻辑、水位、ack 语义、收尾顺序一一对应。
|
||||
//
|
||||
@@ -39,7 +39,7 @@ protocol P2PEngineDelegate: AnyObject
|
||||
|
||||
private enum Wire
|
||||
{
|
||||
static let channelName = "cdrop-file"
|
||||
static let channelName = "commilitia-drop-file"
|
||||
static let chunkSize = 64 * 1024
|
||||
static let highWatermark: UInt64 = 16 * 1024 * 1024
|
||||
static let lowWatermark: UInt64 = 4 * 1024 * 1024
|
||||
@@ -94,7 +94,7 @@ final class LibWebRtcEngine
|
||||
}
|
||||
|
||||
// StartOutgoing:建 PeerConnection + DataChannel,发 offer,通道就绪后流式发出 filePath。
|
||||
// filePath 为本机绝对路径(原生从 cdrop-file://<id> 解析得到)。completion 在 offer 已铸并
|
||||
// filePath 为本机绝对路径(原生从 commilitia-drop-file://<id> 解析得到)。completion 在 offer 已铸并
|
||||
// setLocalDescription 完成后回(或出错时回错误),对齐 JS 侧信令链 seed 语义(见 p2pIos.ts chain)。
|
||||
func startOutgoing(sessionId: String, peerName: String, filePath: String,
|
||||
iceServersJSON: String, completion: @escaping (Error?) -> Void)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<string>en</string>
|
||||
</array>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<string>Commilitia Drop</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
@@ -35,7 +35,7 @@
|
||||
<string>net.commilitia.Commilitia-Drop</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>cdrop</string>
|
||||
<string>commilitia-drop</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -50,7 +50,7 @@
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_cdrop._tcp</string>
|
||||
<string>_commilitia-drop._tcp</string>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Commilitia Drop 使用相机扫描二维码登录新设备。</string>
|
||||
|
||||
@@ -3,10 +3,11 @@ import Network
|
||||
|
||||
// 触发本地网络权限弹窗(#5)。iOS 14+ 默认拒绝 App 访问本地网络;未授权时 WKWebView 内的
|
||||
// WebRTC 收集不到 host 候选 → 同内网也退回 srflx / relay(慢、非直连)。WKWebView 自身不会
|
||||
// 触发该权限请求,须由宿主 App 主动发起一次本地网络访问。这里启动一个对 _cdrop._tcp 的
|
||||
// 触发该权限请求,须由宿主 App 主动发起一次本地网络访问。这里启动一个对
|
||||
// _commilitia-drop._tcp 的
|
||||
// NWBrowser 浏览即触发系统弹窗;用户允许后,后续 RTCPeerConnection 才能收集本地 host / mDNS
|
||||
// 候选实现直连。仅需触发一次;保持 browser 存活以维持授权态。project.yml 已声明
|
||||
// NSLocalNetworkUsageDescription + NSBonjourServices(_cdrop._tcp)。
|
||||
// NSLocalNetworkUsageDescription + NSBonjourServices(_commilitia-drop._tcp)。
|
||||
enum LocalNetworkPermission
|
||||
{
|
||||
private static var browser: NWBrowser?
|
||||
@@ -18,7 +19,7 @@ enum LocalNetworkPermission
|
||||
guard browser == nil else { return }
|
||||
let params = NWParameters()
|
||||
params.includePeerToPeer = true
|
||||
let b = NWBrowser(for: .bonjour(type: "_cdrop._tcp", domain: nil), using: params)
|
||||
let b = NWBrowser(for: .bonjour(type: "_commilitia-drop._tcp", domain: nil), using: params)
|
||||
b.browseResultsChangedHandler = { _, _ in }
|
||||
browser = b
|
||||
b.start(queue: .main)
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"qr.show.nameHint": "Shown to your other devices. ASCII characters only.",
|
||||
"qr.show.generate": "Generate code",
|
||||
"qr.show.scanTitle": "Scan to approve",
|
||||
"qr.show.howto1": "Open the camera or cdrop on another signed-in device.",
|
||||
"qr.show.howto1": "Open the camera or Commilitia Drop on another signed-in device.",
|
||||
"qr.show.howto2": "Scan the code above and approve this device.",
|
||||
"qr.show.waiting": "Waiting for approval…",
|
||||
"qr.show.approved": "Approved — signing you in…",
|
||||
@@ -189,11 +189,11 @@
|
||||
"notify.transfer.failedTitle": "Transfer failed",
|
||||
"settings.account.title": "Account",
|
||||
"settings.account.signOut": "Sign out",
|
||||
"settings.desktop.title": "Desktop",
|
||||
"settings.desktop.title": "App settings",
|
||||
"settings.desktop.clipboardSync": "Auto-sync clipboard",
|
||||
"settings.desktop.clipboardSyncHint": "Copies are uploaded to the cloud, and clipboard updates from your other devices are applied here.",
|
||||
"settings.desktop.launchAtLogin": "Launch at login",
|
||||
"settings.desktop.launchAtLoginHint": "Start cdrop in the background after you sign in (lives in the menu bar).",
|
||||
"settings.desktop.launchAtLoginHint": "Start Commilitia Drop in the background after you sign in (lives in the menu bar or system tray).",
|
||||
"settings.desktop.downloadDir": "Download folder",
|
||||
"settings.desktop.downloadDirHint": "Received files are saved here. Leave unset to use the system Downloads folder.",
|
||||
"settings.desktop.downloadDirChoose": "Change…",
|
||||
@@ -292,7 +292,7 @@
|
||||
"errors.messageEmpty": "Message is empty",
|
||||
"errors.messageOverflow": "Message exceeds 4 KB; send a file instead",
|
||||
"errors.noReceiver": "No receiver selected",
|
||||
"errors.devTokenMissing": "Dev mode: VITE_CDROP_DEV_TOKEN is not set in .env.local",
|
||||
"errors.devTokenMissing": "Dev mode: the development token is not configured in .env.local",
|
||||
"errors.noAccessToken": "No access token; user must log in",
|
||||
"errors.clipboardUnavailable": "Browser clipboard API unavailable (requires HTTPS + permission)",
|
||||
"errors.clipboardOverflow": "Content too large; exceeds {{max}} byte limit",
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"qr.show.nameHint": "此名称会展示给你的其他设备,仅可使用 ASCII 字符。",
|
||||
"qr.show.generate": "生成二维码",
|
||||
"qr.show.scanTitle": "用手机扫码批准",
|
||||
"qr.show.howto1": "在另一台已登录的设备上打开相机或 cdrop。",
|
||||
"qr.show.howto1": "在另一台已登录的设备上打开相机或 Commilitia Drop。",
|
||||
"qr.show.howto2": "扫描上方二维码,按提示批准此设备。",
|
||||
"qr.show.waiting": "等待批准…",
|
||||
"qr.show.approved": "已批准,正在进入…",
|
||||
@@ -189,11 +189,11 @@
|
||||
"notify.transfer.failedTitle": "传输失败",
|
||||
"settings.account.title": "账号",
|
||||
"settings.account.signOut": "退出登录",
|
||||
"settings.desktop.title": "桌面",
|
||||
"settings.desktop.title": "应用设置",
|
||||
"settings.desktop.clipboardSync": "剪贴板自动同步",
|
||||
"settings.desktop.clipboardSyncHint": "复制即上传到云端,并接收其他设备的剪贴板更新。",
|
||||
"settings.desktop.launchAtLogin": "开机自启",
|
||||
"settings.desktop.launchAtLoginHint": "登录系统后自动在后台启动 cdrop(菜单栏常驻)。",
|
||||
"settings.desktop.launchAtLoginHint": "登录系统后自动在后台启动 Commilitia Drop(常驻菜单栏或系统托盘)。",
|
||||
"settings.desktop.downloadDir": "下载目录",
|
||||
"settings.desktop.downloadDirHint": "接收到的文件保存到此目录;留空则使用系统下载目录。",
|
||||
"settings.desktop.downloadDirChoose": "更改…",
|
||||
@@ -292,7 +292,7 @@
|
||||
"errors.messageEmpty": "消息为空",
|
||||
"errors.messageOverflow": "消息超过4 KB;请改用文件传输",
|
||||
"errors.noReceiver": "未选择接收方",
|
||||
"errors.devTokenMissing": "Dev模式:未在.env.local设置VITE_CDROP_DEV_TOKEN",
|
||||
"errors.devTokenMissing": "Dev 模式:未在 .env.local 配置开发令牌",
|
||||
"errors.noAccessToken": "无访问令牌:用户必须先登录",
|
||||
"errors.clipboardUnavailable": "浏览器不支持剪贴板API(需HTTPS与权限)",
|
||||
"errors.clipboardOverflow": "内容过大,超过 {{max}} 字节上限",
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"qr.show.nameHint": "此名稱會顯示給你的其他裝置,僅可使用 ASCII 字元。",
|
||||
"qr.show.generate": "產生 QR 碼",
|
||||
"qr.show.scanTitle": "用手機掃碼批准",
|
||||
"qr.show.howto1": "在另一部已登入的裝置上開啟相機或 cdrop。",
|
||||
"qr.show.howto1": "在另一部已登入的裝置上開啟相機或 Commilitia Drop。",
|
||||
"qr.show.howto2": "掃描上方 QR 碼,依提示批准此裝置。",
|
||||
"qr.show.waiting": "等待批准…",
|
||||
"qr.show.approved": "已批准,正在進入…",
|
||||
@@ -189,11 +189,11 @@
|
||||
"notify.transfer.failedTitle": "傳輸失敗",
|
||||
"settings.account.title": "帳號",
|
||||
"settings.account.signOut": "登出",
|
||||
"settings.desktop.title": "桌面",
|
||||
"settings.desktop.title": "應用程式設定",
|
||||
"settings.desktop.clipboardSync": "剪貼簿自動同步",
|
||||
"settings.desktop.clipboardSyncHint": "複製後即上傳雲端,並接收其他裝置的剪貼簿更新。",
|
||||
"settings.desktop.launchAtLogin": "開機自動啟動",
|
||||
"settings.desktop.launchAtLoginHint": "登入系統後自動在背景啟動 cdrop(常駐選單列)。",
|
||||
"settings.desktop.launchAtLoginHint": "登入系統後自動在背景啟動 Commilitia Drop(常駐選單列或系統匣)。",
|
||||
"settings.desktop.downloadDir": "下載資料夾",
|
||||
"settings.desktop.downloadDirHint": "接收到的檔案會儲存至此資料夾;留空則使用系統下載資料夾。",
|
||||
"settings.desktop.downloadDirChoose": "變更…",
|
||||
@@ -292,7 +292,7 @@
|
||||
"errors.messageEmpty": "訊息為空",
|
||||
"errors.messageOverflow": "訊息超過4 KB;請改用檔案傳輸",
|
||||
"errors.noReceiver": "未選擇接收方",
|
||||
"errors.devTokenMissing": "Dev模式:未在.env.local設定VITE_CDROP_DEV_TOKEN",
|
||||
"errors.devTokenMissing": "Dev 模式:未在 .env.local 設定開發權杖",
|
||||
"errors.noAccessToken": "無存取權杖:使用者必須先登入",
|
||||
"errors.clipboardUnavailable": "瀏覽器不支援剪貼簿API(需HTTPS與權限)",
|
||||
"errors.clipboardOverflow": "內容過大,超過 {{max}} 位元組上限",
|
||||
|
||||
@@ -80,7 +80,7 @@ struct RootView: View
|
||||
.opacity(0)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
// Share Extension 交接:cdrop://share 唤起 / 前台扫到收件箱待发文件 → 直入发送预览(与手动
|
||||
// Share Extension 交接:commilitia-drop://share 唤起 / 前台扫到收件箱待发文件。
|
||||
// 选文件同一表单 SendComposeSheet),预填分享的文件,选目标设备发送。onSend 走 sendShares
|
||||
// (把收件箱文件 move 到私有暂存再发),完成后 pendingShareFiles 清空 → 表单自动收起。
|
||||
.onAppear { engine.loadPendingShares() }
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<string>net.commilitia.Commilitia-Drop</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>cdrop</string>
|
||||
<string>commilitia-drop</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -41,7 +41,7 @@
|
||||
<string>26.0</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_cdrop._tcp</string>
|
||||
<string>_commilitia-drop._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Commilitia Drop 需要访问本地网络以发现同内网设备并建立直连传输。</string>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<string>Commilitia Drop</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
|
||||
@@ -45,14 +45,15 @@ targets:
|
||||
path: Sources/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: Commilitia Drop
|
||||
CFBundleName: Commilitia Drop
|
||||
# 品牌字标字体 Fredoka SemiBold(Shared/Fonts/,随 bundle 打包),用于 Wordmark(与 web 一致)。
|
||||
UIAppFonts:
|
||||
- Fredoka-SemiBold.ttf
|
||||
# cdrop:// URL scheme:Share Extension 交接后经 cdrop://share 深链唤起主 app 选设备发送。
|
||||
# commilitia-drop:// URL scheme:Share Extension 交接后唤起主 app 选设备发送。
|
||||
CFBundleURLTypes:
|
||||
- CFBundleURLName: net.commilitia.Commilitia-Drop
|
||||
CFBundleURLSchemes:
|
||||
- cdrop
|
||||
- commilitia-drop
|
||||
# 声明支持的语言,使系统控件(EditButton 的「编辑」、滑动删除的「删除」等系统字符
|
||||
# 串)跟随设备语言本地化,而非永远英文。开发区域设简体。
|
||||
CFBundleDevelopmentRegion: zh-Hans
|
||||
@@ -88,7 +89,7 @@ targets:
|
||||
NSAllowsLocalNetworking: true
|
||||
NSLocalNetworkUsageDescription: "Commilitia Drop 需要访问本地网络以发现同内网设备并建立直连传输。"
|
||||
NSBonjourServices:
|
||||
- "_cdrop._tcp"
|
||||
- "_commilitia-drop._tcp"
|
||||
NSCameraUsageDescription: "Commilitia Drop 使用相机扫描二维码登录新设备。"
|
||||
# 图库选择:从相册选取图片 / 视频发送(PhotosPicker,见 RootView #14)。
|
||||
NSPhotoLibraryUsageDescription: "Commilitia Drop 需要访问照片库,以便从相册选取图片或视频发送给其他设备。"
|
||||
@@ -115,6 +116,7 @@ targets:
|
||||
path: Share/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: Commilitia Drop
|
||||
CFBundleName: Commilitia Drop
|
||||
# 品牌字标字体(分享引导界面的 Wordmark 用),随扩展 bundle 打包 + 注册。
|
||||
UIAppFonts:
|
||||
- Fredoka-SemiBold.ttf
|
||||
@@ -145,6 +147,7 @@ targets:
|
||||
path: Widgets/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: Commilitia Drop
|
||||
CFBundleName: Commilitia Drop
|
||||
NSExtension:
|
||||
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|
||||
settings:
|
||||
@@ -212,15 +215,15 @@ targets:
|
||||
- zh-Hans
|
||||
- zh-Hant
|
||||
- en
|
||||
# ASWebAuthenticationSession 的 OAuth 回调经 cdrop:// scheme 回到 app(同 iOS)。
|
||||
# ASWebAuthenticationSession 的 OAuth 回调经 commilitia-drop:// scheme 回到 app。
|
||||
CFBundleURLTypes:
|
||||
- CFBundleURLName: net.commilitia.Commilitia-Drop
|
||||
CFBundleURLSchemes:
|
||||
- cdrop
|
||||
- commilitia-drop
|
||||
# 本地网络:libwebrtc 收集 host 候选、同内网直连须本地网络权限(macOS 15+ 亦弹窗)。
|
||||
NSLocalNetworkUsageDescription: "Commilitia Drop 需要访问本地网络以发现同内网设备并建立直连传输。"
|
||||
NSBonjourServices:
|
||||
- "_cdrop._tcp"
|
||||
- "_commilitia-drop._tcp"
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: net.commilitia.Commilitia-Drop
|
||||
|
||||
Reference in New Issue
Block a user