You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -329,213 +329,53 @@ With a document-based SwiftUI app, the SwiftUI app framework owns the lifetime o
329
329
If the file saved from the document-based app is stored in iCloud, the operating system may destroy an existing instance and re-create it from the contents on device - most notably after having replicated the file with iCloud.
330
330
There may be other instances of where the document can be rebuilt, but the important aspect to note is that SwiftUI is in control of that instance's lifecycle.
331
331
332
-
To provide peer to peer syncing, MeetingNotes handles the document being ephemeral by enabling an app-level sync coordinator: [DocumentSyncCoordinator.swift](https://github.com/automerge/MeetingNotes/blob/main/MeetingNotes/PeerNetworking/DocumentSyncCoordinator.swift)
333
-
This coordinator has properties for tracking Documents using identifiers that represent those documents and identifiers to represent the peers it syncs with.
334
-
The sync coordinator presents itself as an `Observable` object for more convenient use within SwiftUI views, to provide information about peers, connections, and exposing a control to establish a new connection.
335
-
336
-
When MeetingNotes enables sync for a document, it registers a document with the SyncCoordinator, which builds a [NWTextRecord](https://developer.apple.com/documentation/network/nwtxtrecord) instance to use in advertising that the document.
On activating sync, the coordinator activates both an [NWBrowser](https://developer.apple.com/documentation/network/nwbrowser) and [NWListener](https://developer.apple.com/documentation/network/nwlistener) instance.
351
-
In addition to activating the network services, the coordinator starts a timer to drive checks for document updates to determine if they should send a network sync message.
352
-
When a connection is established, it subscribes to the timer to drive checks to sync the Automerge document.
353
-
354
-
#### Network Browser
355
-
356
-
The browser looks for nearby peers that the app can sync with, while the listener provides the means to accept network connections.
357
-
The actual sync connection can be initiated by either peer, and only one needs to be initiated to support sync.
358
-
359
-
The browser filters results by the type of network protocol it is initialized with: [AutomergeSyncProtocol](https://github.com/automerge/MeetingNotes/blob/main/MeetingNotes/PeerNetworking/AutomergeSyncProtocol.swift).
360
-
The `NWBrowser` instance sees all available listeners, including itself, when the listener is active.
361
-
The handler that processes browser updates filters the results to only show other peers on the network.
332
+
To provide peer to peer syncing, MeetingNotes uses the [automerge-repo-swift package](https://github.com/automerge/automerge-repo-swift).
333
+
It creates a single globally available instance of a repository to track documents that are loaded by the SwiftUI document-based app.
334
+
To provide the network connections, it also creates an instance of a `WebSocketprovider` and `PeerToPeerProvider`, and adds those to the repository at the end of app initialization:
362
335
363
336
```swift
364
-
// Only show broadcasting peers that doesn't have the name
MeetingNotes automatically connects to a new peer listed within [NWBrowser.Result](https://developer.apple.com/documentation/network/nwbrowser/result) when running on iOS.
380
-
The view that shows these results also provides a button to establish a connection manually.
381
-
The auto-connect waits for a short, random period of time before establishing an automatic connection.
382
-
383
-
#### Network Listener
384
-
385
-
To accept a connection, the coordinator activates a bonjour listener for the document being shared.
386
-
Within MeetingNotes, the listener is configured with the sync protocol, a `NWTxtRecord` that describes the document, and network parameters to configure TCP and TLS.
387
-
MeetingNotes uses the document identifier as a pre-shared TLS secret, which both enables encryption and constraints sync connections to other instances that use this same convention.
388
-
389
-
> Warning: Using a pre-shared secret is _not_ a recommended security practice, and this example makes no attestations of being a secure means of encrypting the communications.
390
-
391
-
While the browser receives the published TXTRecord of the peer with the Bonjour notifications, the Listener only knows that it has received a connection.
392
-
Because of this, at the start, who initiated the connection is unknown.
393
-
MeetingNotes accepts any full connections that get fully established with TLS, using the document identifier as a shared key.
394
-
A more fully developed application might also track and determine acceptability of connections using additional information - either embedded within the network sync protocol or passed as parameters within the protocol.
395
-
396
-
Once MeetingNotes accepts a connection, it creates an instance of [SyncConnection](https://github.com/automerge/MeetingNotes/blob/main/MeetingNotes/PeerNetworking/SyncConnection.swift).
397
-
398
-
#### Syncing over a connection
399
-
400
-
`SyncConnection` tracks the state of a connection as well as the sync state with a peer.
401
-
It is initialized with a [NWConnection](https://developer.apple.com/documentation/network/nwconnection), the identifier for the document.
402
-
It maintains it's own identifier and establishes an instance of `SyncState` to track the state of the peer on the other side of the connection.
403
-
404
-
Upon initialization, the connection wrapper subscribes to the timer provided by the sync coordinator.
405
-
The `SyncConnection` uses the timer signal to drive a check to determine if a sync message should be sent.
The underlying network protocol only sends an event if the call to `generateSyncMessage(state:)` returns non-nil data.
425
-
The heart of the synchronization happens when the connection receives a network protocol sync message.
426
-
This message is structured wrapper around the sync bytes from another Automerge document, along with a minimal type-of-message identifier, taking advantage of the [Network framework](https://developer.apple.com/documentation/network) to frame and establish the messages being transferred.
427
-
Once received, the connection uses [NWProtocolFramer](https://developer.apple.com/documentation/network/nwprotocolframer) to retrieve the message from the bytes sent over the network, and delegates receiving the message to be processed if complete, before waiting for the next message on the network.
428
-
429
-
```swift
430
-
privatefuncreceiveNextMessage() {
431
-
guardlet connection = connection else {
432
-
return
433
-
}
362
+
The SwiftUI document-based API is all synchronous, so loading an Automerge document it provides is down within the view when it first appears.
434
363
435
-
connection.receiveMessage { content, context, isComplete, error in
436
-
Logger.syncConnection
437
-
.debug(
438
-
"\(self.shortId, privacy: .public): Received a \(isComplete ?"complete":"incomplete", privacy: .public) msg on connection"
439
-
)
440
-
iflet content {
441
-
Logger.syncConnection.debug(" - received \(content.count) bytes")
442
-
} else {
443
-
Logger.syncConnection.debug(" - received no data with msg")
444
-
}
445
-
// Extract your message type from the received context.
446
-
iflet syncMessage = context?
447
-
.protocolMetadata(
448
-
definition: AutomergeSyncProtocol.definition
449
-
) as? NWProtocolFramer.Message,
450
-
let endpoint =self.connection?.endpoint
451
-
{
452
-
self.receivedMessage(
453
-
content: content,
454
-
message: syncMessage,
455
-
from: endpoint)
456
-
}
457
-
if error ==nil {
458
-
// Continue to receive more messages until we receive
459
-
// an error.
460
-
self.receiveNextMessage()
461
-
} else {
462
-
Logger.syncConnection.error(" - error on received message: \(error)")
463
-
self.cancel()
464
-
}
465
-
}
466
-
}
467
364
```
468
-
469
-
The connection processes the received sync protocol message with the `receivedMessage` function, using the identifier of the document stored with the connection to retrieve a reference to the document instance.
470
-
Neither the connection, nor the sync coordinator object, can maintain a stable reference to the Automerge document instance because SwiftUI owns the life-cycle of the app's `ReferenceFileDocument` subclass.
471
-
To work around SwiftUI replacing this class, the coordinator maintains and updates references as `Document` subclasses register themselves, in order to provide a quick lookup by the document's identifier.
472
-
473
-
With a reference to the document, the method invokes `receiveSyncMessageWithPatches(state:message:)` to receive any provided changes, and uses the returns array of `Patch` to log how many patches were returned.
474
-
Immediately after receiving an update, the function calls `generateSyncMessage(state:)` to determine if the additional sync messages are needed, and sends a return sync message if the function returns any data.
fatalError("Crashed loading the document: \(error.localizedDescription)")
528
374
}
529
375
}
530
376
```
531
377
532
-
With this pattern established on both sides of a Bonjour connection, once a sync process is initiated, the functions send messages back and forth until a sync is complete.
533
-
The timer, provided from the sync coordinator, is only needed to start to drive sync messages when changes have occurred locally.
534
-
535
-
> Note: The messages that contain changes to sync generated by Automerge are _not_ guaranteed to have all the updates needed within a single round trip.
536
-
The underlying mechanism optimizes for sharing the state of heads initially, resulting in a small initial message, followed by sets of changes from either side.
537
-
The full sync process is iterative, which allows for efficient sync even when the two peers may be concurrently syncing with other, unseen or unknown, peers.
538
-
539
-
The timer frequency in MeetingNotes is intentionally set to a short value to drive sync updates frequently enough to appear to "sync with each keystroke" to show off interactively collaboration.
540
-
Your own app may not need, or want, to drive a network sync this frequently.
541
-
378
+
Once added to the repository, toolbar buttons on the `MeetingNotesDocumentView` toggle a WebSocket connection or activate the peer to peer networking.
379
+
`PeerSyncView` provides information about available peers on your local network, and allows you to explicitly connect to those peers.
380
+
The repository handles syncing automatically as the Automerge document is updated.
381
+
Both the WebSocket and peer-to-peer networking implement the Automerge sync protocol over their respective transports.
0 commit comments