Skip to content

Commit 2090206

Browse files
XEP-0359: Message deduplication based on stanza-id
1 parent 7e12b7b commit 2090206

17 files changed

+154
-66
lines changed

ChatSecure.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@
429429
D943AA441E6A0BB9007F3564 /* XMPPAccountCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D943AA411E6A0BA3007F3564 /* XMPPAccountCell.xib */; };
430430
D94ACBA41DFA206500B8C0F5 /* OTRBuddyCache.h in Headers */ = {isa = PBXBuildFile; fileRef = D94ACBA21DFA206500B8C0F5 /* OTRBuddyCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
431431
D94ACBA51DFA206500B8C0F5 /* OTRBuddyCache.m in Sources */ = {isa = PBXBuildFile; fileRef = D94ACBA31DFA206500B8C0F5 /* OTRBuddyCache.m */; };
432+
D94D5A0F1F98132300AC23BF /* XMPPMessage+ChatSecure.swift in Sources */ = {isa = PBXBuildFile; fileRef = D94D5A0E1F98132300AC23BF /* XMPPMessage+ChatSecure.swift */; };
432433
D955157A1EFA0A9F008AA429 /* HTMLReader.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9EC476C1EFA03FC00C39B25 /* HTMLReader.framework */; };
433434
D955157B1EFA0A9F008AA429 /* KVOController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9EC476E1EFA03FC00C39B25 /* KVOController.framework */; };
434435
D955157D1EFA0A9F008AA429 /* MBProgressHUD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9EC47701EFA03FC00C39B25 /* MBProgressHUD.framework */; };
@@ -1095,6 +1096,7 @@
10951096
D943AA411E6A0BA3007F3564 /* XMPPAccountCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = XMPPAccountCell.xib; sourceTree = "<group>"; };
10961097
D94ACBA21DFA206500B8C0F5 /* OTRBuddyCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OTRBuddyCache.h; sourceTree = "<group>"; };
10971098
D94ACBA31DFA206500B8C0F5 /* OTRBuddyCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OTRBuddyCache.m; sourceTree = "<group>"; };
1099+
D94D5A0E1F98132300AC23BF /* XMPPMessage+ChatSecure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XMPPMessage+ChatSecure.swift"; sourceTree = "<group>"; };
10981100
D95553D71C4F16730019667A /* ChatSecure.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = ChatSecure.entitlements; sourceTree = "<group>"; };
10991101
D9599BB91E284BCA006A4450 /* StringsConverter.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; name = StringsConverter.py; path = Strings/StringsConverter.py; sourceTree = "<group>"; };
11001102
D9599BBA1E284BCA006A4450 /* StringsUnused.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; name = StringsUnused.py; path = Strings/StringsUnused.py; sourceTree = "<group>"; };
@@ -1331,6 +1333,7 @@
13311333
633105011A16D1A300C17BAE /* Categories */ = {
13321334
isa = PBXGroup;
13331335
children = (
1336+
D94D5A0E1F98132300AC23BF /* XMPPMessage+ChatSecure.swift */,
13341337
D997E3F21EAECD1400BF72EF /* UITableView+ChatSecure.h */,
13351338
D997E3F31EAECD1400BF72EF /* UITableView+ChatSecure.m */,
13361339
D97175511E60D59300675DDE /* YapDatabaseViewConnection+ChatSecure.h */,
@@ -3091,6 +3094,7 @@
30913094
D93DDA7E1BA79A2400CD8331 /* UIViewController+ChatSecure.m in Sources */,
30923095
6358FDE01CDC097A00C9D3B6 /* OTRXMPPMessageStatusModule.swift in Sources */,
30933096
D93DDA7F1BA79A2400CD8331 /* JSQMessagesCollectionViewCell+ChatSecure.m in Sources */,
3097+
D94D5A0F1F98132300AC23BF /* XMPPMessage+ChatSecure.swift in Sources */,
30943098
D93DDA801BA79A2400CD8331 /* NSString+ChatSecure.m in Sources */,
30953099
D93DDA811BA79A2400CD8331 /* NSFileManager+ChatSecure.m in Sources */,
30963100
D93DDA821BA79A2400CD8331 /* OTRAttachmentPicker.m in Sources */,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// XMPPMessage+ChatSecure.swift
3+
// ChatSecureCore
4+
//
5+
// Created by Chris Ballinger on 10/18/17.
6+
// Copyright © 2017 Chris Ballinger. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public extension XMPPMessage {
12+
/// Safely extracts XEP-0359 stanza-id
13+
@objc public func extractStanzaId(account: OTRXMPPAccount) -> String? {
14+
let stanzaIds = self.stanzaIds
15+
guard stanzaIds.count > 0,
16+
let xmpp = OTRProtocolManager.shared.protocol(for: account) as? OTRXMPPManager,
17+
xmpp.xmppCapabilities.hasValidStanzaId(self) else {
18+
return nil
19+
}
20+
var byJID: XMPPJID? = nil
21+
if self.isGroupChatMessage {
22+
byJID = self.from?.bareJID
23+
} else {
24+
byJID = account.bareJID
25+
}
26+
if let jid = byJID {
27+
return stanzaIds[jid]
28+
}
29+
return nil
30+
}
31+
}

ChatSecure/Classes/Controllers/OTRDatabaseManager.m

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
NSString *const OTRYapDatabaseSignalPreKeyIdSecondaryIndexColumnName = @"OTRYapDatabaseSignalPreKeyIdSecondaryIndexColumnName";
4040
NSString *const OTRYapDatabaseSignalPreKeyAccountKeySecondaryIndexColumnName = @"OTRYapDatabaseSignalPreKeyAccountKeySecondaryIndexColumnName";
4141

42+
4243
@interface OTRDatabaseManager ()
4344

4445
@property (nonatomic, strong, nullable) YapDatabase *database;
@@ -234,6 +235,8 @@ - (YapDatabaseSecondaryIndex *)setupMessageSecondaryIndexes {
234235
[setup addColumn:OTRYapDatabaseRemoteMessageIdSecondaryIndexColumnName withType:YapDatabaseSecondaryIndexTypeText];
235236
[setup addColumn:OTRYapDatabaseMessageThreadIdSecondaryIndexColumnName withType:YapDatabaseSecondaryIndexTypeText];
236237
[setup addColumn:OTRYapDatabaseUnreadMessageSecondaryIndexColumnName withType:YapDatabaseSecondaryIndexTypeInteger];
238+
[setup addColumn:SecondaryIndexName.originId withType:YapDatabaseSecondaryIndexTypeText];
239+
[setup addColumn:SecondaryIndexName.stanzaId withType:YapDatabaseSecondaryIndexTypeText];
237240

238241
YapDatabaseSecondaryIndexHandler *indexHandler = [YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction * _Nonnull transaction, NSMutableDictionary * _Nonnull dict, NSString * _Nonnull collection, NSString * _Nonnull key, id _Nonnull object) {
239242
if ([object conformsToProtocol:@protocol(OTRMessageProtocol)])
@@ -249,10 +252,16 @@ - (YapDatabaseSecondaryIndex *)setupMessageSecondaryIndexes {
249252
if (message.threadId) {
250253
[dict setObject:message.threadId forKey:OTRYapDatabaseMessageThreadIdSecondaryIndexColumnName];
251254
}
255+
if (message.originId.length) {
256+
[dict setObject:message.originId forKey:SecondaryIndexName.originId];
257+
}
258+
if (message.stanzaId.length) {
259+
[dict setObject:message.stanzaId forKey:SecondaryIndexName.stanzaId];
260+
}
252261
}
253262
}];
254263

255-
YapDatabaseSecondaryIndex *secondaryIndex = [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:indexHandler versionTag:@"2"];
264+
YapDatabaseSecondaryIndex *secondaryIndex = [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:indexHandler versionTag:@"5"];
256265

257266
return secondaryIndex;
258267
}

ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ NS_ASSUME_NONNULL_BEGIN
4141
@property (nonatomic, strong, readonly) OTRXMPPAccount *account;
4242

