feat(controlled): 实现设备激活与安全认证流程

- 添加API客户端、加密存储和provision/token激活逻辑
- WebSocket改用Bearer令牌认证,移除REGISTER请求
- 设备ID改为服务端下发,支持令牌刷新和强制下线处理
- 新增deviceSecret加密存储和accessToken自动刷新
- 更新设备ID获取方式为出厂SN,添加安全存储依赖
This commit is contained in:
2026-08-01 15:13:12 +08:00
parent 376a2c1217
commit 6eb2c7321a
124 changed files with 10535 additions and 862 deletions

View File

@@ -5,17 +5,22 @@ protocol SignalingClientDelegate: AnyObject {
func signalingDidDisconnect()
func signaling(didFail error: String)
func signaling(didReceive message: SignalMessage)
/// 4001
func signalingTokenExpired()
/// 线 4003
func signalingForceLogout()
}
/// WebSocket URLSessionWebSocketTask
/// REGISTERdeviceType = CONTROLLER
/// 线
///
/// URLRequest `Authorization: Bearer <accessToken>`
/// REGISTER REGISTER_SUCCESS
final class SignalingClient: NSObject {
weak var delegate: SignalingClientDelegate?
private let serverUrl: String
private let deviceId: String
private let token: String?
private var session: URLSession?
private var task: URLSessionWebSocketTask?
private var manuallyClosed = false
@@ -23,10 +28,12 @@ final class SignalingClient: NSObject {
private var isOpened = false
/// /
private var didNotifyClosure = false
/// 25s PING
private var heartbeatTimer: Timer?
init(serverUrl: String, deviceId: String) {
init(serverUrl: String, token: String?) {
self.serverUrl = serverUrl
self.deviceId = deviceId
self.token = token
super.init()
}
@@ -38,10 +45,17 @@ final class SignalingClient: NSObject {
manuallyClosed = false
isOpened = false
didNotifyClosure = false
// Bearer tokenURLSessionWebSocketTask URLRequest
var request = URLRequest(url: url)
if let token, !token.isEmpty {
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 15
session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
task = session?.webSocketTask(with: url)
task = session?.webSocketTask(with: request)
// didOpenWithProtocol
// socket receive Code 57
task?.resume()
@@ -50,6 +64,7 @@ final class SignalingClient: NSObject {
func disconnect() {
manuallyClosed = true
isOpened = false
stopHeartbeat()
task?.cancel(with: .normalClosure, reason: "Disconnecting".data(using: .utf8))
task = nil
session?.invalidateAndCancel()
@@ -71,12 +86,19 @@ final class SignalingClient: NSObject {
// MARK: - Private
private func registerDevice() {
var msg = SignalMessage()
msg.type = "REGISTER"
msg.fromDeviceId = deviceId
msg.deviceType = "CONTROLLER"
send(msg)
private func startHeartbeat() {
stopHeartbeat()
heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 25, repeats: true) { [weak self] _ in
guard let self, let task = self.task else { return }
task.send(.string("{\"type\":\"PING\"}")) { error in
if let error { NSLog("[Signaling] ping error: \(error.localizedDescription)") }
}
}
}
private func stopHeartbeat() {
heartbeatTimer?.invalidate()
heartbeatTimer = nil
}
private func receiveLoop() {
@@ -99,24 +121,20 @@ final class SignalingClient: NSObject {
}
/// """"
/// Socket Code 57
private func handleReceiveFailure(_ error: Error) {
guard !manuallyClosed, !didNotifyClosure else { return }
didNotifyClosure = true
let nsError = error as NSError
// NSPOSIXErrorDomain Code 57: Socket is not connected
let isDisconnect = (nsError.domain == NSPOSIXErrorDomain && nsError.code == 57)
|| (nsError.domain == NSURLErrorDomain
&& (nsError.code == NSURLErrorNetworkConnectionLost
|| nsError.code == NSURLErrorCancelled))
|| nsError.code == NSURLErrorCancelled))
DispatchQueue.main.async {
if self.isOpened || isDisconnect {
// ""
self.delegate?.signalingDidDisconnect()
} else {
// ""
self.delegate?.signaling(didFail: error.localizedDescription)
}
}
@@ -137,9 +155,10 @@ extension SignalingClient: URLSessionWebSocketDelegate {
webSocketTask: URLSessionWebSocketTask,
didOpenWithProtocol protocol: String?) {
isOpened = true
// socket receive
// socket receive
// REGISTER Bearer
receiveLoop()
registerDevice()
startHeartbeat()
DispatchQueue.main.async {
self.delegate?.signalingDidConnect()
}
@@ -150,8 +169,19 @@ extension SignalingClient: URLSessionWebSocketDelegate {
didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
reason: Data?) {
isOpened = false
stopHeartbeat()
guard !manuallyClosed, !didNotifyClosure else { return }
didNotifyClosure = true
let code = Int(closeCode.rawValue)
if code == 4001 {
DispatchQueue.main.async { self.delegate?.signalingTokenExpired() }
return
}
if code == 4003 {
DispatchQueue.main.async { self.delegate?.signalingForceLogout() }
return
}
DispatchQueue.main.async {
self.delegate?.signalingDidDisconnect()
}