Skip to content

A reliable MultipeerConnectivity wrapper for easy server/client implementation

Notifications You must be signed in to change notification settings

RanduSoft/RSMultipeerConnectivity

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 

Repository files navigation

RSMultipeerConnectivity

RSMultipeerConnectivity is a Swift wrapper over Apple's MultipeerConnectivity framework for building local peer-to-peer server/client flows with typed messaging.

Why?

MultipeerConnectivity is powerful but verbose. This package is useful when you want to:

  • Ship LAN peer-to-peer features faster with a higher-level API.
  • Keep server/client roles explicit instead of managing raw delegates manually.
  • Send and receive typed Codable models with less boilerplate.
  • Add handshake validation and controlled peer rejection.
  • Integrate easily with SwiftUI state via @Published properties and callbacks.

Features

  • Simple server/client manager API.
  • Typed send/receive helpers for Codable payloads.
  • DataObject<T> wrapper with IDs and timestamps.
  • Optional handshake validation on server invitations.
  • Server-side peer rejection and kick support.
  • Observable state (@Published) for SwiftUI apps.
  • Built-in iOS debug UI (DebugView).
  • Encrypted sessions (MCEncryptionPreference.required).

Requirements

  • Swift 5.9+
  • Apple platforms:
    • iOS 15+
    • macOS 12+
    • Mac Catalyst 15+
    • tvOS 15+
    • visionOS 1+

Installation (Swift Package Manager)

In Xcode:

  1. File -> Add Packages...
  2. Use this repository URL.
  3. Add product RSMultipeerConnectivity.

Or in Package.swift:

dependencies: [
    .package(url: "https://github.com/RanduSoft/RSMultipeerConnectivity.git", from: "2.0.0")
]

iOS Setup Requirements

For iOS, you must configure Bonjour service discovery and local network access.

1. Add Bonjour Service to Info.plist

<key>NSBonjourServices</key>
<array>
    <string>_randusoft-mp._tcp</string>
</array>

This value is configurable, but the format must stay:

_<serviceType>._tcp

If your Config(serviceType:) is "my-service", the Bonjour entry must be:

_my-service._tcp

2. Enable Local Network Capability

In Xcode, open your target and add:

  • Signing & Capabilities -> + Capability -> Local Network

Quick Onboarding Test (iOS)

For a one-line smoke test, present DemoView:

import RSMultipeerConnectivity
import SwiftUI

DemoView()

DemoView shows a server and client in the same screen so new users can quickly verify discovery, connection, and messaging.

DebugView is internal and intentionally not part of the public API.

Core Types

  • Config(serviceType:enableLogging:)
  • ServerConnectivityManager
  • ClientConnectivityManager
  • ClientHandshakeRequest
  • ServerHandshakeResponse
  • DataObject<Content: Codable>
  • ConnectivityError

Quick Start

1. Create a Server

import RSMultipeerConnectivity
import MultipeerConnectivity

let server = ServerConnectivityManager(
    displayName: "Host",
    config: Config(serviceType: "my-service", enableLogging: true)
)

server.onPeerConnected = { peer in
    print("Connected:", peer.displayName)
}

server.onPeerDisconnected = { peer in
    print("Disconnected:", peer.displayName)
}

server.onPeerRejected = { peer, reason in
    print("Rejected \(peer.displayName), reason:", reason ?? "none")
}

server.invitationValidator = { peer, handshake in
    guard handshake.appVersion.hasPrefix("2.") else {
        return ServerHandshakeResponse(allowed: false, reason: "Unsupported app version")
    }
    return ServerHandshakeResponse(allowed: true)
}

server.start()

2. Create a Client

import RSMultipeerConnectivity
import MultipeerConnectivity

let client = ClientConnectivityManager(
    displayName: "Player 1",
    config: Config(serviceType: "my-service", enableLogging: true)
)

client.onConnectionStateChange = { isConnected in
    print(isConnected ? "Connected" : "Disconnected")
}

client.onKick = { reason in
    print("Kicked:", reason ?? "no reason")
}

client.start()

3. Connect to a Discovered Server

if let serverPeer = client.availablePeers.first {
    let handshake = ClientHandshakeRequest(
        deviceDetails: "iPhone 16 Pro",
        appVersion: "2.0.0"
    )

    try client.connectToServer(serverPeer, handshakeRequest: handshake)
}

Sending Data

Send raw Data

let data = Data("hello".utf8)
try client.send(data)

Send any Codable

struct ChatMessage: Codable {
    let text: String
}

try client.send(ChatMessage(text: "Hi"))

Send DataObject<T>

try client.sendDataObject(DataObject(content: "Ping"))

Receiving Data

Receive raw payloads

client.onReceive = { payload in
    print("Raw bytes:", payload.data.count, "from", payload.peerId.displayName)
}

Receive typed Codable

let token = client.receive(ChatMessage.self) { result in
    print("Message:", result.object.text, "from", result.peerId.displayName)
}

client.cancelReceiveDataObject(id: token)

Receive typed DataObject<T>

let token = client.receiveDataObject(String.self) { payload in
    print(payload.dataObject.content, payload.dataObject.timestamp)
}

client.cancelReceiveDataObject(id: token)

Handshake Behavior

  • If ServerConnectivityManager.invitationValidator is set:
    • Missing handshake context is rejected.
    • Invalid handshake payload is rejected.
    • Valid handshake is passed to your validator.
  • If no validator is set:
    • Invitations are accepted.

Kicking Peers

try server.kickPeer(peerId, forReason: "Not authorized")
// or
try server.kickPeers(server.connectedClients, forReason: "Server maintenance")

Client onKick is called and the client disconnects.

SwiftUI Debug View (iOS)

Use DemoView for local testing/onboarding.

Notes

  • displayName must be non-empty and <= 63 UTF-8 bytes.
  • serviceType must follow MultipeerConnectivity constraints.
  • State and receive callbacks are dispatched to the main queue.

About

A reliable MultipeerConnectivity wrapper for easy server/client implementation

Resources

Stars

Watchers

Forks

Languages