4343
@property (nonatomic, strong, readonly) XMPPRoster *xmppRoster;
44+
@property (nonatomic, strong, readonly) XMPPCapabilities *xmppCapabilities;
4445
@property (nonatomic, strong, readonly) OTRXMPPRoomManager *roomManager;
4546
@property (nonatomic, strong, nullable) OTROMEMOSignalCoordinator *omemoSignalCoordinator;
4647
@property (nonatomic, strong, readonly) OTRServerCapabilities *serverCapabilities;

ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager.m

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -953,11 +953,8 @@ - (void)xmppStream:(XMPPStream *)sender didFailToSendIQ:(XMPPIQ *)iq error:(NSEr
953953
- (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error
954954
{
955955
DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, message, error);
956-
if (![message.elementID length]) {
957-
return;
958-
}
959956
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
960-
[transaction enumerateMessagesWithId:message.elementID block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
957+
[transaction enumerateMessagesWithElementId:message.elementID originId:message.originId stanzaId:nil block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
961958
if ([databaseMessage isKindOfClass:[OTRBaseMessage class]]) {
962959
((OTRBaseMessage *)databaseMessage).error = error;
963960
[(OTRBaseMessage *)databaseMessage saveWithTransaction:transaction];

ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager_Private.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ NS_ASSUME_NONNULL_BEGIN
2323
@property (nonatomic, strong, readonly) XMPPReconnect *xmppReconnect;
2424
@property (nonatomic, strong, readonly) XMPPvCardTempModule *xmppvCardTempModule;
2525
@property (nonatomic, strong, readonly) XMPPvCardAvatarModule *xmppvCardAvatarModule;
26-
@property (nonatomic, strong, readonly) XMPPCapabilities *xmppCapabilities;
2726
@property (nonatomic, strong, readonly) XMPPCapabilitiesCoreDataStorage *xmppCapabilitiesStorage;
2827
@property (nonatomic, strong, readonly) OTRYapDatabaseRosterStorage * xmppRosterStorage;
2928
@property (nonatomic, strong) OTRCertificatePinning * certificatePinningModule;

ChatSecure/Classes/Controllers/XMPP/OTRXMPPMessageYapStorage.m

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -133,21 +133,14 @@ - (void)xmppStream:(XMPPStream *)stream didReceiveMessage:(XMPPMessage *)xmppMes
133133
}
134134

135135
// Extract XEP-0359 stanza-id
136-
NSString *stanzaId = nil;
137136
NSString *originId = xmppMessage.originId;
138-
NSDictionary<XMPPJID*,NSString*> *stanzaIds = xmppMessage.stanzaIds;
139-
if (stanzaIds.count) {
140-
OTRXMPPManager *xmpp = (OTRXMPPManager*)[OTRProtocolManager.shared protocolForAccount:account];
141-
XMPPCapabilities *capabilities = xmpp.xmppCapabilities;
142-
BOOL hasValidStanzaId = [capabilities hasValidStanzaId:xmppMessage];
143-
XMPPJID *myJID = account.bareJID;
144-
if (hasValidStanzaId && myJID) {
145-
stanzaId = stanzaIds[myJID];
146-
}
147-
}
137+
NSString *stanzaId = [xmppMessage extractStanzaIdWithAccount:account];
138+
message.originId = originId;
139+
message.stanzaId = stanzaId;
148140

149-
if ([self isDuplicateMessage:xmppMessage buddyUniqueId:messageBuddy.uniqueId transaction:transaction]) {
141+
if ([self isDuplicateMessage:xmppMessage stanzaId:stanzaId buddyUniqueId:messageBuddy.uniqueId transaction:transaction]) {
150142
DDLogWarn(@"Duplicate message received: %@", xmppMessage);
143+
return;
151144
}
152145

153146
if (message.text) {
@@ -186,18 +179,20 @@ - (void)handleDeliverResponse:(XMPPMessage *)xmppMessage transaction:(YapDatabas
186179
}
187180
}
188181

189-
/** It is a violation of the XMPP spec to discard messages with duplicate stanza ids */
190-
- (BOOL)isDuplicateMessage:(XMPPMessage *)message buddyUniqueId:(NSString *)buddyUniqueId transaction:(YapDatabaseReadWriteTransaction *)transaction
182+
/** It is a violation of the XMPP spec to discard messages with duplicate stanza elementIds. We must use XEP-0359 stanza-id only. */
183+
- (BOOL)isDuplicateMessage:(XMPPMessage *)message stanzaId:(NSString*)stanzaId buddyUniqueId:(NSString *)buddyUniqueId transaction:(YapDatabaseReadWriteTransaction *)transaction
191184
{
192185
__block BOOL result = NO;
193-
if ([message.elementID length]) {
194-
[transaction enumerateMessagesWithId:message.elementID block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
195-
if ([[databaseMessage threadId] isEqualToString:buddyUniqueId]) {
196-
*stop = YES;
197-
result = YES;
198-
}
199-
}];
186+
if (!stanzaId.length) {
187+
return NO;
200188
}
189+
190+
[transaction enumerateMessagesWithElementId:nil originId:nil stanzaId:stanzaId block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
191+
if ([[databaseMessage threadId] isEqualToString:buddyUniqueId]) {
192+
*stop = YES;
193+
result = YES;
194+
}
195+
}];
201196
return result;
202197
}
203198

@@ -216,31 +211,41 @@ - (void)handleCarbonMessage:(XMPPMessage *)forwardedMessage stream:(XMPPStream *
216211
}
217212

218213
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * __nonnull transaction) {
219-
220-
OTRXMPPBuddy *buddy = [OTRXMPPBuddy fetchBuddyForUsername:username accountName:stream.tag transaction:transaction];
214+
NSString *accountId = stream.tag;
215+
OTRXMPPAccount *account = [OTRXMPPAccount fetchObjectWithUniqueID:accountId transaction:transaction];
216+
OTRXMPPBuddy *buddy = [OTRXMPPBuddy fetchBuddyWithUsername:username withAccountUniqueId:accountId transaction:transaction];
221217

222218
if (!buddy) {
223219
return;
224220
}
221+
// Extract XEP-0359 stanza-id
222+
NSString *originId = forwardedMessage.originId;
223+
NSString *stanzaId = [forwardedMessage extractStanzaIdWithAccount:account];
224+
225225
if (incoming) {
226226
[self handleChatState:forwardedMessage username:username stream:stream transaction:transaction];
227227
[self handleDeliverResponse:forwardedMessage transaction:transaction];
228228
}
229-
if ([self isDuplicateMessage:forwardedMessage buddyUniqueId:buddy.uniqueId transaction:transaction]) {
229+
230+
if ([self isDuplicateMessage:forwardedMessage stanzaId:stanzaId buddyUniqueId:buddy.uniqueId transaction:transaction]) {
230231
DDLogWarn(@"Duplicate message received: %@", forwardedMessage);
232+
return;
231233
}
232234
if ([forwardedMessage isMessageWithBody] && ![forwardedMessage isErrorMessage] && ![OTRKit stringStartsWithOTRPrefix:forwardedMessage.body]) {
235+
OTRBaseMessage *message = nil;
233236
if (incoming) {
234-
OTRIncomingMessage *message = [self incomingMessageFromXMPPMessage:forwardedMessage buddyId:buddy.uniqueId];
237+
OTRIncomingMessage *incomingMessage = [self incomingMessageFromXMPPMessage:forwardedMessage buddyId:buddy.uniqueId];
235238
NSString *activeThreadYapKey = [[OTRAppDelegate appDelegate] activeThreadYapKey];
236239
if([activeThreadYapKey isEqualToString:message.threadId]) {
237-
message.read = YES;
240+
incomingMessage.read = YES;
238241
}
239-
[message saveWithTransaction:transaction];
242+
message = incomingMessage;
240243
} else {
241-
OTROutgoingMessage *message = [self outgoingMessageFromXMPPMessage:forwardedMessage buddyId:buddy.uniqueId];
242-
[message saveWithTransaction:transaction];
244+
message = [self outgoingMessageFromXMPPMessage:forwardedMessage buddyId:buddy.uniqueId];
243245
}
246+
message.originId = originId;
247+
message.stanzaId = stanzaId;
248+
[message saveWithTransaction:transaction];
244249
}
245250
}];
246251
}

ChatSecure/Classes/Controllers/XMPP/OTRXMPPRoomManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
250250
}];
251251

