Repository navigation
Expand file tree
/
Copy pathPresence.swift
More file actions
144 lines (131 loc) · 4.62 KB
/
Copy pathPresence.swift
File metadata and controls
144 lines (131 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//
// Presence.swift
// Realtime
//
// Created by Guilherme Souza on 29/10/26.
//
public import Foundation
/// One tracked client under a presence key.
///
/// A presence key groups every client that tracks under it, so one key holds one entry per
/// device or tab. Set the key with ``RealtimeChannelConfiguration/Presence-swift.struct/key``;
/// when it is `nil`, the server gives each client its own random key.
///
/// ## Topics
/// ### Properties
/// - ``ref``
/// - ``previousRef``
/// - ``payload``
/// ### Decoding
/// - ``decode(as:decoder:)``
public struct PresenceEntry: Sendable, Hashable {
/// The server-assigned reference that identifies this entry.
public var ref: String
/// The reference of the entry this one replaced, when the client called
/// ``RealtimeChannel/track(_:encoder:)`` again.
public var previousRef: String?
/// The payload the client passed to ``RealtimeChannel/track(_:encoder:)``.
public var payload: JSONObject
/// Creates a presence entry.
///
/// - Parameters:
/// - ref: The server-assigned reference.
/// - previousRef: The reference of the entry this one replaced.
/// - payload: The tracked payload.
public init(ref: String, previousRef: String? = nil, payload: JSONObject) {
self.ref = ref
self.previousRef = previousRef
self.payload = payload
}
/// Decodes ``payload`` into a `Decodable` model.
///
/// - Parameters:
/// - _: The target type. Can be inferred from context.
/// - decoder: The decoder to use. `nil` uses `JSONDecoder.supabase()`.
/// - Returns: The decoded payload.
/// - Throws: A `DecodingError` when the payload does not match `T`.
public func decode<T: Decodable>(
as _: T.Type = T.self,
decoder: JSONDecoder? = nil
) throws -> T {
try payload.decode(as: T.self, decoder: decoder)
}
}
/// The clients present on a channel, by presence key.
///
/// Read it with ``RealtimeChannel/presenceState`` or observe it with
/// ``RealtimeChannel/presenceStates()``.
///
/// ## Topics
/// ### Properties
/// - ``entries``
/// ### Decoding
/// - ``decode(as:decoder:ignoringUndecodable:)``
public struct PresenceState: Sendable, Hashable {
/// The entries of each presence key. A key with several entries is a client on several
/// devices or tabs.
public var entries: [String: [PresenceEntry]]
/// Creates a presence state.
///
/// - Parameter entries: The entries of each presence key.
public init(entries: [String: [PresenceEntry]] = [:]) {
self.entries = entries
}
/// Decodes the payload of every entry into a `Decodable` model.
///
/// - Parameters:
/// - _: The target type. Can be inferred from context.
/// - decoder: The decoder to use. `nil` uses `JSONDecoder.supabase()`.
/// - ignoringUndecodable: When `true`, entries that do not decode as `T` are skipped, and
/// keys with no decoded entry are left out. Clients that track other payloads, or none,
/// do not make the call fail. Defaults to `true`.
/// - Returns: The decoded payloads of each presence key.
/// - Throws: A `DecodingError` when `ignoringUndecodable` is `false` and an entry does not
/// decode as `T`.
public func decode<T: Decodable>(
as _: T.Type = T.self,
decoder: JSONDecoder? = nil,
ignoringUndecodable: Bool = true
) throws -> [String: [T]] {
var decoded: [String: [T]] = [:]
for (key, entries) in entries {
let values: [T] =
if ignoringUndecodable {
entries.compactMap { try? $0.decode(as: T.self, decoder: decoder) }
} else {
try entries.map { try $0.decode(as: T.self, decoder: decoder) }
}
if !values.isEmpty { decoded[key] = values }
}
return decoded
}
}
/// The entries that joined and left a channel's presence in one sync.
///
/// Received from ``RealtimeChannel/presenceChanges()``. An update to a tracked payload shows up
/// as a join of the new entry, with ``PresenceEntry/previousRef`` set, and a leave of the old one.
///
/// ## Topics
/// ### Properties
/// - ``joins``
/// - ``leaves``
public struct PresenceChange: Sendable, Hashable {
/// The entries that joined, by presence key.
public var joins: [String: [PresenceEntry]]
/// The entries that left, by presence key.
public var leaves: [String: [PresenceEntry]]
/// Creates a presence change.
///
/// - Parameters:
/// - joins: The entries that joined, by presence key.
/// - leaves: The entries that left, by presence key.
public init(
joins: [String: [PresenceEntry]] = [:], leaves: [String: [PresenceEntry]] = [:]
) {
self.joins = joins
self.leaves = leaves
}
var isEmpty: Bool {
joins.isEmpty && leaves.isEmpty
}
}