mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-20 03:01:52 +08:00
[feat] add remote host version checks and update prompts
This commit is contained in:
@@ -61,6 +61,7 @@ typedef NS_ENUM(NSInteger, CrossDeskVideoAdaptationPolicy) {
|
||||
height:(NSInteger)height;
|
||||
- (void)rtcBridge:(CrossDeskRTCBridge *)bridge
|
||||
didReceiveHostName:(NSString *)hostName
|
||||
appVersion:(NSString *)appVersion
|
||||
displayNames:(NSArray<NSString *> *)displayNames
|
||||
displaySizes:(NSArray<NSValue *> *)displaySizes;
|
||||
- (void)rtcBridge:(CrossDeskRTCBridge *)bridge
|
||||
@@ -101,6 +102,11 @@ typedef NS_ENUM(NSInteger, CrossDeskVideoAdaptationPolicy) {
|
||||
|
||||
@property(nonatomic, weak, nullable) id<CrossDeskRTCBridgeDelegate> delegate;
|
||||
|
||||
// Uses the same release/patch ordering as the desktop controller.
|
||||
+ (nullable NSString *)availableUpdateForAppVersion:(NSString *)appVersion
|
||||
releaseJSON:(NSData *)releaseJSON
|
||||
NS_SWIFT_NAME(availableUpdate(appVersion:releaseJSON:));
|
||||
|
||||
- (void)configureWithSignalHost:(NSString *)host
|
||||
signalPort:(NSInteger)signalPort;
|
||||
- (void)setVideoAdaptationPolicy:(CrossDeskVideoAdaptationPolicy)policy;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <display_stream_id.h>
|
||||
#include <app_version.h>
|
||||
#include <file_transfer_format.h>
|
||||
#include <remote_action.h>
|
||||
#include <stream_names.h>
|
||||
@@ -330,7 +331,8 @@ void DispatchMain(dispatch_block_t block) {
|
||||
- (void)handleData:(const char *)data
|
||||
size:(size_t)size
|
||||
sourceID:(const char *)sourceID
|
||||
sourceIDSize:(size_t)sourceIDSize;
|
||||
sourceIDSize:(size_t)sourceIDSize
|
||||
generation:(uint64_t)generation;
|
||||
- (void)handleStats:(const MiniRtcNetTrafficStats *)stats mode:(TraversalMode)mode;
|
||||
- (void)sendMessage:(const std::string &)message
|
||||
reliable:(BOOL)reliable
|
||||
@@ -377,7 +379,8 @@ void OnDataBuffer(const char *data, size_t size, const char *, size_t,
|
||||
[owner handleData:data
|
||||
size:size
|
||||
sourceID:source_id
|
||||
sourceIDSize:source_id_size];
|
||||
sourceIDSize:source_id_size
|
||||
generation:context->generation];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +491,16 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
std::atomic<uint64_t> _presenceGeneration;
|
||||
}
|
||||
|
||||
+ (nullable NSString *)availableUpdateForAppVersion:(NSString *)appVersion
|
||||
releaseJSON:(NSData *)releaseJSON {
|
||||
const auto latest = crossdesk::ParseVersionInfoJSON(std::string_view(
|
||||
static_cast<const char *>(releaseJSON.bytes), releaseJSON.length));
|
||||
if (!latest) return nil;
|
||||
const auto update = crossdesk::AvailableAppUpdate(appVersion.UTF8String ?: "",
|
||||
*latest);
|
||||
return update ? [NSString stringWithUTF8String:update->c_str()] : nil;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -1264,6 +1277,10 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
CopyCString(action.i.host_name, sizeof(action.i.host_name),
|
||||
controller_name);
|
||||
action.i.host_name_size = std::strlen(action.i.host_name);
|
||||
NSString *appVersion = [[NSBundle mainBundle]
|
||||
objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
||||
CopyCString(action.i.app_version, sizeof(action.i.app_version),
|
||||
appVersion.UTF8String ?: "");
|
||||
action.i.display_list = nullptr;
|
||||
action.i.display_num = 0;
|
||||
action.i.left = nullptr;
|
||||
@@ -1515,7 +1532,8 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
- (void)handleData:(const char *)data
|
||||
size:(size_t)size
|
||||
sourceID:(const char *)sourceID
|
||||
sourceIDSize:(size_t)sourceIDSize {
|
||||
sourceIDSize:(size_t)sourceIDSize
|
||||
generation:(uint64_t)generation {
|
||||
if (!data || size == 0 || !sourceID) return;
|
||||
const std::string source(sourceID, sourceIDSize);
|
||||
if (source == kControlStream) {
|
||||
@@ -1528,6 +1546,8 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
initWithBytes:action.i.host_name
|
||||
length:action.i.host_name_size
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSString *app_version = crossdesk::IsValidAppVersion(action.i.app_version)
|
||||
? [NSString stringWithUTF8String:action.i.app_version] : nil;
|
||||
NSMutableArray<NSString *> *display_names =
|
||||
[NSMutableArray arrayWithCapacity:action.i.display_num];
|
||||
NSMutableArray<NSValue *> *display_sizes =
|
||||
@@ -1548,12 +1568,14 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
}
|
||||
crossdesk::FreeRemoteAction(action);
|
||||
DispatchMain(^{
|
||||
if (![self isControllerGenerationActive:generation]) return;
|
||||
id<CrossDeskRTCBridgeDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:
|
||||
@selector(rtcBridge:didReceiveHostName:displayNames:
|
||||
@selector(rtcBridge:didReceiveHostName:appVersion:displayNames:
|
||||
displaySizes:)]) {
|
||||
[delegate rtcBridge:self
|
||||
didReceiveHostName:host_name ?: @""
|
||||
appVersion:app_version ?: @""
|
||||
displayNames:display_names
|
||||
displaySizes:display_sizes];
|
||||
}
|
||||
|
||||
@@ -359,10 +359,17 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
@Published private(set) var recentConnectionPresence: [String: Bool] = [:]
|
||||
@Published private(set) var deviceOfflineAlertVisible = false
|
||||
@Published private(set) var connectionFailureMessage: String?
|
||||
@Published private(set) var remoteUpdateMessage: String?
|
||||
|
||||
let bridge = CrossDeskRTCBridge()
|
||||
private let audioPlayer = RemoteAudioPlayer()
|
||||
private var activeRemoteID = ""
|
||||
private var remoteAppVersion = ""
|
||||
private var remoteHostInfoReceived = false
|
||||
private var remoteVersionCheckAttempted = false
|
||||
private var remoteVersionCheckGeneration = UUID()
|
||||
private var remoteVersionCheckTask: URLSessionDataTask?
|
||||
private static let remoteUpdateNotice = "被控端版本过低,请升级到最新版本。"
|
||||
private var pendingRememberPassword = false
|
||||
private var connectionRecorded = false
|
||||
private var shouldCaptureThumbnail = false
|
||||
@@ -406,6 +413,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
|
||||
deinit {
|
||||
presenceTimer?.invalidate()
|
||||
remoteVersionCheckTask?.cancel()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@@ -460,6 +468,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
}
|
||||
|
||||
private func beginRemoteConnection(_ identifier: String) {
|
||||
resetRemoteVersionCheck()
|
||||
cancelVideoRecovery()
|
||||
cancelPresenceProbe()
|
||||
activeRemoteID = identifier
|
||||
@@ -552,6 +561,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
}
|
||||
|
||||
private func resetConnection() {
|
||||
resetRemoteVersionCheck()
|
||||
connectionFailureMessage = nil
|
||||
cancelVideoRecovery()
|
||||
let wasCheckingPresence = pendingPresenceRemoteID != nil
|
||||
@@ -576,11 +586,62 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
}
|
||||
|
||||
func retry() {
|
||||
resetRemoteVersionCheck()
|
||||
cancelVideoRecovery()
|
||||
bridge.disconnect()
|
||||
connect()
|
||||
}
|
||||
|
||||
func dismissRemoteUpdate() {
|
||||
remoteUpdateMessage = nil
|
||||
}
|
||||
|
||||
private func resetRemoteVersionCheck() {
|
||||
remoteVersionCheckTask?.cancel()
|
||||
remoteVersionCheckTask = nil
|
||||
remoteVersionCheckGeneration = UUID()
|
||||
remoteVersionCheckAttempted = false
|
||||
remoteAppVersion = ""
|
||||
remoteHostInfoReceived = false
|
||||
remoteUpdateMessage = nil
|
||||
}
|
||||
|
||||
private func checkRemoteVersionIfNeeded() {
|
||||
guard isConnected, remoteHostInfoReceived,
|
||||
!remoteVersionCheckAttempted else { return }
|
||||
remoteVersionCheckAttempted = true
|
||||
if remoteAppVersion.isEmpty {
|
||||
remoteUpdateMessage = Self.remoteUpdateNotice
|
||||
return
|
||||
}
|
||||
let generation = remoteVersionCheckGeneration
|
||||
let appVersion = remoteAppVersion
|
||||
// Only public release metadata is fetched; no peer ID or version is sent.
|
||||
let url = URL(string: "https://version.crossdesk.cn/version.json")!
|
||||
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData,
|
||||
timeoutInterval: 15)
|
||||
remoteVersionCheckTask = URLSession.shared.dataTask(with: request) {
|
||||
[weak self] data, response, error in
|
||||
let latest: String?
|
||||
if error == nil, let data,
|
||||
(response as? HTTPURLResponse)?.statusCode == 200 {
|
||||
latest = CrossDeskRTCBridge.availableUpdate(appVersion: appVersion,
|
||||
releaseJSON: data)
|
||||
} else {
|
||||
latest = nil
|
||||
}
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self,
|
||||
self.remoteVersionCheckGeneration == generation,
|
||||
self.isConnected else { return }
|
||||
self.remoteVersionCheckTask = nil
|
||||
guard latest != nil else { return }
|
||||
self.remoteUpdateMessage = Self.remoteUpdateNotice
|
||||
}
|
||||
}
|
||||
remoteVersionCheckTask?.resume()
|
||||
}
|
||||
|
||||
func savedPassword(for remoteID: String) -> String {
|
||||
ConnectionCredentialStore.password(for: remoteID) ?? ""
|
||||
}
|
||||
@@ -879,6 +940,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
case 1:
|
||||
connectionStatus = "已连接"
|
||||
sessionVisible = true
|
||||
checkRemoteVersionIfNeeded()
|
||||
recordSuccessfulConnectionIfNeeded()
|
||||
audioPlayer.setEnabled(audioEnabled)
|
||||
bridge.setAudioEnabled(audioEnabled)
|
||||
@@ -966,8 +1028,12 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
|
||||
func rtcBridge(_ bridge: CrossDeskRTCBridge,
|
||||
didReceiveHostName hostName: String,
|
||||
appVersion: String,
|
||||
displayNames remoteDisplayNames: [String],
|
||||
displaySizes remoteDisplaySizes: [NSValue]) {
|
||||
remoteHostInfoReceived = true
|
||||
remoteAppVersion = appVersion
|
||||
checkRemoteVersionIfNeeded()
|
||||
updateActiveConnectionName(hostName)
|
||||
let names = remoteDisplayNames.enumerated().map { index, displayName in
|
||||
let name = displayName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
@@ -147,6 +147,14 @@ struct RemoteSessionView: View {
|
||||
.zIndex(50)
|
||||
}
|
||||
}
|
||||
.alert("升级提示", isPresented: Binding(
|
||||
get: { session.remoteUpdateMessage != nil },
|
||||
set: { if !$0 { session.dismissRemoteUpdate() } }
|
||||
)) {
|
||||
Button("知道了", role: .cancel) { session.dismissRemoteUpdate() }
|
||||
} message: {
|
||||
Text(session.remoteUpdateMessage ?? "")
|
||||
}
|
||||
.onDisappear {
|
||||
keyboardInputVisible = false
|
||||
viewport = RemoteViewportState()
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -241,12 +241,12 @@
|
||||
{
|
||||
"id": "inih",
|
||||
"name": "inih / INIReader",
|
||||
"version": "6286ae6c1c015609d3070cc139e5feea900caf7c",
|
||||
"version": "24d9d9deefb95ede2cdb18e77cb01350101f70f8",
|
||||
"license": "BSD-3-Clause",
|
||||
"source": {
|
||||
"kind": "repository",
|
||||
"url": "https://github.com/kunkundi/minirtc",
|
||||
"revision": "6286ae6c1c015609d3070cc139e5feea900caf7c"
|
||||
"revision": "24d9d9deefb95ede2cdb18e77cb01350101f70f8"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
@@ -527,12 +527,12 @@
|
||||
{
|
||||
"id": "minirtc",
|
||||
"name": "MiniRTC",
|
||||
"version": "6286ae6c1c015609d3070cc139e5feea900caf7c",
|
||||
"version": "24d9d9deefb95ede2cdb18e77cb01350101f70f8",
|
||||
"license": "LGPL-3.0-only",
|
||||
"source": {
|
||||
"kind": "repository",
|
||||
"url": "https://github.com/kunkundi/minirtc",
|
||||
"revision": "6286ae6c1c015609d3070cc139e5feea900caf7c"
|
||||
"revision": "24d9d9deefb95ede2cdb18e77cb01350101f70f8"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
@@ -833,12 +833,12 @@
|
||||
{
|
||||
"id": "webrtc",
|
||||
"name": "WebRTC-derived code in MiniRTC",
|
||||
"version": "6286ae6c1c015609d3070cc139e5feea900caf7c",
|
||||
"version": "24d9d9deefb95ede2cdb18e77cb01350101f70f8",
|
||||
"license": "BSD-3-Clause and patent grant",
|
||||
"source": {
|
||||
"kind": "repository",
|
||||
"url": "https://github.com/kunkundi/minirtc",
|
||||
"revision": "6286ae6c1c015609d3070cc139e5feea900caf7c"
|
||||
"revision": "24d9d9deefb95ede2cdb18e77cb01350101f70f8"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
@@ -898,7 +898,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"bundle_sha256": "09376251bbda3f5a1f7112f15dcbb1259fef7898f85b45aacafa767348aafdc2",
|
||||
"bundle_sha256": "58702163ac79d8821007b1ae44ef054cf1d9a3cec9d6334b852f0bbe99b5af01",
|
||||
"toolchain": {
|
||||
"xmake_version": "3.1.1",
|
||||
"xmake_repository_revision": "121109352da272cd1a3d4ed52f2dcda34f839955"
|
||||
|
||||
Reference in New Issue
Block a user