You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionMessagingKit/Jobs/GroupLeavingJob.swift

213 lines
9.4 KiB
Swift

// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
Merge remote-tracking branch 'upstream/dev' into feature/updated-user-config-handling # Conflicts: # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Home/HomeVC.swift # Session/Home/HomeViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Shared/FullConversationCell.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Models/SessionThread.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKit/Shared Models/SessionThreadViewModel.swift # SessionUIKit/Utilities/UIContextualAction+Theming.swift # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
2 years ago
import Combine
import GRDB
import SignalCoreKit
import SessionUtilitiesKit
import SessionSnodeKit
public enum GroupLeavingJob: JobExecutor {
public static var maxFailureCount: Int = 0
public static var requiresThreadId: Bool = true
public static var requiresInteractionId: Bool = true
public static func run(
_ job: Job,
queue: DispatchQueue,
success: @escaping (Job, Bool, Dependencies) -> (),
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
deferred: @escaping (Job, Dependencies) -> (),
using dependencies: Dependencies = Dependencies()
) {
guard
let detailsData: Data = job.details,
let details: Details = try? JSONDecoder(using: dependencies).decode(Details.self, from: detailsData),
Merge remote-tracking branch 'upstream/dev' into feature/updated-user-config-handling # Conflicts: # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Home/HomeVC.swift # Session/Home/HomeViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Shared/FullConversationCell.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Models/SessionThread.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKit/Shared Models/SessionThreadViewModel.swift # SessionUIKit/Utilities/UIContextualAction+Theming.swift # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
2 years ago
let threadId: String = job.threadId,
let interactionId: Int64 = job.interactionId
else {
SNLog("[GroupLeavingJob] Failed due to missing details")
Merge remote-tracking branch 'upstream/dev' into feature/job-runner-unit-tests # Conflicts: # Session.xcodeproj/project.pbxproj # Session/Meta/Session-Prefix.pch # Session/Notifications/SyncPushTokensJob.swift # Session/Utilities/BackgroundPoller.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Models/Profile.swift # SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift # SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift # SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift # SessionMessagingKit/Jobs/Types/FailedAttachmentDownloadsJob.swift # SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift # SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift # SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift # SessionMessagingKit/Jobs/Types/MessageSendJob.swift # SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift # SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift # SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift # SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift # SessionMessagingKit/Sending & Receiving/MessageSender.swift # SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift # SessionMessagingKit/Utilities/AppReadiness.m # SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift # SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift # SessionShareExtension/ShareNavController.swift # SessionSnodeKit/Jobs/GetSnodePoolJob.swift # SessionUtilitiesKit/Configuration.swift # SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift # SignalUtilitiesKit/Meta/SignalUtilitiesKit.h # SignalUtilitiesKit/Utilities/SSKAsserts.h
2 years ago
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
}
Merge remote-tracking branch 'upstream/dev' into feature/updated-user-config-handling # Conflicts: # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Home/HomeVC.swift # Session/Home/HomeViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Shared/FullConversationCell.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Models/SessionThread.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKit/Shared Models/SessionThreadViewModel.swift # SessionUIKit/Utilities/UIContextualAction+Theming.swift # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
2 years ago
let destination: Message.Destination = .closedGroup(groupPublicKey: threadId)
dependencies[singleton: .storage]
.writePublisher { db -> LeaveType in
guard
let threadVariant: SessionThread.Variant = try? SessionThread
.filter(id: threadId)
.select(.variant)
.asRequest(of: SessionThread.Variant.self)
.fetchOne(db)
else {
SNLog("[GroupLeavingJob] Failed due to non-existent group conversation")
Merge remote-tracking branch 'upstream/dev' into feature/updated-user-config-handling # Conflicts: # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Home/HomeVC.swift # Session/Home/HomeViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Shared/FullConversationCell.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Models/SessionThread.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKit/Shared Models/SessionThreadViewModel.swift # SessionUIKit/Utilities/UIContextualAction+Theming.swift # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
2 years ago
throw MessageSenderError.noThread
2 years ago
}
Merge remote-tracking branch 'upstream/dev' into feature/updated-user-config-handling # Conflicts: # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Home/HomeVC.swift # Session/Home/HomeViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Shared/FullConversationCell.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Models/SessionThread.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKit/Shared Models/SessionThreadViewModel.swift # SessionUIKit/Utilities/UIContextualAction+Theming.swift # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
2 years ago
guard (try? ClosedGroup.exists(db, id: threadId)) == true else {
SNLog("[GroupLeavingJob] Failed due to non-existent group")
Merge remote-tracking branch 'upstream/dev' into feature/updated-user-config-handling # Conflicts: # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Home/HomeVC.swift # Session/Home/HomeViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Shared/FullConversationCell.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Models/SessionThread.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKit/Shared Models/SessionThreadViewModel.swift # SessionUIKit/Utilities/UIContextualAction+Theming.swift # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
2 years ago
throw MessageSenderError.invalidClosedGroupUpdate
2 years ago
}
let userSessionId: SessionId = getUserSessionId(db, using: dependencies)
let isAdminUser: Bool = GroupMember
.filter(GroupMember.Columns.groupId == threadId)
.filter(GroupMember.Columns.profileId == userSessionId.hexString)
.filter(GroupMember.Columns.role == GroupMember.Role.admin)
.isNotEmpty(db)
let numAdminUsers: Int = (try? GroupMember
.filter(GroupMember.Columns.groupId == threadId)
.filter(GroupMember.Columns.role == GroupMember.Role.admin)
.distinct()
.fetchCount(db))
.defaulting(to: 0)
switch (threadVariant, details.behaviour, (isAdminUser && numAdminUsers == 1)) {
case (.legacyGroup, _, _):
// Legacy group only supports the 'leave' behaviour so don't bother checking
return .leave(
try MessageSender.preparedSend(
db,
message: ClosedGroupControlMessage(kind: .memberLeft),
to: destination,
namespace: destination.defaultNamespace,
interactionId: job.interactionId,
fileIds: [],
using: dependencies
)
)
case (.group, .leave, false):
return .leave(
try MessageSender.preparedSend(
db,
message: GroupUpdateMemberLeftMessage(),
to: destination,
namespace: destination.defaultNamespace,
interactionId: job.interactionId,
fileIds: [],
using: dependencies
)
)
case (.group, .delete, _), (.group, .leave, true):
try SessionUtil.deleteGroupForEveryone(
db,
groupSessionId: SessionId(.group, hex: threadId),
using: dependencies
)
return .delete
default: throw MessageSenderError.invalidClosedGroupUpdate
}
}
.flatMap { leaveType -> AnyPublisher<Void, Error> in
switch leaveType {
case .leave(let leaveMessage):
return leaveMessage
.send(using: dependencies)
.map { _ in () }
.eraseToAnyPublisher()
case .delete:
return ConfigurationSyncJob
.run(sessionIdHexString: threadId, using: dependencies)
.map { _ in () }
.eraseToAnyPublisher()
}
}
.tryCatch { error -> AnyPublisher<Void, Error> in
/// If it failed due to one of these errors then clear out any associated data (as the `SessionThread` exists but
/// either the data required to send the `MEMBER_LEFT` message doesn't or the user has had their access to the
/// group revoked which would leave the user in a state where they can't leave the group)
switch (error as? MessageSenderError, error as? SnodeAPIError) {
case (.invalidClosedGroupUpdate, _), (.noKeyPair, _), (.encryptionFailed, _),
(_, .unauthorised), (_, .invalidAuthentication):
return Just(()).setFailureType(to: Error.self).eraseToAnyPublisher()
default: throw error
}
}
.subscribe(on: queue, using: dependencies)
.receive(on: queue, using: dependencies)
.sinkUntilComplete(
receiveCompletion: { result in
switch result {
case .failure(let error):
let updatedBody: String = {
switch details.behaviour {
case .leave: return "group_unable_to_leave".localized()
case .delete: return "group_unable_to_delete".localized()
}
}()
// Update the interaction to indicate we failed to leave the group
dependencies[singleton: .storage].writeAsync(using: dependencies) { db in
try Interaction
.filter(id: interactionId)
.updateAll(
db,
Interaction.Columns.variant
.set(to: Interaction.Variant.infoGroupCurrentUserErrorLeaving),
Interaction.Columns.body.set(to: updatedBody)
)
}
failure(job, error, true, dependencies)
case .finished:
// Remove all of the group data
dependencies[singleton: .storage].writeAsync(using: dependencies) { db in
try ClosedGroup.removeData(
db,
threadIds: [threadId],
dataToRemove: .allData,
calledFromConfig: nil,
using: dependencies
)
}
success(job, false, dependencies)
Merge remote-tracking branch 'upstream/dev' into feature/updated-user-config-handling # Conflicts: # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Home/HomeVC.swift # Session/Home/HomeViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Shared/FullConversationCell.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Models/SessionThread.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKit/Shared Models/SessionThreadViewModel.swift # SessionUIKit/Utilities/UIContextualAction+Theming.swift # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
2 years ago
}
}
)
}
}
// MARK: - GroupLeavingJob.Details
extension GroupLeavingJob {
public struct Details: Codable {
private enum CodingKeys: String, CodingKey {
case behaviour
}
public enum Behaviour: Int, Codable {
/// Will leave the group, deleting it from the current device but letting the group continue to exist as long
/// the current user isn't the only admin
case leave = 1
/// Will permanently delete the group, this will result in the group being deleted from all member devices
case delete = 2
}
public let behaviour: Behaviour
// MARK: - Initialization
public init(behaviour: Behaviour) {
self.behaviour = behaviour
}
}
}
// MARK: - Convenience
private extension GroupLeavingJob {
enum LeaveType {
case leave(HTTP.PreparedRequest<Void>)
case delete
}
}