mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
252 lines
9.1 KiB
Swift
252 lines
9.1 KiB
Swift
//
|
|
// Copyright 2023 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
import Foundation
|
|
import NearbyConnections
|
|
|
|
class Model: ObservableObject {
|
|
@Published var endpointName = Config.defaultEndpointName {
|
|
didSet {
|
|
invalidateAdvertising()
|
|
}
|
|
}
|
|
@Published var strategy = Config.defaultStategy {
|
|
didSet {
|
|
invalidateAdvertising()
|
|
invalidateDiscovery()
|
|
}
|
|
}
|
|
@Published var isAdvertisingEnabled = Config.defaultAdvertisingState {
|
|
didSet {
|
|
invalidateAdvertising()
|
|
}
|
|
}
|
|
@Published var isDiscoveryEnabled = Config.defaultDiscoveryState {
|
|
didSet {
|
|
invalidateDiscovery()
|
|
}
|
|
}
|
|
|
|
@Published private(set) var requests: [ConnectionRequest] = []
|
|
@Published private(set) var connections: [ConnectedEndpoint] = []
|
|
@Published private(set) var endpoints: [DiscoveredEndpoint] = []
|
|
|
|
var connectionManager: ConnectionManager!
|
|
var advertiser: Advertiser?
|
|
var discoverer: Discoverer?
|
|
|
|
init() {
|
|
invalidateAdvertising()
|
|
invalidateDiscovery()
|
|
}
|
|
|
|
private var isAdvertising = Config.defaultAdvertisingState
|
|
private func invalidateAdvertising() {
|
|
defer {
|
|
isAdvertising = isAdvertisingEnabled
|
|
}
|
|
if isAdvertising {
|
|
advertiser?.stopAdvertising()
|
|
}
|
|
if !isAdvertisingEnabled {
|
|
return
|
|
}
|
|
connectionManager = ConnectionManager(serviceID: Config.serviceId, strategy: strategy)
|
|
connectionManager.delegate = self
|
|
|
|
advertiser = Advertiser(connectionManager: connectionManager)
|
|
advertiser?.delegate = self
|
|
advertiser?.startAdvertising(using: endpointName.data(using: .utf8)!)
|
|
}
|
|
|
|
private var isDiscovering = Config.defaultDiscoveryState
|
|
private func invalidateDiscovery() {
|
|
defer {
|
|
isDiscovering = isDiscoveryEnabled
|
|
}
|
|
if isDiscovering {
|
|
discoverer?.stopDiscovery()
|
|
}
|
|
if !isDiscoveryEnabled {
|
|
return
|
|
}
|
|
connectionManager = ConnectionManager(serviceID: Config.serviceId, strategy: strategy)
|
|
connectionManager.delegate = self
|
|
|
|
discoverer = Discoverer(connectionManager: connectionManager)
|
|
discoverer?.delegate = self
|
|
discoverer?.startDiscovery()
|
|
}
|
|
|
|
func requestConnection(to endpointID: EndpointID) {
|
|
discoverer?.requestConnection(to: endpointID, using: endpointName.data(using: .utf8)!)
|
|
}
|
|
|
|
func disconnect(from endpointID: EndpointID) {
|
|
connectionManager.disconnect(from: endpointID)
|
|
}
|
|
|
|
func sendBytes(to endpointIDs: [EndpointID]) {
|
|
let payloadID = PayloadID.unique()
|
|
let token = connectionManager.send(Config.bytePayload.data(using: .utf8)!, to: endpointIDs, id: payloadID)
|
|
let payload = Payload(
|
|
id: payloadID,
|
|
type: .bytes,
|
|
status: .inProgress(Progress()),
|
|
isIncoming: false,
|
|
cancellationToken: token
|
|
)
|
|
for endpointID in endpointIDs {
|
|
guard let index = connections.firstIndex(where: { $0.id == endpointID }) else {
|
|
return
|
|
}
|
|
connections[index].payloads.insert(payload, at: 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Model: DiscovererDelegate {
|
|
func discoverer(_ discoverer: Discoverer, didFind endpointID: EndpointID, with context: Data) {
|
|
let endpoint = DiscoveredEndpoint(
|
|
id: endpointID,
|
|
endpointName: String(data: context, encoding: .utf8)!
|
|
)
|
|
endpoints.insert(endpoint, at: 0)
|
|
}
|
|
|
|
func discoverer(_ discoverer: Discoverer, didLose endpointID: EndpointID) {
|
|
guard let index = endpoints.firstIndex(where: { $0.id == endpointID }) else {
|
|
return
|
|
}
|
|
endpoints.remove(at: index)
|
|
}
|
|
}
|
|
|
|
extension Model: AdvertiserDelegate {
|
|
func advertiser(_ advertiser: Advertiser, didReceiveConnectionRequestFrom endpointID: EndpointID, with context: Data, connectionRequestHandler: @escaping (Bool) -> Void) {
|
|
let endpoint = DiscoveredEndpoint(
|
|
id: endpointID,
|
|
endpointName: String(data: context, encoding: .utf8)!
|
|
)
|
|
endpoints.insert(endpoint, at: 0)
|
|
connectionRequestHandler(true)
|
|
}
|
|
}
|
|
|
|
extension Model: ConnectionManagerDelegate {
|
|
func connectionManager(_ connectionManager: ConnectionManager, didReceive verificationCode: String, from endpointID: EndpointID, verificationHandler: @escaping (Bool) -> Void) {
|
|
guard let index = endpoints.firstIndex(where: { $0.id == endpointID }) else {
|
|
return
|
|
}
|
|
let endpoint = endpoints.remove(at: index)
|
|
let request = ConnectionRequest(
|
|
id: endpointID,
|
|
endpointName: endpoint.endpointName,
|
|
pin: verificationCode,
|
|
shouldAccept: { accept in
|
|
verificationHandler(accept)
|
|
}
|
|
)
|
|
requests.insert(request, at: 0)
|
|
}
|
|
|
|
func connectionManager(_ connectionManager: ConnectionManager, didReceive data: Data, withID payloadID: PayloadID, from endpointID: EndpointID) {
|
|
let payload = Payload(
|
|
id: payloadID,
|
|
type: .bytes,
|
|
status: .success,
|
|
isIncoming: true,
|
|
cancellationToken: nil
|
|
)
|
|
guard let index = connections.firstIndex(where: { $0.id == endpointID }) else {
|
|
return
|
|
}
|
|
connections[index].payloads.insert(payload, at: 0)
|
|
}
|
|
|
|
func connectionManager(_ connectionManager: ConnectionManager, didReceive stream: InputStream, withID payloadID: PayloadID, from endpointID: EndpointID, cancellationToken token: CancellationToken) {
|
|
let payload = Payload(
|
|
id: payloadID,
|
|
type: .stream,
|
|
status: .success,
|
|
isIncoming: true,
|
|
cancellationToken: token
|
|
)
|
|
guard let index = connections.firstIndex(where: { $0.id == endpointID }) else {
|
|
return
|
|
}
|
|
connections[index].payloads.insert(payload, at: 0)
|
|
}
|
|
|
|
func connectionManager(_ connectionManager: ConnectionManager, didStartReceivingResourceWithID payloadID: PayloadID, from endpointID: EndpointID, at localURL: URL, withName name: String, cancellationToken token: CancellationToken) {
|
|
let payload = Payload(
|
|
id: payloadID,
|
|
type: .file,
|
|
status: .inProgress(Progress()),
|
|
isIncoming: true,
|
|
cancellationToken: token
|
|
)
|
|
guard let index = connections.firstIndex(where: { $0.id == endpointID }) else {
|
|
return
|
|
}
|
|
connections[index].payloads.insert(payload, at: 0)
|
|
}
|
|
|
|
func connectionManager(_ connectionManager: ConnectionManager, didReceiveTransferUpdate update: TransferUpdate, from endpointID: EndpointID, forPayload payloadID: PayloadID) {
|
|
guard let connectionIndex = connections.firstIndex(where: { $0.id == endpointID }),
|
|
let payloadIndex = connections[connectionIndex].payloads.firstIndex(where: { $0.id == payloadID }) else {
|
|
return
|
|
}
|
|
switch update {
|
|
case .success:
|
|
connections[connectionIndex].payloads[payloadIndex].status = .success
|
|
case .canceled:
|
|
connections[connectionIndex].payloads[payloadIndex].status = .canceled
|
|
case .failure:
|
|
connections[connectionIndex].payloads[payloadIndex].status = .failure
|
|
case let .progress(progress):
|
|
connections[connectionIndex].payloads[payloadIndex].status = .inProgress(progress)
|
|
}
|
|
}
|
|
|
|
func connectionManager(_ connectionManager: ConnectionManager, didChangeTo state: ConnectionState, for endpointID: EndpointID) {
|
|
switch (state) {
|
|
case .connecting:
|
|
break
|
|
case .connected:
|
|
guard let index = requests.firstIndex(where: { $0.id == endpointID }) else {
|
|
return
|
|
}
|
|
let request = requests.remove(at: index)
|
|
let connection = ConnectedEndpoint(
|
|
id: endpointID,
|
|
endpointName: request.endpointName
|
|
)
|
|
connections.insert(connection, at: 0)
|
|
case .disconnected:
|
|
guard let index = connections.firstIndex(where: { $0.id == endpointID }) else {
|
|
return
|
|
}
|
|
connections.remove(at: index)
|
|
case .rejected:
|
|
guard let index = requests.firstIndex(where: { $0.id == endpointID }) else {
|
|
return
|
|
}
|
|
requests.remove(at: index)
|
|
}
|
|
}
|
|
}
|