252252
}
253+
253254
// Handle group chat message receipts
254255
[OTRXMPPRoomMessage handleDeliveryReceiptResponseWithMessage:message writeConnection:self.databaseConnection];
255256
}

ChatSecure/Classes/Controllers/XMPP/OTRXMPPRoomYapStorage.m

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#import "OTRAccount.h"
1212
#import <ChatSecureCore/ChatSecureCore-Swift.h>
1313
#import "OTRLog.h"
14+
#import "OTRXMPPManager_Private.h"
1415
@import YapDatabase;
1516
@import XMPPFramework;
1617

@@ -68,10 +69,9 @@ - (OTRXMPPRoom *)fetchRoomWithXMPPRoomJID:(NSString *)roomJID accountId:(NSStrin
6869
return [OTRXMPPRoom fetchObjectWithUniqueID:[OTRXMPPRoom createUniqueId:accountId jid:roomJID] transaction:transaction];
6970
}
7071

71-
- (BOOL)existsMessage:(XMPPMessage *)message from:(XMPPJID *)fromJID account:(NSString *)acountKey transaction:(YapDatabaseReadTransaction *)transaction
72+
- (BOOL)existsMessage:(XMPPMessage *)message from:(XMPPJID *)fromJID stanzaId:(nullable NSString*)stanzaId transaction:(YapDatabaseReadTransaction *)transaction
7273
{
7374
NSDate *remoteTimestamp = [message delayedDeliveryDate];
74-
7575
if (!remoteTimestamp)
7676
{
7777
// When the xmpp server sends us a room message, it will always timestamp delayed messages.
@@ -80,40 +80,44 @@ - (BOOL)existsMessage:(XMPPMessage *)message from:(XMPPJID *)fromJID account:(NS
8080

8181
return NO;
8282
}
83-
84-
NSString *elementID = [message elementID];
85-
if ([elementID length]) {
86-
__block BOOL result = NO;
87-
[transaction enumerateMessagesWithId:elementID block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
88-
//Need to check room JID
89-
//So if message has same ID and same room jid that's got to be the same message, right?
90-
if ([databaseMessage isKindOfClass:[OTRXMPPRoomMessage class]]) {
91-
OTRXMPPRoomMessage *msg = (OTRXMPPRoomMessage *)databaseMessage;
92-
if ([msg.roomJID isEqualToString:fromJID.bare]) {
93-
*stop = YES;
94-
result = YES;
95-
}}
96-
}];
97-
return result;
98-
}
99-
100-
return NO;
83+
NSString *elementID = message.elementID;
84+
__block BOOL result = NO;
85+
[transaction enumerateMessagesWithElementId:elementID originId:nil stanzaId:stanzaId block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
86+
//Need to check room JID
87+
//So if message has same ID and same room jid that's got to be the same message, right?
88+
if ([databaseMessage isKindOfClass:[OTRXMPPRoomMessage class]]) {
89+
OTRXMPPRoomMessage *msg = (OTRXMPPRoomMessage *)databaseMessage;
90+
if ([msg.roomJID isEqualToString:fromJID.bare]) {
91+
*stop = YES;
92+
result = YES;
93+
}}
94+
}];
95+
return result;
10196
}
10297

