Merge branch 'dev' into refactor-4
This commit is contained in:
commit
14c87139c6
9
Podfile
9
Podfile
|
@ -2,7 +2,6 @@ platform :ios, '12.0'
|
|||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
|
||||
use_frameworks!
|
||||
inhibit_all_warnings!
|
||||
|
||||
target 'Session' do
|
||||
pod 'AFNetworking', inhibit_warnings: true
|
||||
|
@ -130,9 +129,9 @@ def enable_extension_support_for_pure_layout(installer)
|
|||
end
|
||||
|
||||
def set_minimum_deployment_target(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |build_configuration|
|
||||
build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
|
||||
end
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |build_configuration|
|
||||
build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -230,6 +230,6 @@ SPEC CHECKSUMS:
|
|||
YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665
|
||||
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
|
||||
|
||||
PODFILE CHECKSUM: 1aad5fbd49d168d2f50da0b3b3e515247f1ce291
|
||||
PODFILE CHECKSUM: 3489ed70ea51f2bf705bf99703efc71d697de373
|
||||
|
||||
COCOAPODS: 1.10.0.rc.1
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
extension AppDelegate : OpenGroupAPIDelegate {
|
||||
|
||||
public func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: OpenGroupInfo) {
|
||||
let storage = OWSPrimaryStorage.shared()
|
||||
let publicChatID = "\(server).\(channel)"
|
||||
Storage.writeSync { transaction in
|
||||
// Update user count
|
||||
storage.setUserCount(info.memberCount, forPublicChatWithID: publicChatID, in: transaction)
|
||||
let groupThread = TSGroupThread.getOrCreateThread(withGroupId: publicChatID.data(using: .utf8)!, groupType: .openGroup, transaction: transaction)
|
||||
// Update display name if needed
|
||||
let groupModel = groupThread.groupModel
|
||||
if groupModel.groupName != info.displayName {
|
||||
let newGroupModel = TSGroupModel(title: info.displayName, memberIds: groupModel.groupMemberIds, image: groupModel.groupImage, groupId: groupModel.groupId, groupType: groupModel.groupType, adminIds: groupModel.groupAdminIds)
|
||||
groupThread.groupModel = newGroupModel
|
||||
groupThread.save(with: transaction)
|
||||
}
|
||||
// Download and update profile picture if needed
|
||||
let oldProfilePictureURL = storage.getProfilePictureURL(forPublicChatWithID: publicChatID, in: transaction)
|
||||
if oldProfilePictureURL != info.profilePictureURL || groupModel.groupImage == nil {
|
||||
storage.setProfilePictureURL(info.profilePictureURL, forPublicChatWithID: publicChatID, in: transaction)
|
||||
if let profilePictureURL = info.profilePictureURL {
|
||||
var sanitizedServerURL = server
|
||||
var sanitizedProfilePictureURL = profilePictureURL
|
||||
while sanitizedServerURL.hasSuffix("/") { sanitizedServerURL.removeLast(1) }
|
||||
while sanitizedProfilePictureURL.hasPrefix("/") { sanitizedProfilePictureURL.removeFirst(1) }
|
||||
let url = "\(sanitizedServerURL)/\(sanitizedProfilePictureURL)"
|
||||
FileServerAPI.downloadAttachment(from: url).map2 { data in
|
||||
let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(data.count), sourceFilename: nil, caption: nil, albumMessageId: nil)
|
||||
try attachmentStream.write(data)
|
||||
groupThread.updateAvatar(with: attachmentStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,24 @@
|
|||
import SessionMessagingKit
|
||||
import SessionProtocolKit
|
||||
import SessionSnodeKit
|
||||
|
||||
@objc(SNConfiguration)
|
||||
final class Configuration : NSObject {
|
||||
|
||||
private static let pnServerURL = "https://live.apns.getsession.org"
|
||||
private static let pnServerPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049"
|
||||
|
||||
@objc static func performMainSetup() {
|
||||
SNMessagingKit.configure(
|
||||
storage: Storage.shared,
|
||||
signalStorage: OWSPrimaryStorage.shared(),
|
||||
identityKeyStore: OWSIdentityManager.shared(),
|
||||
sessionRestorationImplementation: SessionRestorationImplementation(),
|
||||
certificateValidator: SMKCertificateDefaultValidator(trustRoot: OWSUDManagerImpl.trustRoot()),
|
||||
openGroupAPIDelegate: UIApplication.shared.delegate as! AppDelegate,
|
||||
pnServerURL: pnServerURL,
|
||||
pnServerPublicKey: pnServerURL
|
||||
)
|
||||
SessionProtocolKit.configure(storage: Storage.shared, sharedSenderKeysDelegate: UIApplication.shared.delegate as! AppDelegate)
|
||||
SessionSnodeKit.configure(storage: Storage.shared)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
import Foundation
|
||||
import PromiseKit
|
||||
|
||||
extension Storage : SessionMessagingKitStorageProtocol {
|
||||
|
||||
// MARK: Signal Protocol
|
||||
public func getOrGenerateRegistrationID(using transaction: Any) -> UInt32 {
|
||||
SSKEnvironment.shared.tsAccountManager.getOrGenerateRegistrationId(transaction as! YapDatabaseReadWriteTransaction)
|
||||
}
|
||||
|
||||
public func getSenderCertificate(for publicKey: String) -> SMKSenderCertificate {
|
||||
let (promise, seal) = Promise<SMKSenderCertificate>.pending()
|
||||
SSKEnvironment.shared.udManager.ensureSenderCertificate { senderCertificate in
|
||||
seal.fulfill(senderCertificate)
|
||||
} failure: { error in
|
||||
// Should never fail
|
||||
}
|
||||
return try! promise.wait()
|
||||
}
|
||||
|
||||
// MARK: Shared Sender Keys
|
||||
private static let closedGroupPrivateKeyCollection = "LokiClosedGroupPrivateKeyCollection"
|
||||
|
||||
public func getClosedGroupPrivateKey(for publicKey: String) -> String? {
|
||||
var result: String?
|
||||
Storage.read { transaction in
|
||||
result = transaction.object(forKey: publicKey, inCollection: Storage.closedGroupPrivateKeyCollection) as? String
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
internal static func setClosedGroupPrivateKey(_ privateKey: String, for publicKey: String, using transaction: Any) {
|
||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(privateKey, forKey: publicKey, inCollection: Storage.closedGroupPrivateKeyCollection)
|
||||
}
|
||||
|
||||
internal static func removeClosedGroupPrivateKey(for publicKey: String, using transaction: Any) {
|
||||
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: publicKey, inCollection: Storage.closedGroupPrivateKeyCollection)
|
||||
}
|
||||
|
||||
func getUserClosedGroupPublicKeys() -> Set<String> {
|
||||
var result: Set<String> = []
|
||||
Storage.read { transaction in
|
||||
result = Set(transaction.allKeys(inCollection: Storage.closedGroupPrivateKeyCollection))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func isClosedGroup(_ publicKey: String) -> Bool {
|
||||
getUserClosedGroupPublicKeys().contains(publicKey)
|
||||
}
|
||||
|
||||
// MARK: Jobs
|
||||
public func persist(_ job: Job, using transaction: Any) { fatalError("Not implemented.") }
|
||||
public func markJobAsSucceeded(_ job: Job, using transaction: Any) { fatalError("Not implemented.") }
|
||||
public func markJobAsFailed(_ job: Job, using transaction: Any) { fatalError("Not implemented.") }
|
||||
|
||||
// MARK: Authorization
|
||||
private static func getAuthTokenCollection(for server: String) -> String {
|
||||
return (server == FileServerAPI.server) ? "LokiStorageAuthTokenCollection" : "LokiGroupChatAuthTokenCollection"
|
||||
}
|
||||
|
||||
public func getAuthToken(for server: String) -> String? {
|
||||
let collection = Storage.getAuthTokenCollection(for: server)
|
||||
var result: String? = nil
|
||||
Storage.read { transaction in
|
||||
result = transaction.object(forKey: server, inCollection: collection) as? String
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func setAuthToken(for server: String, to newValue: String, using transaction: Any) {
|
||||
let collection = Storage.getAuthTokenCollection(for: server)
|
||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: server, inCollection: collection)
|
||||
}
|
||||
|
||||
public func removeAuthToken(for server: String, using transaction: Any) {
|
||||
let collection = Storage.getAuthTokenCollection(for: server)
|
||||
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: server, inCollection: collection)
|
||||
}
|
||||
|
||||
// MARK: Open Group Public Keys
|
||||
private static let openGroupPublicKeyCollection = "LokiOpenGroupPublicKeyCollection"
|
||||
|
||||
public func getOpenGroupPublicKey(for server: String) -> String? {
|
||||
var result: String? = nil
|
||||
Storage.read { transaction in
|
||||
result = transaction.object(forKey: server, inCollection: Storage.openGroupPublicKeyCollection) as? String
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func setOpenGroupPublicKey(for server: String, to newValue: String, using transaction: Any) {
|
||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: server, inCollection: Storage.openGroupPublicKeyCollection)
|
||||
}
|
||||
|
||||
// MARK: Last Message Server ID
|
||||
private static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection"
|
||||
|
||||
public func getLastMessageServerID(for group: UInt64, on server: String) -> UInt64? {
|
||||
var result: UInt64? = nil
|
||||
Storage.read { transaction in
|
||||
result = transaction.object(forKey: "\(server).\(group)", inCollection: Storage.lastMessageServerIDCollection) as? UInt64
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) {
|
||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: "\(server).\(group)", inCollection: Storage.lastMessageServerIDCollection)
|
||||
}
|
||||
|
||||
public func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any) {
|
||||
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.lastMessageServerIDCollection)
|
||||
}
|
||||
|
||||
// MARK: Last Deletion Server ID
|
||||
private static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection"
|
||||
|
||||
public func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt64? {
|
||||
var result: UInt64? = nil
|
||||
Storage.read { transaction in
|
||||
result = transaction.object(forKey: "\(server).\(group)", inCollection: Storage.lastDeletionServerIDCollection) as? UInt64
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) {
|
||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: "\(server).\(group)", inCollection: Storage.lastDeletionServerIDCollection)
|
||||
}
|
||||
|
||||
public func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any) {
|
||||
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.lastDeletionServerIDCollection)
|
||||
}
|
||||
|
||||
// MARK: Open Group Metadata
|
||||
private static let openGroupUserCountCollection = "LokiPublicChatUserCountCollection"
|
||||
private static let openGroupMessageIDCollection = "LKMessageIDCollection"
|
||||
|
||||
public func setUserCount(to newValue: Int, forOpenGroupWithID openGroupID: String, using transaction: Any) {
|
||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: openGroupID, inCollection: Storage.openGroupUserCountCollection)
|
||||
}
|
||||
|
||||
public func getIDForMessage(withServerID serverID: UInt64) -> UInt64? {
|
||||
var result: UInt64? = nil
|
||||
Storage.read { transaction in
|
||||
result = transaction.object(forKey: String(serverID), inCollection: Storage.openGroupMessageIDCollection) as? UInt64
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func setOpenGroupDisplayName(to displayName: String, for publicKey: String, on channel: UInt64, server: String, using transaction: Any) {
|
||||
let collection = "\(server).\(channel)" // FIXME: This should be a proper collection
|
||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(displayName, forKey: publicKey, inCollection: collection)
|
||||
}
|
||||
|
||||
public func setLastProfilePictureUploadDate(_ date: Date) {
|
||||
UserDefaults.standard[.lastProfilePictureUpload] = date
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
extension Storage : SessionSnodeKitStorageProtocol {
|
||||
|
||||
// MARK: Onion Request Paths
|
||||
internal static let onionRequestPathCollection = "LokiOnionRequestPathCollection"
|
||||
private static let onionRequestPathCollection = "LokiOnionRequestPathCollection"
|
||||
|
||||
public func getOnionRequestPaths() -> [OnionRequestAPI.Path] {
|
||||
let collection = Storage.onionRequestPathCollection
|
||||
|
|
|
@ -7,7 +7,17 @@ extension Storage {
|
|||
Storage.writeSync { work($0) }
|
||||
}
|
||||
|
||||
public func withAsync(_ work: @escaping (Any) -> Void, completion: @escaping () -> Void) {
|
||||
Storage.write(with: { work($0) }, completion: completion)
|
||||
}
|
||||
|
||||
public func getUserPublicKey() -> String? {
|
||||
return OWSIdentityManager.shared().identityKeyPair()?.publicKey.toHexString()
|
||||
}
|
||||
|
||||
public func getUserKeyPair() -> ECKeyPair? {
|
||||
return OWSIdentityManager.shared().identityKeyPair()
|
||||
}
|
||||
|
||||
public func getUserDisplayName() -> String? { fatalError() }
|
||||
}
|
||||
|
|
|
@ -1235,6 +1235,17 @@ static const int kYapDatabaseRangeMaxLength = 25000;
|
|||
OWSAssertDebug(!viewItemCache[interaction.uniqueId]);
|
||||
viewItemCache[interaction.uniqueId] = viewItem;
|
||||
[viewItems addObject:viewItem];
|
||||
TSMessage *message = (TSMessage *)viewItem.interaction;
|
||||
if (message.hasAttachmentsInNSE) {
|
||||
[SSKEnvironment.shared.attachmentDownloads downloadAttachmentsForMessage:message
|
||||
transaction:transaction
|
||||
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
|
||||
OWSLogInfo(@"Successfully redownloaded attachment in thread: %@", message.thread);
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
OWSLogWarn(@"Failed to redownload message with error: %@", error);
|
||||
}];
|
||||
}
|
||||
|
||||
return viewItem;
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ public struct Configuration {
|
|||
internal static var shared: Configuration!
|
||||
}
|
||||
|
||||
public enum SessionMessagingKitX { // Just to make the external API nice
|
||||
public enum SNMessagingKit { // Just to make the external API nice
|
||||
|
||||
public static func configure(
|
||||
storage: SessionMessagingKitStorageProtocol,
|
||||
|
|
|
@ -19,7 +19,7 @@ public final class FileServerAPI : DotNetAPI {
|
|||
/// be other parameters in the request. On average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when
|
||||
/// uploading a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request but that's only
|
||||
/// possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds.
|
||||
public static let fileSizeORMultiplier: Double = 3
|
||||
public static let fileSizeORMultiplier: Double = 2
|
||||
|
||||
@objc public static let server = "https://file.getsession.org"
|
||||
@objc public static let fileStorageBucketURL = "https://file-static.lokinet.org"
|
||||
|
|
|
@ -35,7 +35,7 @@ public final class NotifyPNServerJob : NSObject, Job, NSCoding { // NSObject/NSC
|
|||
let request = TSRequest(url: url, method: "POST", parameters: parameters)
|
||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ]
|
||||
attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.global()) {
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, using: Configuration.shared.pnServerPublicKey).map { _ in }
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, target: "/loki/v2/lsrpc", using: Configuration.shared.pnServerPublicKey).map { _ in }
|
||||
}.done(on: DispatchQueue.global()) { // Intentionally capture self
|
||||
self.handleSuccess()
|
||||
}.catch(on: DispatchQueue.global()) { error in
|
||||
|
|
|
@ -17,7 +17,7 @@ public final class OpenGroupAPI : DotNetAPI {
|
|||
|
||||
public static let profilePictureType = "network.loki.messenger.avatar"
|
||||
|
||||
@objc public static let openGroupMessageType = "network.loki.messenger.openGroup"
|
||||
@objc public static let openGroupMessageType = "network.loki.messenger.publicChat"
|
||||
|
||||
// MARK: Open Group Public Key Validation
|
||||
public static func getOpenGroupServerPublicKey(for server: String) -> Promise<String> {
|
||||
|
@ -122,7 +122,7 @@ public final class OpenGroupAPI : DotNetAPI {
|
|||
SNLog("Ignoring open group message with invalid signature.")
|
||||
return nil
|
||||
}
|
||||
let existingMessageID = storage.getIDForMessage(withServerID: UInt(result.serverID!))
|
||||
let existingMessageID = storage.getIDForMessage(withServerID: result.serverID!)
|
||||
guard existingMessageID == nil else {
|
||||
SNLog("Ignoring duplicate open group message.")
|
||||
return nil
|
||||
|
|
|
@ -2,8 +2,8 @@ import SessionProtocolKit
|
|||
|
||||
public protocol SessionMessagingKitStorageProtocol {
|
||||
|
||||
func with(_ work: (Any) -> Void)
|
||||
func withAsync(_ work: (Any) -> Void, completion: () -> Void)
|
||||
func with(_ work: @escaping (Any) -> Void)
|
||||
func withAsync(_ work: @escaping (Any) -> Void, completion: @escaping () -> Void)
|
||||
|
||||
func getUserPublicKey() -> String?
|
||||
func getUserKeyPair() -> ECKeyPair?
|
||||
|
@ -20,14 +20,14 @@ public protocol SessionMessagingKitStorageProtocol {
|
|||
func removeAuthToken(for server: String, using transaction: Any)
|
||||
func getOpenGroupPublicKey(for server: String) -> String?
|
||||
func setOpenGroupPublicKey(for server: String, to newValue: String, using transaction: Any)
|
||||
func getLastMessageServerID(for group: UInt64, on server: String) -> UInt?
|
||||
func getLastMessageServerID(for group: UInt64, on server: String) -> UInt64?
|
||||
func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any)
|
||||
func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any)
|
||||
func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt64?
|
||||
func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any)
|
||||
func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any)
|
||||
func setUserCount(to newValue: Int, forOpenGroupWithID: String, using transaction: Any)
|
||||
func getIDForMessage(withServerID serverID: UInt) -> UInt?
|
||||
func setUserCount(to newValue: Int, forOpenGroupWithID openGroupID: String, using transaction: Any)
|
||||
func getIDForMessage(withServerID serverID: UInt64) -> UInt64?
|
||||
func setOpenGroupDisplayName(to displayName: String, for publicKey: String, on channel: UInt64, server: String, using transaction: Any)
|
||||
func setLastProfilePictureUploadDate(_ date: Date) // Stored in user defaults so no transaction is needed
|
||||
}
|
||||
|
|
|
@ -122,11 +122,11 @@ public class DotNetAPI : NSObject {
|
|||
return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .userInitiated)) {
|
||||
serverPublicKeyPromise.then(on: DispatchQueue.global(qos: .userInitiated)) { serverPublicKey in
|
||||
return OnionRequestAPI.sendOnionRequest(request, to: host, using: serverPublicKey, isJSONRequired: false).map(on: DispatchQueue.global(qos: .userInitiated)) { json in
|
||||
guard let body = json["body"] as? JSON, let data = body["data"] as? [UInt8] else {
|
||||
guard let body = json["result"] as? String, let data = Data(base64Encoded: body) else {
|
||||
SNLog("Couldn't parse attachment from: \(json).")
|
||||
throw Error.parsingFailed
|
||||
}
|
||||
return Data(data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ internal extension OnionRequestAPI {
|
|||
let plaintext = try encode(ciphertext: payloadAsData, json: [ "headers" : "" ])
|
||||
let result = try AESGCM.encrypt(plaintext, for: snodeX25519PublicKey)
|
||||
seal.fulfill(result)
|
||||
case .server(_, let serverX25519PublicKey):
|
||||
case .server(_, _, let serverX25519PublicKey):
|
||||
let plaintext = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ])
|
||||
let result = try AESGCM.encrypt(plaintext, for: serverX25519PublicKey)
|
||||
seal.fulfill(result)
|
||||
|
@ -48,8 +48,8 @@ internal extension OnionRequestAPI {
|
|||
case .snode(let snode):
|
||||
let snodeED25519PublicKey = snode.publicKeySet.ed25519Key
|
||||
parameters = [ "destination" : snodeED25519PublicKey ]
|
||||
case .server(let host, _):
|
||||
parameters = [ "host" : host, "target" : "/loki/v2/lsrpc", "method" : "POST" ]
|
||||
case .server(let host, let target, _):
|
||||
parameters = [ "host" : host, "target" : target, "method" : "POST" ]
|
||||
}
|
||||
parameters["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString()
|
||||
let x25519PublicKey: String
|
||||
|
@ -57,7 +57,7 @@ internal extension OnionRequestAPI {
|
|||
case .snode(let snode):
|
||||
let snodeX25519PublicKey = snode.publicKeySet.x25519Key
|
||||
x25519PublicKey = snodeX25519PublicKey
|
||||
case .server(_, let serverX25519PublicKey):
|
||||
case .server(_, _, let serverX25519PublicKey):
|
||||
x25519PublicKey = serverX25519PublicKey
|
||||
}
|
||||
do {
|
||||
|
|
|
@ -26,7 +26,7 @@ public enum OnionRequestAPI {
|
|||
// MARK: Destination
|
||||
public enum Destination {
|
||||
case snode(Snode)
|
||||
case server(host: String, x25519PublicKey: String)
|
||||
case server(host: String, target: String, x25519PublicKey: String)
|
||||
}
|
||||
|
||||
// MARK: Error
|
||||
|
@ -284,7 +284,7 @@ public enum OnionRequestAPI {
|
|||
}
|
||||
|
||||
/// Sends an onion request to `server`. Builds new paths as needed.
|
||||
public static func sendOnionRequest(_ request: NSURLRequest, to server: String, using x25519PublicKey: String, isJSONRequired: Bool = true) -> Promise<JSON> {
|
||||
public static func sendOnionRequest(_ request: NSURLRequest, to server: String, target: String = "/loki/v3/lsrpc", using x25519PublicKey: String, isJSONRequired: Bool = true) -> Promise<JSON> {
|
||||
var rawHeaders = request.allHTTPHeaderFields ?? [:]
|
||||
rawHeaders.removeValue(forKey: "User-Agent")
|
||||
var headers: JSON = rawHeaders.mapValues { value in
|
||||
|
@ -327,7 +327,7 @@ public enum OnionRequestAPI {
|
|||
"method" : request.httpMethod!,
|
||||
"headers" : headers
|
||||
]
|
||||
let destination = Destination.server(host: host, x25519PublicKey: x25519PublicKey)
|
||||
let destination = Destination.server(host: host, target: target, x25519PublicKey: x25519PublicKey)
|
||||
let promise = sendOnionRequest(with: payload, to: destination, isJSONRequired: isJSONRequired)
|
||||
promise.catch2 { error in
|
||||
SNLog("Couldn't reach server: \(url) due to error: \(error).")
|
||||
|
|
|
@ -787,6 +787,8 @@
|
|||
C3548F0624456447009433A8 /* PNModeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0524456447009433A8 /* PNModeVC.swift */; };
|
||||
C3548F0824456AB6009433A8 /* UIView+Wrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */; };
|
||||
C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C354E75923FE2A7600CE22E3 /* BaseVC.swift */; };
|
||||
C3550A03255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3550A02255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift */; };
|
||||
C3550A1D255DD73500194B6A /* Storage+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3550A1C255DD73500194B6A /* Storage+SessionMessagingKit.swift */; };
|
||||
C35E8AA82485C85800ACB629 /* GeoLite2-Country-Locations-English.csv in Resources */ = {isa = PBXBuildFile; fileRef = C35E8AA52485C85400ACB629 /* GeoLite2-Country-Locations-English.csv */; };
|
||||
C35E8AA92485C85800ACB629 /* GeoLite2-Country-Blocks-IPv4.csv in Resources */ = {isa = PBXBuildFile; fileRef = C35E8AA62485C85600ACB629 /* GeoLite2-Country-Blocks-IPv4.csv */; };
|
||||
C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35E8AAD2485E51D00ACB629 /* IP2Country.swift */; };
|
||||
|
@ -2138,6 +2140,8 @@
|
|||
C3548F0524456447009433A8 /* PNModeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeVC.swift; sourceTree = "<group>"; };
|
||||
C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Wrapping.swift"; sourceTree = "<group>"; };
|
||||
C354E75923FE2A7600CE22E3 /* BaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseVC.swift; sourceTree = "<group>"; };
|
||||
C3550A02255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+OpenGroupAPI.swift"; sourceTree = "<group>"; };
|
||||
C3550A1C255DD73500194B6A /* Storage+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+SessionMessagingKit.swift"; sourceTree = "<group>"; };
|
||||
C35E8AA22485C72300ACB629 /* SwiftCSV.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftCSV.framework; path = ThirdParty/Carthage/Build/iOS/SwiftCSV.framework; sourceTree = "<group>"; };
|
||||
C35E8AA52485C85400ACB629 /* GeoLite2-Country-Locations-English.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "GeoLite2-Country-Locations-English.csv"; sourceTree = "<group>"; };
|
||||
C35E8AA62485C85600ACB629 /* GeoLite2-Country-Blocks-IPv4.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "GeoLite2-Country-Blocks-IPv4.csv"; sourceTree = "<group>"; };
|
||||
|
@ -3247,6 +3251,7 @@
|
|||
C31F812425258F9C00DD9FD9 /* Database */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C3550A1C255DD73500194B6A /* Storage+SessionMessagingKit.swift */,
|
||||
C3F0A619255C9902007BE2A3 /* Storage+SessionProtocolKit.swift */,
|
||||
C3F0A607255C98A6007BE2A3 /* Storage+SessionSnodeKit.swift */,
|
||||
C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */,
|
||||
|
@ -4446,6 +4451,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
C3F0A58F255C8E3D007BE2A3 /* Meta */,
|
||||
C3550A02255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift */,
|
||||
C3F0A62B255C9937007BE2A3 /* AppDelegate+SharedSenderKeys.swift */,
|
||||
C3F0A5EB255C970D007BE2A3 /* Configuration.swift */,
|
||||
B8CCF63B239757C10091D419 /* Components */,
|
||||
|
@ -6115,6 +6121,7 @@
|
|||
34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */,
|
||||
B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */,
|
||||
B80C6B5B2384C7F900FDBC8B /* DeviceNameModalDelegate.swift in Sources */,
|
||||
C3550A03255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift in Sources */,
|
||||
340FC8B8204DAC8D007AEB0F /* AddToGroupViewController.m in Sources */,
|
||||
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */,
|
||||
B879D449247E1BE300DB3608 /* PathVC.swift in Sources */,
|
||||
|
@ -6224,6 +6231,7 @@
|
|||
340FC8AB204DAC8D007AEB0F /* DomainFrontingCountryViewController.m in Sources */,
|
||||
4C586926224FAB83003FD070 /* AVAudioSession+OWS.m in Sources */,
|
||||
3496744D2076768700080B5F /* OWSMessageBubbleView.m in Sources */,
|
||||
C3550A1D255DD73500194B6A /* Storage+SessionMessagingKit.swift in Sources */,
|
||||
C331FFF42558FF0300070591 /* PNOptionView.swift in Sources */,
|
||||
34B3F8751E8DF1700035BE1A /* CallViewController.swift in Sources */,
|
||||
4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */,
|
||||
|
@ -6436,6 +6444,13 @@
|
|||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "SessionShareExtension/Meta/SignalShareExtension-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"COCOAPODS=1",
|
||||
"$(inherited)",
|
||||
"SQLITE_HAS_CODEC=1",
|
||||
"PURELAYOUT_APP_EXTENSIONS=1",
|
||||
);
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
|
||||
|
@ -6499,6 +6514,13 @@
|
|||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "SessionShareExtension/Meta/SignalShareExtension-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"COCOAPODS=1",
|
||||
"$(inherited)",
|
||||
"SQLITE_HAS_CODEC=1",
|
||||
"PURELAYOUT_APP_EXTENSIONS=1",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
|
@ -6550,6 +6572,13 @@
|
|||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"COCOAPODS=1",
|
||||
"$(inherited)",
|
||||
"SQLITE_HAS_CODEC=1",
|
||||
"PURELAYOUT_APP_EXTENSIONS=1",
|
||||
);
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = SessionPushNotificationExtension/Meta/Info.plist;
|
||||
|
@ -6614,6 +6643,13 @@
|
|||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"COCOAPODS=1",
|
||||
"$(inherited)",
|
||||
"SQLITE_HAS_CODEC=1",
|
||||
"PURELAYOUT_APP_EXTENSIONS=1",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
|
@ -6795,6 +6831,13 @@
|
|||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREFIX_HEADER = "SignalUtilitiesKit/Meta/SignalUtilitiesKit-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"COCOAPODS=1",
|
||||
"$(inherited)",
|
||||
"SQLITE_HAS_CODEC=1",
|
||||
"PURELAYOUT_APP_EXTENSIONS=1",
|
||||
);
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
INFOPLIST_FILE = SignalUtilitiesKit/Meta/Info.plist;
|
||||
|
@ -6865,6 +6908,13 @@
|
|||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_PREFIX_HEADER = "SignalUtilitiesKit/Meta/SignalUtilitiesKit-Prefix.pch";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"COCOAPODS=1",
|
||||
"$(inherited)",
|
||||
"SQLITE_HAS_CODEC=1",
|
||||
"PURELAYOUT_APP_EXTENSIONS=1",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
|
@ -7584,7 +7634,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 139;
|
||||
CURRENT_PROJECT_VERSION = 143;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -7597,6 +7647,7 @@
|
|||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
HAVE_CONFIG_H,
|
||||
"PURELAYOUT_APP_EXTENSIONS=1",
|
||||
);
|
||||
GCC_STRICT_ALIASING = NO;
|
||||
GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO;
|
||||
|
@ -7619,7 +7670,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 1.6.4;
|
||||
MARKETING_VERSION = 1.6.5;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
|
@ -7651,7 +7702,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 139;
|
||||
CURRENT_PROJECT_VERSION = 143;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -7664,6 +7715,7 @@
|
|||
"$(inherited)",
|
||||
HAVE_CONFIG_H,
|
||||
"RELEASE=1",
|
||||
"PURELAYOUT_APP_EXTENSIONS=1",
|
||||
);
|
||||
GCC_STRICT_ALIASING = NO;
|
||||
GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO;
|
||||
|
@ -7686,7 +7738,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 1.6.4;
|
||||
MARKETING_VERSION = 1.6.5;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
PRODUCT_NAME = Session;
|
||||
|
|
|
@ -30,7 +30,7 @@ public final class LokiPushNotificationManager : NSObject {
|
|||
let request = TSRequest(url: url, method: "POST", parameters: parameters)
|
||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ]
|
||||
let promise: Promise<Void> = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) {
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, using: pnServerPublicKey).map2 { response in
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, target: "/loki/v2/lsrpc", using: pnServerPublicKey).map2 { response in
|
||||
guard let json = response["body"] as? JSON else {
|
||||
return print("[Loki] Couldn't unregister from push notifications.")
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ public final class LokiPushNotificationManager : NSObject {
|
|||
let request = TSRequest(url: url, method: "POST", parameters: parameters)
|
||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ]
|
||||
let promise: Promise<Void> = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) {
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, using: pnServerPublicKey).map2 { response in
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, target: "/loki/v2/lsrpc", using: pnServerPublicKey).map2 { response in
|
||||
guard let json = response["body"] as? JSON else {
|
||||
return print("[Loki] Couldn't register device token.")
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ public final class LokiPushNotificationManager : NSObject {
|
|||
let request = TSRequest(url: url, method: "POST", parameters: parameters)
|
||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ]
|
||||
let promise: Promise<Void> = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) {
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, using: pnServerPublicKey).map2 { response in
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, target: "/loki/v2/lsrpc", using: pnServerPublicKey).map2 { response in
|
||||
guard let json = response["body"] as? JSON else {
|
||||
return print("[Loki] Couldn't subscribe/unsubscribe closed group: \(closedGroupPublicKey).")
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ public final class LokiPushNotificationManager : NSObject {
|
|||
let request = TSRequest(url: url, method: "POST", parameters: parameters)
|
||||
request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ]
|
||||
let promise: Promise<Void> = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) {
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, using: pnServerPublicKey).map2 { response in
|
||||
OnionRequestAPI.sendOnionRequest(request, to: server, target: "/loki/v2/lsrpc", using: pnServerPublicKey).map2 { response in
|
||||
guard let json = response["body"] as? JSON else {
|
||||
return print("[Loki] Couldn't notify PN server.")
|
||||
}
|
||||
|
|
|
@ -290,6 +290,13 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
|
|||
[job.attachmentPointer saveWithTransaction:transaction];
|
||||
|
||||
if (job.message) {
|
||||
if (!CurrentAppContext().isMainApp) {
|
||||
job.message.hasAttachmentsInNSE = true;
|
||||
} else {
|
||||
job.message.hasAttachmentsInNSE = false;
|
||||
}
|
||||
|
||||
[job.message saveWithTransaction:transaction];
|
||||
[job.message touchWithTransaction:transaction];
|
||||
}
|
||||
}];
|
||||
|
|
|
@ -25,6 +25,11 @@ static NSString *const OWSFailedAttachmentDownloadsJobAttachmentStateIndex = @"i
|
|||
|
||||
@implementation OWSFailedAttachmentDownloadsJob
|
||||
|
||||
- (OWSAttachmentDownloads *)attachmentDownloads
|
||||
{
|
||||
return SSKEnvironment.shared.attachmentDownloads;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
|
||||
{
|
||||
self = [super init];
|
||||
|
|
|
@ -26,7 +26,7 @@ extension OnionRequestAPI {
|
|||
let plaintext = try encode(ciphertext: payloadAsData, json: [ "headers" : "" ])
|
||||
let result = try EncryptionUtilities.encrypt(plaintext, using: snodeX25519PublicKey)
|
||||
seal.fulfill(result)
|
||||
case .server(_, let serverX25519PublicKey):
|
||||
case .server(_, _, let serverX25519PublicKey):
|
||||
let plaintext = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ])
|
||||
let result = try EncryptionUtilities.encrypt(plaintext, using: serverX25519PublicKey)
|
||||
seal.fulfill(result)
|
||||
|
@ -47,8 +47,8 @@ extension OnionRequestAPI {
|
|||
case .snode(let snode):
|
||||
let snodeED25519PublicKey = snode.publicKeySet.ed25519Key
|
||||
parameters = [ "destination" : snodeED25519PublicKey ]
|
||||
case .server(let host, _):
|
||||
parameters = [ "host" : host, "target" : "/loki/v2/lsrpc", "method" : "POST" ]
|
||||
case .server(let host, let target, _):
|
||||
parameters = [ "host" : host, "target" : target, "method" : "POST" ]
|
||||
}
|
||||
parameters["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString()
|
||||
let x25519PublicKey: String
|
||||
|
@ -56,7 +56,7 @@ extension OnionRequestAPI {
|
|||
case .snode(let snode):
|
||||
let snodeX25519PublicKey = snode.publicKeySet.x25519Key
|
||||
x25519PublicKey = snodeX25519PublicKey
|
||||
case .server(_, let serverX25519PublicKey):
|
||||
case .server(_, _, let serverX25519PublicKey):
|
||||
x25519PublicKey = serverX25519PublicKey
|
||||
}
|
||||
do {
|
||||
|
|
|
@ -34,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// Open groups
|
||||
@property (nonatomic) uint64_t openGroupServerMessageID;
|
||||
@property (nonatomic, readonly) BOOL isOpenGroupMessage;
|
||||
// Push notifications
|
||||
@property (nonatomic) BOOL hasAttachmentsInNSE;
|
||||
|
||||
- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE;
|
||||
|
||||
|
|
Loading…
Reference in New Issue