Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8a58ae1
fix: potential race condition - WPB-21441
caldrian Dec 3, 2025
7c658d7
create isApp, isBot, isAppOrBot
caldrian Dec 3, 2025
a32c61c
fix build errors in Wire-iOS
caldrian Dec 3, 2025
33ab6b6
fix error
caldrian Dec 3, 2025
86e95a5
revert renaming
caldrian Dec 3, 2025
291ff43
fix property names
caldrian Dec 4, 2025
6234722
add missing argument
caldrian Dec 4, 2025
9b55828
rename protocol
caldrian Dec 4, 2025
deabb1e
revert changes
caldrian Dec 4, 2025
b2d6409
Merge branch 'develop' into fix/potential-race-condition-WPB-21441
caldrian Dec 4, 2025
5a90951
Merge branch 'fix/potential-race-condition-WPB-21441' into chore/sepa…
caldrian Dec 4, 2025
4f2fd2d
Merge remote-tracking branch 'github/gh-readonly-queue/develop/pr-396…
caldrian Dec 4, 2025
b2230d5
Merge branch 'fix/potential-race-condition-WPB-21441' into chore/sepa…
caldrian Dec 4, 2025
59edd7c
fixes
caldrian Dec 4, 2025
bdb435e
minor change
caldrian Dec 4, 2025
bab57b6
rename protocol
caldrian Dec 4, 2025
a67301a
fix condition
caldrian Dec 4, 2025
9340106
Merge branch 'release/cycle-4.12' into chore/separate-isapp-isbot-WPB…
caldrian Dec 5, 2025
edb5c09
Trigger CI
caldrian Dec 8, 2025
f92bcd5
fix: add missing developer flag check - WPB-22238 (#3987)
samwyndham Dec 8, 2025
82076c5
fix typo
caldrian Dec 8, 2025
5f4aba5
Merge branch 'release/cycle-4.12' of github.com:wireapp/wire-ios into…
caldrian Dec 10, 2025
cf7e241
chore: separate user properties isApp and isBot - WPB-21441
caldrian Dec 10, 2025
104ec35
Merge branch 'chore/separate-isapp-isbot-WPB-21441' of github.com:wir…
caldrian Dec 10, 2025
375a366
chore: separate user properties isApp and isBot - WPB-21441
caldrian Dec 10, 2025
4b0b73d
Merge branch 'chore/separate-isapp-isbot-WPB-21441' of github.com:wir…
caldrian Dec 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ public final class MessageLocalStore: MessageLocalStoreProtocol {

let members = selfUserTeam.members.compactMap(\.user)
let guests = localParticipants.filter {
!$0.isApp && $0.membership == nil
!$0.isAppOrBot && $0.membership == nil
}

newConversationMessage.allTeamUsersAdded = localParticipants.isSuperset(of: members)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private extension FilesView {
}
}

if !viewModel.isRecycleBin {
if !viewModel.isRecycleBin, viewModel.isFoldersEnabled {
ToolbarItem(placement: .navigationBarTrailing) {
moreActionsButton
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extension CoreDataStackError: LocalizedError {
}
}

@objc
@objc(ZMContextProvider)
public protocol ContextProvider {

var account: Account { get }
Expand Down Expand Up @@ -114,8 +114,8 @@ public protocol CoreDataStackProtocol: ContextProvider {

}

@objcMembers
public class CoreDataStack: NSObject, CoreDataStackProtocol {
@objc @objcMembers
public class CoreDataStack: NSObject, CoreDataStackProtocol, ContextProvider {

public let account: Account

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ extension ZMConversation: ConversationLike {

public var sortedOtherParticipants: [UserType] {
localParticipants
.filter { !$0.isApp }
.filter { !$0.isAppOrBot }
.sortedAscendingPrependingNil(by: \.name)
}

public var sortedApps: [UserType] {
localParticipants
.filter(\.isApp)
.filter(\.isAppOrBot)
.sortedAscendingPrependingNil(by: \.name)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public extension ZMConversation {
internal class func keyPathsForValuesAffectingExternalParticipantsState() -> Set<String> {
[
"participantRoles.user.isApp",
"participantRoles.user.isBot",
"participantRoles.user.hasTeam",
"participantRoles.user.isExternalPartner"
]
Expand All @@ -63,7 +64,7 @@ public extension ZMConversation {
let selfUser = ZMUser.selfUser(in: managedObjectContext!)
let otherUsers = participants.subtracting([selfUser])

if otherUsers.count == 1, otherUsers.first!.isApp {
if otherUsers.count == 1, otherUsers.first!.isAppOrBot {
return []
}

Expand All @@ -75,7 +76,7 @@ public extension ZMConversation {
for user in otherUsers {
if canDisplayGuests, user.isFederated {
state.insert(.visibleRemotes)
} else if user.isApp {
} else if user.isAppOrBot {
state.insert(.visibleApps)
} else if canDisplayExternals, user.isExternalPartner {
state.insert(.visibleExternals)
Expand All @@ -98,7 +99,7 @@ public extension ZMConversation {
/// Returns whether apps are present, regardless of the display rules.

var areAppsPresent: Bool {
localParticipants.any(\.isApp)
localParticipants.any(\.isAppOrBot)
}

/// Returns whether guests are present, regardless of the display rules.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public extension ZMConversation {
team == selfUserTeam {

let members = selfUserTeam.members.compactMap(\.user)
let guests = users.filter { !$0.isApp && $0.membership == nil }
let guests = users.filter { !$0.isAppOrBot && $0.membership == nil }

systemMessage.allTeamUsersAdded = users.isSuperset(of: members)
systemMessage.numberOfGuestsAdded = Int16(guests.count)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ + (NSSet *)keyPathsForValuesAffectingIsReadOnly;
return [NSSet setWithObjects:ZMConversationConversationTypeKey, ZMConversationParticipantRolesKey, ZMConversationIsForcedReadOnlyKey, nil];
}

+ (instancetype)existingOneOnOneConversationWithUser:(ZMUser *)otherUser inUserSession:(id<ContextProvider>)session;
+ (instancetype)existingOneOnOneConversationWithUser:(ZMUser *)otherUser inUserSession:(id<ZMContextProvider>)session;
{
NOT_USED(session);
return otherUser.oneOnOneConversation;
Expand Down
9 changes: 5 additions & 4 deletions wire-ios-data-model/Source/Model/User/ServiceUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import Foundation

@objc
public protocol ServiceUser: AnyObject, UserType {
public protocol Bot: AnyObject, UserType {
var providerIdentifier: String? { get }
var serviceIdentifier: String? { get }
}

@objc
public protocol SearchServiceUser: ServiceUser {
public protocol BotSearchResult: Bot {
var summary: String? { get }
}

public typealias ServiceUser = Bot
public typealias SearchServiceUser = BotSearchResult

extension ZMUser {
static let servicesMustBeMentioned = false
static let serviceMentionKeyword = "@bots"
Expand Down
8 changes: 3 additions & 5 deletions wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import Foundation

extension Set<ZMUser> {

var apps: Set<ZMUser> {
filter(\.isApp)
var appsOrBots: Set<ZMUser> {
filter(\.isAppOrBot)
}

func categorizeServicesAndUser() -> (services: Set<ZMUser>, users: Set<ZMUser>) {
let services = apps
let services = appsOrBots
let users = subtracting(services)
return (services, users)
}
Expand Down
8 changes: 7 additions & 1 deletion wire-ios-data-model/Source/Model/User/UserType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,16 @@ public protocol UserType: NSObjectProtocol, UserConnections {
/// The role (and permissions) e.g. partner, member, admin, owner
var teamRole: TeamRole { get }

/// Whether this is an app/bot/service user.
/// Whether this is an app (new-style MLS service).

var isApp: Bool { get }

/// Whether this is a bot (old-style service).

var isBot: Bool { get }

var isAppOrBot: Bool { get }

/// Whether this uses uses SSO.
var usesCompanyLogin: Bool { get }

Expand Down
13 changes: 10 additions & 3 deletions wire-ios-data-model/Source/Model/User/ZMSearchUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ extension ZMSearchUser: SearchServiceUser {

@objc
public class ZMSearchUser: NSObject, UserType {

public var providerIdentifier: String?
public var summary: String?
public var assetKeys: SearchUserAssetKeys?
Expand Down Expand Up @@ -209,9 +210,15 @@ public class ZMSearchUser: NSObject, UserType {
}

public var isApp: Bool {
providerIdentifier != nil
false // Apps (new-style services) don't use ZMSearchUser
}

public var isBot: Bool {
providerIdentifier?.isEmpty == false
}

public var isAppOrBot: Bool { isBot }

public var usesCompanyLogin: Bool {
user?.usesCompanyLogin == true
}
Expand Down Expand Up @@ -474,7 +481,7 @@ public class ZMSearchUser: NSObject, UserType {
}

@objc
public init(
public required init(
contextProvider: ContextProvider,
name: String,
handle: String?,
Expand Down Expand Up @@ -650,7 +657,7 @@ public class ZMSearchUser: NSObject, UserType {
}

@objc public var canBeConnected: Bool {
guard !isApp else { return false }
guard !isAppOrBot else { return false }

if let user {
return user.canBeConnected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ public extension ZMUser {
return false
}

return !isApp // Bots are never guests
return !isAppOrBot // Apps or bots are never guests
&& !isFederated // Federated users are never guests
&& ZMUser.selfUser(in: context).hasTeam // There can't be guests in a team that doesn't exist
&& conversation.localParticipantsContain(user: self)
Expand Down
14 changes: 12 additions & 2 deletions wire-ios-data-model/Source/Model/User/ZMUser.m
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ @implementation ZMUser
return [NSSet setWithObjects:TypeKey, nil];
}

+ (NSSet<NSString *> *)keyPathsForValuesAffectingIsBot
{
return [NSSet setWithObjects:TypeKey, nil];
}

+ (NSSet<NSString *> *)keyPathsForValuesAffectingIsAppOrBot
{
return [NSSet setWithObjects:TypeKey, nil];
}

- (BOOL)isSelfUser
{
if ([self isZombieObject]) {
Expand Down Expand Up @@ -239,7 +249,7 @@ - (BOOL) managedByWire {

- (BOOL)canBeConnected;
{
if (self.isApp || self.isWirelessUser) {
if (self.isAppOrBot || self.isWirelessUser) {
return NO;
}
return ! self.isConnected && ! self.isPendingApprovalByOtherUser;
Expand Down Expand Up @@ -710,7 +720,7 @@ + (instancetype)selfUserInContext:(NSManagedObjectContext *)moc;

@implementation ZMUser (Utilities)

+ (ZMUser<ZMEditableUserType> *)selfUserInUserSession:(id<ContextProvider>)session
+ (ZMUser<ZMEditableUserType> *)selfUserInUserSession:(id<ZMContextProvider>)session
{
VerifyReturnNil(session != nil);
return [self selfUserInContext:session.viewContext];
Expand Down
10 changes: 9 additions & 1 deletion wire-ios-data-model/Source/Model/User/ZMUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,15 @@ extension ZMUser: UserType {
}

@objc public var isApp: Bool {
type == .app || type == .bot
type == .app
}

@objc public var isBot: Bool {
type == .bot
}

@objc public var isAppOrBot: Bool {
isApp || isBot
}

public var teamName: String? {
Expand Down
4 changes: 2 additions & 2 deletions wire-ios-data-model/Source/Model/ZMManagedObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ @interface ZMManagedObject ()

@implementation ZMManagedObject

+ (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inUserSession:(id<ContextProvider>)userSession
+ (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inUserSession:(id<ZMContextProvider>)userSession
{
VerifyReturnNil(url != nil);
VerifyReturnNil(userSession != nil);
Expand All @@ -56,7 +56,7 @@ + (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inManagedObject
return [psc managedObjectIDForURIRepresentation:url];
}

+ (instancetype)existingObjectWithID:(NSManagedObjectID *)identifier inUserSession:(id<ContextProvider>)userSession;
+ (instancetype)existingObjectWithID:(NSManagedObjectID *)identifier inUserSession:(id<ZMContextProvider>)userSession;
{
VerifyReturnNil(identifier);
VerifyReturnNil(userSession);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ extension ZMUser: ObjectInSnapshot {
#keyPath(ZMUser.readReceiptsEnabledChangedRemotely),
ZMUserKeys.RichProfile,
#keyPath(ZMUser.isApp),
#keyPath(ZMUser.isBot),
#keyPath(ZMUser.serviceIdentifier),
#keyPath(ZMUser.providerIdentifier),
ZMUserKeys.legalHoldRequest,
Expand Down
2 changes: 1 addition & 1 deletion wire-ios-data-model/Source/Public/ZMConversation.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ typedef NS_ENUM(int16_t, ZMConversationListIndicator) {
- (void)markAsUnread;

/// If that conversation exists, it is returned, @c nil otherwise.
+ (nullable instancetype)existingOneOnOneConversationWithUser:(nonnull ZMUser *)otherUser inUserSession:(nonnull id<ContextProvider> )session;
+ (nullable instancetype)existingOneOnOneConversationWithUser:(nonnull ZMUser *)otherUser inUserSession:(nonnull id<ZMContextProvider> )session;

@end

Expand Down
6 changes: 3 additions & 3 deletions wire-ios-data-model/Source/Public/ZMManagedObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@

extern NSString * _Nonnull const ZMDataPropertySuffix;

@protocol ContextProvider;
@protocol ZMContextProvider;

@interface ZMManagedObject : NSManagedObject

@property (nonatomic, readonly) BOOL isZombieObject;

+ (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inUserSession:(nullable id<ContextProvider>)userSession;
+ (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inUserSession:(nullable id<ZMContextProvider>)userSession;
+ (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inManagedObjectContext:(nullable NSManagedObjectContext *)context;
+ (nullable instancetype)existingObjectWithID:(nullable NSManagedObjectID *)identifier inUserSession:(nullable id<ContextProvider>)userSession;
+ (nullable instancetype)existingObjectWithID:(nullable NSManagedObjectID *)identifier inUserSession:(nullable id<ZMContextProvider>)userSession;
+ (nullable instancetype)existingObjectWithObjectIdentifier:(nullable NSString *)identifier inManagedObjectContext:(nullable NSManagedObjectContext *)context;

- (nullable NSString *)objectIDURLString;
Expand Down
2 changes: 1 addition & 1 deletion wire-ios-data-model/Source/Public/ZMUser.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ typedef NS_ENUM(int16_t, ZMBlockState) {

@interface ZMUser (Utilities)

+ (ZMUser<ZMEditableUserType> *_Nonnull)selfUserInUserSession:(id<ContextProvider> _Nonnull)session;
+ (ZMUser<ZMEditableUserType> *_Nonnull)selfUserInUserSession:(id<ZMContextProvider> _Nonnull)session;

@end

Expand Down
2 changes: 1 addition & 1 deletion wire-ios-data-model/Tests/Source/Model/TeamTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ final class TeamTests: ZMConversationTestsBase {
let guest = ZMUser.insertNewObject(in: uiMOC)
let bot = ZMUser.insertNewObject(in: uiMOC)
bot.type = .bot
XCTAssert(bot.isApp)
XCTAssert(bot.isAppOrBot)
guard let conversation = ZMConversation.insertGroupConversation(
moc: uiMOC,
participants: [guest, bot],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ final class ZMSearchUserPayloadParsingTests: ZMBaseManagedObjectTest {
XCTAssertEqual(user.domain, domain)
XCTAssertEqual(user.remoteIdentifier, uuid)
XCTAssertEqual(user.zmAccentColor?.rawValue, 5)
XCTAssertFalse(user.isApp)
XCTAssertFalse(user.isAppOrBot)
XCTAssertTrue(user.canBeConnected)
}

Expand All @@ -88,7 +88,7 @@ final class ZMSearchUserPayloadParsingTests: ZMBaseManagedObjectTest {
)!

// then
XCTAssertTrue(user.isApp)
XCTAssertTrue(user.isAppOrBot)
XCTAssertEqual(user.summary, "Short summary")
XCTAssertEqual(user.providerIdentifier, provider.transportString())
XCTAssertEqual(user.serviceIdentifier, uuid.transportString())
Expand Down
2 changes: 1 addition & 1 deletion wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -1927,7 +1927,7 @@ - (void)testThatItReturnsCorrectUserNameForService
user.name = @"User Name";
[user setValue:@(ZMTypeOfUserBot) forKey:@"typeValue"];

XCTAssertTrue(user.isApp);
XCTAssertTrue(user.isAppOrBot);
XCTAssertEqualObjects(user.name, @"User Name");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

@interface ZMSyncStrategy : NSObject <TearDownCapable, RequestStrategy>

- (instancetype _Nonnull )initWithContextProvider:(id<ContextProvider> _Nonnull)contextProvider
- (instancetype _Nonnull )initWithContextProvider:(id<ZMContextProvider> _Nonnull)contextProvider
notificationsDispatcher:(NotificationDispatcher * _Nonnull)notificationsDispatcher
operationStatus:(OperationStatus * _Nonnull)operationStatus
application:(id<ZMApplication> _Nonnull)application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ @implementation ZMSyncStrategy
ZM_EMPTY_ASSERTING_INIT()


- (instancetype)initWithContextProvider:(id<ContextProvider>)contextProvider
- (instancetype)initWithContextProvider:(id<ZMContextProvider>)contextProvider
notificationsDispatcher:(NotificationDispatcher *)notificationsDispatcher
operationStatus:(OperationStatus *)operationStatus
application:(id<ZMApplication>)application
Expand Down
Loading