10398
- (void)insertIncomingMessage:(XMPPMessage *)message intoRoom:(XMPPRoom *)room
10499
{
105100
NSString *accountId = room.xmppStream.tag;
106101
NSString *roomJID = room.roomJID.bare;
107102
XMPPJID *fromJID = [message from];
108-
103+
if (!accountId || !roomJID || !fromJID) {
104+
return;
105+
}
109106
__block OTRXMPPRoomMessage *databaseMessage = nil;
110107
__block OTRXMPPRoom *databaseRoom = nil;
111-
__block OTRAccount *account = nil;
108+
__block OTRXMPPAccount *account = nil;
112109
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
113-
account = [OTRAccount fetchObjectWithUniqueID:accountId transaction:transaction];
110+
account = [OTRXMPPAccount fetchObjectWithUniqueID:accountId transaction:transaction];
114111
// Sends a response receipt when receiving a delivery receipt request
115112
[OTRXMPPRoomMessage handleDeliveryReceiptRequestWithMessage:message xmppStream:room.xmppStream];
116-
if ([self existsMessage:message from:fromJID account:accountId transaction:transaction]) {
113+
114+
// Extract XEP-0359 stanza-id
115+
NSString *stanzaId = [message extractStanzaIdWithAccount:account];
116+
NSString *originId = message.originId;
117+
databaseMessage.originId = originId;
118+
databaseMessage.stanzaId = stanzaId;
119+
120+
if ([self existsMessage:message from:fromJID stanzaId:stanzaId transaction:transaction]) {
117121
// This message already exists and shouldn't be inserted
118122
DDLogVerbose(@"%@: %@ - Duplicate MUC message", THIS_FILE, THIS_METHOD);
119123
return;

ChatSecure/Classes/Controllers/YapDatabaseConstants.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88

99
import Foundation
1010

11+
public class SecondaryIndexName: NSObject {
12+
/// XEP-0359 origin-id
13+
public static let originId = "SecondaryIndexNameOriginId"
14+
/// XEP-0359 stanza-id
15+
public static let stanzaId = "SecondaryIndexNameStanzaId"
16+
}
17+
1118
@objc public enum DatabaseExtensionName: Int {
1219
case groupOccupantsViewName
1320
case buddyDeleteActionViewName

0 commit comments

Comments
 (0)