@ -29,7 +29,14 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - B a t c h i n g & P o l l i n g
public static func poll ( _ server : String ) -> Promise < Void > {
// / T h i s i s a c o n v e n i e n c e m e t h o d w h i c h c a l l s ` / b a t c h ` w i t h a p r e - d e f i n e d s e t o f r e q u e s t s u s e d t o u p d a t e a n O p e n G r o u p
public static func poll (
_ server : String ,
through api : OnionRequestAPIType . Type = OnionRequestAPI . self ,
using storage : SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration . shared . storage ,
nonceGenerator : NonceGenerator16ByteType = NonceGenerator16Byte ( ) ,
date : Date = Date ( )
) -> Promise < [ Endpoint : ( OnionRequestResponseInfoType , Codable ) ] > {
// TODO: R e m o v e c o m m e n t s
// C a p a b i l i t i e s
// F e t c h e a c h r o o m
@ -37,7 +44,7 @@ public final class OpenGroupAPIV2: NSObject {
// / r o o m / < t o k e n > / p o l l I n f o / < i d > i n s t e a d ?
// F e t c h m e s s a g e s f o r e a c h r o o m
// / r o o m / { r o o m T o k e n } / m e s s a g e s / s i n c e / { m e s s a g e S e q u e n c e } :
// F e t c h d e l e t i o n s f o r e a c h r o o m ( i n c l u d e d i n m e s s a g e s )
// F e t c h d e l e t i o n s f o r e a c h r o o m ( i n c l u d e d i n m e s s a g e s )
// o l d c o m p a c t _ p o l l d a t a
// p u b l i c l e t r o o m : S t r i n g
@ -46,7 +53,6 @@ public final class OpenGroupAPIV2: NSObject {
// p u b l i c l e t d e l e t i o n s : [ D e l e t i o n ] ?
// p u b l i c l e t m o d e r a t o r s : [ S t r i n g ] ?
let storage : SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration . shared . storage
let requestResponseType : [ BatchRequestInfo ] = [
BatchRequestInfo (
request : Request (
@ -59,7 +65,7 @@ public final class OpenGroupAPIV2: NSObject {
]
. appending (
storage . getAllV2OpenGroups ( ) . values
. filter { $0 . server = = server }
. filter { $0 . server = = server . lowercased ( ) } // N o t e : T h e ` O p e n G r o u p V 2 ` c o n v e r t s t h e s e r v e r v a l u e t o l o w e r c a s e d u r i n g i n i t
. flatMap { openGroup -> [ BatchRequestInfo ] in
let lastSeqNo : Int64 ? = storage . getLastMessageServerID ( for : openGroup . room , on : server )
let targetSeqNo : Int64 = ( lastSeqNo ? ? 0 )
@ -88,11 +94,22 @@ public final class OpenGroupAPIV2: NSObject {
)
// TODO: H a n d l e r e s p o n s e ( m a y b e i n t h e p o l l e r o r t h e O p e n G r o u p M a n a g e r V 2 ? )
return batch ( server , requests : requestResponseType )
. map { _ in ( ) }
return batch ( server , requests : requestResponseType , through : api , using : storage , nonceGenerator : nonceGenerator , date : date )
}
private static func batch ( _ server : String , requests : [ BatchRequestInfo ] ) -> Promise < String > {
// / T h i s i s u s e d , f o r e x a m p l e , t o p o l l m u l t i p l e r o o m s o n t h e s a m e s e r v e r f o r u p d a t e s i n a s i n g l e q u e r y r a t h e r t h a n n e e d i n g t o m a k e m u l t i p l e r e q u e s t s f o r e a c h r o o m .
// /
// / N o g u a r a n t e e i s m a d e a s t o t h e o r d e r i n w h i c h s u b - r e q u e s t s a r e p r o c e s s e d ; u s e t h e ` / s e q u e n c e ` i n s t e a d i f y o u n e e d t h a t .
// /
// / F o r c o n t a i n e d s u b r e q u e s t s t h a t s p e c i f y a b o d y ( i . e . P O S T o r P U T r e q u e s t s ) e x a c t l y o n e o f ` j s o n ` , ` b 6 4 ` , o r ` b y t e s ` m u s t b e p r o v i d e d w i t h t h e r e q u e s t b o d y .
private static func batch (
_ server : String ,
requests : [ BatchRequestInfo ] ,
through api : OnionRequestAPIType . Type = OnionRequestAPI . self ,
using storage : SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration . shared . storage ,
nonceGenerator : NonceGenerator16ByteType = NonceGenerator16Byte ( ) ,
date : Date = Date ( )
) -> Promise < [ Endpoint : ( OnionRequestResponseInfoType , Codable ) ] > {
let requestBody : BatchRequest = requests . map { BatchSubRequest ( request : $0 . request ) }
let responseTypes = requests . map { $0 . responseType }
@ -107,14 +124,19 @@ public final class OpenGroupAPIV2: NSObject {
body : body
)
return send ( request )
return send ( request , through : api , using : storage , nonceGenerator : nonceGenerator , date : date )
. decoded ( as : responseTypes , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
. map { result in
return " "
result . enumerated ( )
. reduce ( into : [ : ] ) { prev , next in
prev [ requests [ next . offset ] . request . endpoint ] = next . element
}
}
}
public static func compactPoll ( _ server : String ) -> Promise < LegacyCompactPollResponse > {
// TODO: ` / s e q u e n c e ` r e q u e s t
public static func compactPoll ( _ server : String , api : OnionRequestAPIType . Type = OnionRequestAPI . self ) -> Promise < LegacyCompactPollResponse > {
let storage : SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration . shared . storage
let rooms : [ String ] = storage . getAllV2OpenGroups ( ) . values
. filter { $0 . server = = server }
@ -155,7 +177,7 @@ public final class OpenGroupAPIV2: NSObject {
body : body
)
return send ( request )
return send ( request , through : api )
. then ( on : OpenGroupAPIV2 . workQueue ) { _ , maybeData -> Promise < LegacyCompactPollResponse > in
guard let data : Data = maybeData else { throw Error . parsingFailed }
@ -173,101 +195,39 @@ public final class OpenGroupAPIV2: NSObject {
}
}
// MARK: - A u t h e n t i c a t i o n
// TODO: T u r n ' S o d i u m ' a n d ' N o n c e G e n e r a t o r 1 6 B y t e ' i n t o p r o t o c o l s f o r u n i t t e s t i n g .
static func sign (
_ request : URLRequest ,
with publicKey : String ,
sodium : Sodium = Sodium ( ) ,
nonceGenerator : NonceGenerator16Byte = NonceGenerator16Byte ( )
) -> URLRequest ? {
guard let url : URL = request . url else { return nil }
var updatedRequest : URLRequest = request
let path : String = url . path
. appending ( url . query . map { value in " ? \( value ) " } )
let method : String = ( request . httpMethod ? ? " GET " )
let timestamp : Int = Int ( floor ( Date ( ) . timeIntervalSince1970 ) )
let nonce : Data = Data ( nonceGenerator . nonce ( ) )
guard let publicKeyData : Data = publicKey . dataFromHex ( ) else { return nil }
guard let userKeyPair : ECKeyPair = SNMessagingKitConfiguration . shared . storage . getUserKeyPair ( ) else {
return nil
}
// g u a r d l e t b l i n d e d K e y P a i r : E C K e y P a i r = t r y ? u s e r K e y P a i r . c o n v e r t ( t o : . b l i n d e d , w i t h : p u b l i c K e y ) e l s e {
// r e t u r n n i l
// }
// TODO: C h a n g e t h i s b a c k o n c e y o u f i g u r e o u t w h y i t ' s b u s t e d
let blindedKeyPair : ECKeyPair = userKeyPair
// G e n e r a t e t h e s h a r e d S e c r e t b y " a B | | A | | B " w h e r e
// a , A a r e t h e u s e r s p r i v a t e a n d p u b l i c k e y s r e s p e c t i v e l y ,
// B i s t h e S O G S p u b l i c k e y
let maybeSharedSecret : Data ? = sodium . sharedSecret ( blindedKeyPair . privateKey . bytes , publicKeyData . bytes ) ?
. appending ( blindedKeyPair . publicKey )
. appending ( publicKeyData . bytes )
// G e n e r a t e t h e h a s h t o b e s e n t a l o n g w i t h t h e r e q u e s t
// i n t e r m e d i a t e H a s h = B l a k e 2 B ( s h a r e d S e c r e t , s i z e = 4 2 , s a l t = n o n c e b y t e s , p e r s o n = ' s o g s . s h a r e d _ k e y s ' )
// s e c r e t H a s h = B l a k e 2 B (
// M e t h o d | | P a t h | | T i m e s t a m p | | B o d y ,
// s i z e = 4 2 ,
// k e y = r ,
// s a l t = n o n c e b y t e s ,
// p e r s o n = ' s o g s . a u t h _ h e a d e r '
// )
let secretHashMessage : Bytes = method . bytes
. appending ( path . bytes )
. appending ( " \( timestamp ) " . bytes )
. appending ( request . httpBody ? . bytes ? ? [ ] ) // TODO: M i g h t n e e d t o d o t h e ' h t t p B o d y S t r e a m ' a s w e l l ? ? ?
guard let sharedSecret : Data = maybeSharedSecret else { return nil }
guard let intermediateHash : Bytes = sodium . genericHash . hashSaltPersonal ( message : sharedSecret . bytes , outputLength : 42 , key : nil , salt : nonce . bytes , personal : Personalization . sharedKeys . bytes ) else {
return nil
}
guard let secretHash : Bytes = sodium . genericHash . hashSaltPersonal ( message : secretHashMessage , outputLength : 42 , key : intermediateHash , salt : nonce . bytes , personal : Personalization . authHeader . bytes ) else {
return nil
}
updatedRequest . allHTTPHeaderFields = ( request . allHTTPHeaderFields ? ? [ : ] )
. updated ( with : [
Header . sogsPubKey . rawValue : blindedKeyPair . hexEncodedPublicKey ,
Header . sogsTimestamp . rawValue : " \( timestamp ) " ,
Header . sogsNonce . rawValue : nonce . base64EncodedString ( ) ,
Header . sogsHash . rawValue : secretHash . toBase64 ( )
] )
return updatedRequest
}
// MARK: - C a p a b i l i t i e s
public static func capabilities ( on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, Capabilities ) > {
public static func capabilities ( on server : String ) -> Promise < ( OnionRequestResponseInfoType , Capabilities ) > {
let request : Request = Request (
server : server ,
endpoint : . capabilities ,
queryParameters : [ : ] // TODO: A d d a n y r e q u i r e m e n t s ' . r e q u i r e d '
queryParameters : [ : ] // TODO: A d d a n y r e q u i r e m e n t s ' . r e q u i r e d ' .
)
// TODO: H a n d l e a ` 4 1 2 ` r e s p o n s e ( i e . a r e q u i r e d c a p a b i l i t y i s n ' t s u p p o r t e d )
// TODO: H a n d l e a ` 4 1 2 ` r e s p o n s e ( i e . a r e q u i r e d c a p a b i l i t y i s n ' t s u p p o r t e d ) .
return send ( request )
. decoded ( as : Capabilities . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
}
// MARK: - R o o m
public static func rooms ( for server : String ) -> Promise < ( OnionRequestAPI . ResponseInfo , [ Room ] ) > {
public static func rooms (
for server : String ,
through api : OnionRequestAPIType . Type = OnionRequestAPI . self ,
using storage : SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration . shared . storage ,
nonceGenerator : NonceGenerator16ByteType = NonceGenerator16Byte ( ) ,
date : Date = Date ( )
) -> Promise < ( OnionRequestResponseInfoType , [ Room ] ) > {
let request : Request = Request (
server : server ,
endpoint : . rooms
)
return send ( request )
return send ( request , through : api , using : storage , nonceGenerator : nonceGenerator , date : date )
. decoded ( as : [ Room ] . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
}
public static func room ( for roomToken : String , on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, Room ) > {
public static func room ( for roomToken : String , on server : String ) -> Promise < ( OnionRequestResponseInfoType , Room ) > {
let request : Request = Request (
server : server ,
endpoint : . room ( roomToken )
@ -277,7 +237,7 @@ public final class OpenGroupAPIV2: NSObject {
. decoded ( as : Room . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
}
public static func roomPollInfo ( lastUpdated : Int64 , for roomToken : String , on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, RoomPollInfo ) > {
public static func roomPollInfo ( lastUpdated : Int64 , for roomToken : String , on server : String ) -> Promise < ( OnionRequest ResponseInfoType , RoomPollInfo ) > {
let request : Request = Request (
server : server ,
endpoint : . roomPollInfo ( roomToken , lastUpdated )
@ -296,8 +256,8 @@ public final class OpenGroupAPIV2: NSObject {
whisperTo : String ? ,
whisperMods : Bool ,
with serverPublicKey : String
) -> Promise < ( OnionRequest API. ResponseInfo, Message ) > {
// TODO: C h a n g e t h i s t o u s e ' . b l i n d e d ' o n c e i t ' s w o r k i n g
) -> Promise < ( OnionRequest ResponseInfoType , Message ) > {
// TODO: C h a n g e t h i s t o u s e ' . b l i n d e d ' o n c e i t ' s w o r k i n g .
guard let signedRequest : ( data : Data , signature : Data ) = SendMessageRequest . sign ( message : plaintext , for : . standard , with : serverPublicKey ) else {
return Promise ( error : Error . signingFailed )
}
@ -325,7 +285,7 @@ public final class OpenGroupAPIV2: NSObject {
. decoded ( as : Message . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
}
public static func recentMessages ( in roomToken : String , on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, [ Message ] ) > {
public static func recentMessages ( in roomToken : String , on server : String ) -> Promise < ( OnionRequest ResponseInfoType , [ Message ] ) > {
// TODO: R e c e n t v s . S i n c e ?
let request : Request = Request (
server : server ,
@ -338,13 +298,13 @@ public final class OpenGroupAPIV2: NSObject {
return send ( request )
. decoded ( as : [ Message ] . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
. then ( on : OpenGroupAPIV2 . workQueue ) { responseInfo , messages -> Promise < ( OnionRequest API. ResponseInfo, [ Message ] ) > in
. then ( on : OpenGroupAPIV2 . workQueue ) { responseInfo , messages -> Promise < ( OnionRequest ResponseInfoType , [ Message ] ) > in
process ( messages : messages , for : roomToken , on : server )
. map { processedMessages in ( responseInfo , processedMessages ) }
}
}
public static func messagesBefore ( messageId : Int64 , in roomToken : String , on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, [ Message ] ) > {
public static func messagesBefore ( messageId : Int64 , in roomToken : String , on server : String ) -> Promise < ( OnionRequest ResponseInfoType , [ Message ] ) > {
// TODO: R e c e n t v s . S i n c e ?
let request : Request = Request (
server : server ,
@ -357,13 +317,13 @@ public final class OpenGroupAPIV2: NSObject {
return send ( request )
. decoded ( as : [ Message ] . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
. then ( on : OpenGroupAPIV2 . workQueue ) { responseInfo , messages -> Promise < ( OnionRequest API. ResponseInfo, [ Message ] ) > in
. then ( on : OpenGroupAPIV2 . workQueue ) { responseInfo , messages -> Promise < ( OnionRequest ResponseInfoType , [ Message ] ) > in
process ( messages : messages , for : roomToken , on : server )
. map { processedMessages in ( responseInfo , processedMessages ) }
}
}
public static func messagesSince ( seqNo : Int64 , in roomToken : String , on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, [ Message ] ) > {
public static func messagesSince ( seqNo : Int64 , in roomToken : String , on server : String ) -> Promise < ( OnionRequest ResponseInfoType , [ Message ] ) > {
// TODO: R e c e n t v s . S i n c e ?
let request : Request = Request (
server : server ,
@ -376,7 +336,7 @@ public final class OpenGroupAPIV2: NSObject {
return send ( request )
. decoded ( as : [ Message ] . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
. then ( on : OpenGroupAPIV2 . workQueue ) { responseInfo , messages -> Promise < ( OnionRequest API. ResponseInfo, [ Message ] ) > in
. then ( on : OpenGroupAPIV2 . workQueue ) { responseInfo , messages -> Promise < ( OnionRequest ResponseInfoType , [ Message ] ) > in
process ( messages : messages , for : roomToken , on : server )
. map { processedMessages in ( responseInfo , processedMessages ) }
}
@ -384,7 +344,7 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - P i n n i n g
public static func pinMessage ( id : Int64 , in roomToken : String , on server : String ) -> Promise < OnionRequest API. ResponseInfo> {
public static func pinMessage ( id : Int64 , in roomToken : String , on server : String ) -> Promise < OnionRequest ResponseInfoType > {
let request : Request = Request (
method : . post ,
server : server ,
@ -395,7 +355,7 @@ public final class OpenGroupAPIV2: NSObject {
. map { responseInfo , _ in responseInfo }
}
public static func unpinMessage ( id : Int64 , in roomToken : String , on server : String ) -> Promise < OnionRequest API. ResponseInfo> {
public static func unpinMessage ( id : Int64 , in roomToken : String , on server : String ) -> Promise < OnionRequest ResponseInfoType > {
let request : Request = Request (
method : . post ,
server : server ,
@ -406,7 +366,7 @@ public final class OpenGroupAPIV2: NSObject {
. map { responseInfo , _ in responseInfo }
}
public static func unpinAll ( in roomToken : String , on server : String ) -> Promise < OnionRequest API. ResponseInfo> {
public static func unpinAll ( in roomToken : String , on server : String ) -> Promise < OnionRequest ResponseInfoType > {
let request : Request = Request (
method : . post ,
server : server ,
@ -458,7 +418,7 @@ public final class OpenGroupAPIV2: NSObject {
return promise
}
public static func uploadFile ( _ bytes : [ UInt8 ] , fileName : String ? = nil , to roomToken : String , on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, FileUploadResponse ) > {
public static func uploadFile ( _ bytes : [ UInt8 ] , fileName : String ? = nil , to roomToken : String , on server : String ) -> Promise < ( OnionRequest ResponseInfoType , FileUploadResponse ) > {
let request : Request = Request (
method : . post ,
server : server ,
@ -473,7 +433,7 @@ public final class OpenGroupAPIV2: NSObject {
// / W a r n i n g : T h i s a p p r o a c h i s l e s s e f f i c i e n t a s i t e x p e c t s t h e d a t a t o b e b a s e 6 4 E n c o d e d ( w i t h i s 3 3 % l a r g e r t h a n b i n a r y ) , p l e a s e u s e t h e b i n a r y a p p r o a c h
// / w h e n e v e r p o s s i b l e
public static func uploadFile ( _ base64EncodedString : String , fileName : String ? = nil , to roomToken : String , on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, FileUploadResponse ) > {
public static func uploadFile ( _ base64EncodedString : String , fileName : String ? = nil , to roomToken : String , on server : String ) -> Promise < ( OnionRequest ResponseInfoType , FileUploadResponse ) > {
let request : Request = Request (
method : . post ,
server : server ,
@ -486,7 +446,7 @@ public final class OpenGroupAPIV2: NSObject {
. decoded ( as : FileUploadResponse . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
}
public static func downloadFile ( _ fileId : Int64 , from roomToken : String , on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, Data ) > {
public static func downloadFile ( _ fileId : Int64 , from roomToken : String , on server : String ) -> Promise < ( OnionRequest ResponseInfoType , Data ) > {
let request : Request = Request (
server : server ,
endpoint : . roomFileIndividual ( roomToken , fileId )
@ -500,7 +460,7 @@ public final class OpenGroupAPIV2: NSObject {
}
}
public static func downloadFileJson ( _ fileId : Int64 , from roomToken : String , on server : String ) -> Promise < ( OnionRequest API. ResponseInfo, FileDownloadResponse ) > {
public static func downloadFileJson ( _ fileId : Int64 , from roomToken : String , on server : String ) -> Promise < ( OnionRequest ResponseInfoType , FileDownloadResponse ) > {
let request : Request = Request (
server : server ,
endpoint : . roomFileIndividualJson ( roomToken , fileId )
@ -510,6 +470,116 @@ public final class OpenGroupAPIV2: NSObject {
. decoded ( as : FileDownloadResponse . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
}
// MARK: - U s e r s
public static func userBan ( _ sessionId : String , for timeout : TimeInterval ? = nil , from roomTokens : [ String ] ? = nil , on server : String ) -> Promise < ( OnionRequestResponseInfoType , Data ? ) > {
let requestBody : UserBanRequest = UserBanRequest (
rooms : roomTokens ,
global : ( roomTokens = = nil ? true : nil ) ,
timeout : timeout
)
guard let body : Data = try ? JSONEncoder ( ) . encode ( requestBody ) else {
return Promise ( error : Error . parsingFailed )
}
let request : Request = Request (
method : . post ,
server : server ,
endpoint : . userBan ( sessionId ) ,
body : body
)
return send ( request )
}
public static func userUnban ( _ sessionId : String , from roomTokens : [ String ] ? = nil , on server : String ) -> Promise < ( OnionRequestResponseInfoType , Data ? ) > {
let requestBody : UserUnbanRequest = UserUnbanRequest (
rooms : roomTokens ,
global : ( roomTokens = = nil ? true : nil )
)
guard let body : Data = try ? JSONEncoder ( ) . encode ( requestBody ) else {
return Promise ( error : Error . parsingFailed )
}
let request : Request = Request (
method : . post ,
server : server ,
endpoint : . userUnban ( sessionId ) ,
body : body
)
return send ( request )
}
public static func userPermissionUpdate ( _ sessionId : String , read : Bool , write : Bool , upload : Bool , for roomTokens : [ String ] , timeout : TimeInterval , on server : String ) -> Promise < ( OnionRequestResponseInfoType , Data ? ) > {
let requestBody : UserPermissionsRequest = UserPermissionsRequest (
rooms : roomTokens ,
timeout : timeout ,
read : read ,
write : write ,
upload : upload
)
guard let body : Data = try ? JSONEncoder ( ) . encode ( requestBody ) else {
return Promise ( error : Error . parsingFailed )
}
let request : Request = Request (
method : . post ,
server : server ,
endpoint : . userPermission ( sessionId ) ,
body : body
)
return send ( request )
}
public static func userModeratorUpdate ( _ sessionId : String , moderator : Bool , admin : Bool , visible : Bool , for roomTokens : [ String ] ? = nil , on server : String ) -> Promise < ( OnionRequestResponseInfoType , Data ? ) > {
let requestBody : UserModeratorRequest = UserModeratorRequest (
rooms : roomTokens ,
global : ( roomTokens = = nil ? true : nil ) ,
moderator : moderator ,
admin : admin ,
visible : visible
)
guard let body : Data = try ? JSONEncoder ( ) . encode ( requestBody ) else {
return Promise ( error : Error . parsingFailed )
}
let request : Request = Request (
method : . post ,
server : server ,
endpoint : . userModerator ( sessionId ) ,
body : body
)
return send ( request )
}
public static func userDeleteMessages ( _ sessionId : String , for roomTokens : [ String ] ? = nil , on server : String ) -> Promise < ( OnionRequestResponseInfoType , UserDeleteMessagesResponse ) > {
let requestBody : UserDeleteMessagesRequest = UserDeleteMessagesRequest (
rooms : roomTokens ,
global : ( roomTokens = = nil ? true : nil )
)
guard let body : Data = try ? JSONEncoder ( ) . encode ( requestBody ) else {
return Promise ( error : Error . parsingFailed )
}
let request : Request = Request (
method : . post ,
server : server ,
endpoint : . userDeleteMessages ( sessionId ) ,
body : body
)
return send ( request )
. decoded ( as : UserDeleteMessagesResponse . self , on : OpenGroupAPIV2 . workQueue , error : Error . parsingFailed )
}
// MARK: - P r o c e s s i n g
// TODO: M o v e t h e s e m e t h o d s t o t h e O p e n G r o u p M a n a g e r ? ( s e e m s o d d f o r t h e m t o b e i n t h e A P I )
@ -599,9 +669,85 @@ public final class OpenGroupAPIV2: NSObject {
)
}
// MARK: - A u t h e n t i c a t i o n
// TODO: T u r n ' S o d i u m ' i n t o a p r o t o c o l f o r u n i t t e s t i n g
static func sign (
_ request : URLRequest ,
with publicKey : String ,
using storage : SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration . shared . storage ,
sodium : Sodium = Sodium ( ) ,
nonceGenerator : NonceGenerator16ByteType = NonceGenerator16Byte ( ) ,
date : Date = Date ( )
) -> URLRequest ? {
guard let url : URL = request . url else { return nil }
var updatedRequest : URLRequest = request
let path : String = url . path
. appending ( url . query . map { value in " ? \( value ) " } )
let method : String = ( request . httpMethod ? ? " GET " )
let timestamp : Int = Int ( floor ( date . timeIntervalSince1970 ) )
let nonce : Data = Data ( nonceGenerator . nonce ( ) )
guard let publicKeyData : Data = publicKey . dataFromHex ( ) else { return nil }
guard let userKeyPair : ECKeyPair = storage . getUserKeyPair ( ) else {
return nil
}
// g u a r d l e t b l i n d e d K e y P a i r : E C K e y P a i r = t r y ? u s e r K e y P a i r . c o n v e r t ( t o : . b l i n d e d , w i t h : p u b l i c K e y ) e l s e {
// r e t u r n n i l
// }
// TODO: C h a n g e t h i s b a c k o n c e y o u f i g u r e o u t w h y i t ' s b u s t e d
let blindedKeyPair : ECKeyPair = userKeyPair
// / G e n e r a t e t h e s h a r e d S e c r e t b y " a B | | A | | B " w h e r e
// / a , A a r e t h e u s e r s p r i v a t e a n d p u b l i c k e y s r e s p e c t i v e l y ,
// / B i s t h e S O G S p u b l i c k e y
let maybeSharedSecret : Data ? = sodium . sharedSecret ( blindedKeyPair . privateKey . bytes , publicKeyData . bytes ) ?
. appending ( blindedKeyPair . publicKey )
. appending ( publicKeyData . bytes )
// / G e n e r a t e t h e h a s h t o b e s e n t a l o n g w i t h t h e r e q u e s t
// / i n t e r m e d i a t e H a s h = B l a k e 2 B ( s h a r e d S e c r e t , s i z e = 4 2 , s a l t = n o n c e b y t e s , p e r s o n = ' s o g s . s h a r e d _ k e y s ' )
// / s e c r e t H a s h = B l a k e 2 B (
// / M e t h o d | | P a t h | | T i m e s t a m p | | B o d y ,
// / s i z e = 4 2 ,
// / k e y = r ,
// / s a l t = n o n c e b y t e s ,
// / p e r s o n = ' s o g s . a u t h _ h e a d e r '
// / )
let secretHashMessage : Bytes = method . bytes
. appending ( path . bytes )
. appending ( " \( timestamp ) " . bytes )
. appending ( request . httpBody ? . bytes ? ? [ ] ) // TODO: M i g h t n e e d t o d o t h e ' h t t p B o d y S t r e a m ' a s w e l l ? ? ?
guard let sharedSecret : Data = maybeSharedSecret else { return nil }
guard let intermediateHash : Bytes = sodium . genericHash . hashSaltPersonal ( message : sharedSecret . bytes , outputLength : 42 , key : nil , salt : nonce . bytes , personal : Personalization . sharedKeys . bytes ) else {
return nil
}
guard let secretHash : Bytes = sodium . genericHash . hashSaltPersonal ( message : secretHashMessage , outputLength : 42 , key : intermediateHash , salt : nonce . bytes , personal : Personalization . authHeader . bytes ) else {
return nil
}
updatedRequest . allHTTPHeaderFields = ( request . allHTTPHeaderFields ? ? [ : ] )
. updated ( with : [
Header . sogsPubKey . rawValue : blindedKeyPair . hexEncodedPublicKey ,
Header . sogsTimestamp . rawValue : " \( timestamp ) " ,
Header . sogsNonce . rawValue : nonce . base64EncodedString ( ) ,
Header . sogsHash . rawValue : secretHash . toBase64 ( )
] )
return updatedRequest
}
// MARK: - C o n v e n i e n c e
private static func send ( _ request : Request , through api : OnionRequestAPIType . Type = OnionRequestAPI . self ) -> Promise < ( OnionRequestAPI . ResponseInfo , Data ? ) > {
private static func send (
_ request : Request ,
through api : OnionRequestAPIType . Type = OnionRequestAPI . self ,
using storage : SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration . shared . storage ,
nonceGenerator : NonceGenerator16ByteType = NonceGenerator16Byte ( ) ,
date : Date = Date ( )
) -> Promise < ( OnionRequestResponseInfoType , Data ? ) > {
guard let url : URL = request . url else { return Promise ( error : Error . invalidURL ) }
var urlRequest : URLRequest = URLRequest ( url : url )
@ -612,21 +758,21 @@ public final class OpenGroupAPIV2: NSObject {
urlRequest . httpBody = request . body
if request . useOnionRouting {
guard let publicKey = SNMessagingKitConfiguration. shared . storage. getOpenGroupPublicKey ( for : request . server ) else {
guard let publicKey = storage. getOpenGroupPublicKey ( for : request . server ) else {
return Promise ( error : Error . noPublicKey )
}
if request . isAuthRequired {
// A t t e m p t t o s i g n t h e r e q u e s t w i t h t h e n e w a u t h
guard let signedRequest : URLRequest = sign ( urlRequest , with : publicKey ) else {
guard let signedRequest : URLRequest = sign ( urlRequest , with : publicKey , using : storage , nonceGenerator : nonceGenerator , date : date ) else {
return Promise ( error : Error . signingFailed )
}
// TODO: ' r e m o v e A u t h T o k e n ' a s a m i g r a t i o n ? ? ? ( w o u l d p r e v i o u s l y d o t h i s w h e n g e t t i n g a ` 4 0 1 ` ) .
return OnionRequestAPI . sendOnionRequest ( signedRequest , to : request . server , using : publicKey )
return api . sendOnionRequest ( signedRequest , to : request . server , with : publicKey )
}
return OnionRequestAPI . sendOnionRequest ( urlRequest , to : request . server , using : publicKey )
return api . sendOnionRequest ( urlRequest , to : request . server , with : publicKey )
}
preconditionFailure ( " It's currently not allowed to send non onion routed requests. " )
@ -641,6 +787,7 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - - L e g a c y A u t h
@ available ( * , deprecated , message : " Use request signing instead " )
private static func legacyGetAuthToken ( for room : String , on server : String ) -> Promise < String > {
let storage = SNMessagingKitConfiguration . shared . storage
@ -676,6 +823,7 @@ public final class OpenGroupAPIV2: NSObject {
return promise
}
@ available ( * , deprecated , message : " Use request signing instead " )
public static func legacyRequestNewAuthToken ( for room : String , on server : String ) -> Promise < String > {
SNLog ( " Requesting auth token for server: \( server ) . " )
guard let userKeyPair : ECKeyPair = SNMessagingKitConfiguration . shared . storage . getUserKeyPair ( ) else {
@ -705,6 +853,7 @@ public final class OpenGroupAPIV2: NSObject {
}
}
@ available ( * , deprecated , message : " Use request signing instead " )
public static func legacyClaimAuthToken ( _ authToken : String , for room : String , on server : String ) -> Promise < String > {
let requestBody : PublicKeyBody = PublicKeyBody ( publicKey : getUserHexEncodedPublicKey ( ) )
@ -729,6 +878,7 @@ public final class OpenGroupAPIV2: NSObject {
}
// / S h o u l d b e c a l l e d w h e n l e a v i n g a g r o u p .
@ available ( * , deprecated , message : " Use request signing instead " )
public static func legacyDeleteAuthToken ( for room : String , on server : String ) -> Promise < Void > {
let request : Request = Request (
method : . delete ,
@ -748,6 +898,7 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - - L e g a c y R e q u e s t s
@ available ( * , deprecated , message : " Use poll or batch instead " )
public static func legacyCompactPoll ( _ server : String ) -> Promise < LegacyCompactPollResponse > {
let storage : SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration . shared . storage
let rooms : [ String ] = storage . getAllV2OpenGroups ( ) . values
@ -841,6 +992,7 @@ public final class OpenGroupAPIV2: NSObject {
}
}
@ available ( * , deprecated , message : " Use getDefaultRoomsIfNeeded instead " )
public static func legacyGetDefaultRoomsIfNeeded ( ) {
Storage . shared . write (
with : { transaction in
@ -861,6 +1013,7 @@ public final class OpenGroupAPIV2: NSObject {
)
}
@ available ( * , deprecated , message : " Use rooms(for:) instead " )
public static func legacyGetAllRooms ( from server : String ) -> Promise < [ LegacyRoomInfo ] > {
let request : Request = Request (
server : server ,
@ -877,6 +1030,7 @@ public final class OpenGroupAPIV2: NSObject {
}
}
@ available ( * , deprecated , message : " Use room(for:on:) instead " )
public static func legacyGetRoomInfo ( for room : String , on server : String ) -> Promise < LegacyRoomInfo > {
let request : Request = Request (
server : server ,
@ -894,6 +1048,7 @@ public final class OpenGroupAPIV2: NSObject {
}
}
@ available ( * , deprecated , message : " Use roomImage(_:for:on:) instead " )
public static func legacyGetGroupImage ( for room : String , on server : String ) -> Promise < Data > {
// N o r m a l l y t h e i m a g e f o r a g i v e n g r o u p i s s t o r e d w i t h t h e g r o u p t h r e a d , s o i t ' s o n l y
// f e t c h e d o n c e . H o w e v e r , o n t h e j o i n o p e n g r o u p s c r e e n w e s h o w i m a g e s f o r g r o u p s t h e
@ -942,6 +1097,7 @@ public final class OpenGroupAPIV2: NSObject {
return promise
}
@ available ( * , deprecated , message : " Use room(for:on:) instead " )
public static func legacyGetMemberCount ( for room : String , on server : String ) -> Promise < UInt64 > {
let request : Request = Request (
server : server ,
@ -965,6 +1121,7 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - L e g a c y F i l e S t o r a g e
@ available ( * , deprecated , message : " Use uploadFile(_:fileName:to:on:) instead " )
public static func legacyUpload ( _ file : Data , to room : String , on server : String ) -> Promise < UInt64 > {
let requestBody : FileUploadBody = FileUploadBody ( file : file . base64EncodedString ( ) )
@ -982,6 +1139,7 @@ public final class OpenGroupAPIV2: NSObject {
}
}
@ available ( * , deprecated , message : " Use downloadFile(_:from:on:) instead " )
public static func legacyDownload ( _ file : UInt64 , from room : String , on server : String ) -> Promise < Data > {
let request = Request ( server : server , room : room , endpoint : . legacyFile ( file ) )
@ -995,6 +1153,7 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - L e g a c y M e s s a g e S e n d i n g & R e c e i v i n g
@ available ( * , deprecated , message : " Use send(_:to:on:whisperTo:whisperMods:with:) instead " )
public static func legacySend ( _ message : OpenGroupMessageV2 , to room : String , on server : String , with publicKey : String ) -> Promise < OpenGroupMessageV2 > {
guard let signedMessage = message . sign ( with : publicKey ) else { return Promise ( error : Error . signingFailed ) }
guard let body : Data = try ? JSONEncoder ( ) . encode ( signedMessage ) else {
@ -1012,6 +1171,7 @@ public final class OpenGroupAPIV2: NSObject {
}
}
@ available ( * , deprecated , message : " Use recentMessages(in:on:) or messagesSince(seqNo:in:on:) instead " )
public static func legacyGetMessages ( for room : String , on server : String ) -> Promise < [ OpenGroupMessageV2 ] > {
let storage = SNMessagingKitConfiguration . shared . storage
let request : Request = Request (
@ -1033,6 +1193,8 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - L e g a c y M e s s a g e D e l e t i o n
// TODO: N o d e l e t e m e t h o d ? ? ? ?
@ available ( * , deprecated , message : " Use v4 endpoint instead " )
public static func legacyDeleteMessage ( with serverID : Int64 , from room : String , on server : String ) -> Promise < Void > {
let request : Request = Request (
method : . delete ,
@ -1044,6 +1206,7 @@ public final class OpenGroupAPIV2: NSObject {
return legacySend ( request ) . map ( on : OpenGroupAPIV2 . workQueue ) { _ in }
}
@ available ( * , deprecated , message : " Use v4 endpoint instead " )
public static func legacyGetDeletedMessages ( for room : String , on server : String ) -> Promise < [ Deletion ] > {
let storage = SNMessagingKitConfiguration . shared . storage
@ -1066,6 +1229,7 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - L e g a c y M o d e r a t i o n
@ available ( * , deprecated , message : " Use v4 endpoint instead " )
public static func legacyGetModerators ( for room : String , on server : String ) -> Promise < [ String ] > {
let request : Request = Request (
server : server ,
@ -1090,6 +1254,7 @@ public final class OpenGroupAPIV2: NSObject {
}
}
@ available ( * , deprecated , message : " Use v4 endpoint instead " )
public static func legacyBan ( _ publicKey : String , from room : String , on server : String ) -> Promise < Void > {
let requestBody : PublicKeyBody = PublicKeyBody ( publicKey : getUserHexEncodedPublicKey ( ) )
@ -1108,6 +1273,7 @@ public final class OpenGroupAPIV2: NSObject {
return legacySend ( request ) . map ( on : OpenGroupAPIV2 . workQueue ) { _ in }
}
@ available ( * , deprecated , message : " Use v4 endpoint instead " )
public static func legacyBanAndDeleteAllMessages ( _ publicKey : String , from room : String , on server : String ) -> Promise < Void > {
let requestBody : PublicKeyBody = PublicKeyBody ( publicKey : getUserHexEncodedPublicKey ( ) )
@ -1126,6 +1292,7 @@ public final class OpenGroupAPIV2: NSObject {
return legacySend ( request ) . map ( on : OpenGroupAPIV2 . workQueue ) { _ in }
}
@ available ( * , deprecated , message : " Use v4 endpoint instead " )
public static func legacyUnban ( _ publicKey : String , from room : String , on server : String ) -> Promise < Void > {
let request : Request = Request (
method : . delete ,
@ -1140,6 +1307,7 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - P r o c e s s i n g
// TODO: M o v e t h e s e m e t h o d s t o t h e O p e n G r o u p M a n a g e r ? ( s e e m s o d d f o r t h e m t o b e i n t h e A P I )
@ available ( * , deprecated , message : " Use v4 endpoint instead " )
private static func legacyProcess ( messages : [ OpenGroupMessageV2 ] ? , for room : String , on server : String ) -> Promise < [ OpenGroupMessageV2 ] > {
guard let messages : [ OpenGroupMessageV2 ] = messages , ! messages . isEmpty else { return Promise . value ( [ ] ) }
@ -1165,6 +1333,7 @@ public final class OpenGroupAPIV2: NSObject {
return Promise . value ( messages )
}
@ available ( * , deprecated , message : " Use v4 endpoint instead " )
private static func legacyProcess ( deletions : [ Deletion ] ? , for room : String , on server : String ) -> Promise < [ Deletion ] > {
guard let deletions : [ Deletion ] = deletions else { return Promise . value ( [ ] ) }
@ -1192,7 +1361,8 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - L e g a c y C o n v e n i e n c e
private static func legacySend ( _ request : Request , through api : OnionRequestAPIType . Type = LegacyOnionRequestAPI . self ) -> Promise < ( OnionRequestAPI . ResponseInfo , Data ? ) > {
@ available ( * , deprecated , message : " Use v4 endpoint instead " )
private static func legacySend ( _ request : Request , through api : OnionRequestAPIType . Type = OnionRequestAPI . self ) -> Promise < ( OnionRequestResponseInfoType , Data ? ) > {
guard let url : URL = request . url else { return Promise ( error : Error . invalidURL ) }
var urlRequest : URLRequest = URLRequest ( url : url )
@ -1211,14 +1381,14 @@ public final class OpenGroupAPIV2: NSObject {
// B e c a u s e l e g a c y a u t h h a p p e n s o n a p e r - r o o m b a s i s , w e n e e d t o h a v e a r o o m t o
// m a k e a n a u t h e n t i c a t e d r e q u e s t
guard let room = request . room else {
return api . sendOnionRequest ( urlRequest , to : request . server , target: " /loki/v3/lsrpc " , using : publicKey )
return api . sendOnionRequest ( urlRequest , to : request . server , using: . v3 , with : publicKey )
}
return legacyGetAuthToken ( for : room , on : request . server )
. then ( on : OpenGroupAPIV2 . workQueue ) { authToken -> Promise < ( OnionRequest API. ResponseInfo, Data ? ) > in
. then ( on : OpenGroupAPIV2 . workQueue ) { authToken -> Promise < ( OnionRequest ResponseInfoType , Data ? ) > in
urlRequest . setValue ( authToken , forHTTPHeaderField : Header . authorization . rawValue )
let promise = api . sendOnionRequest ( urlRequest , to : request . server , target: " /loki/v3/lsrpc " , using : publicKey )
let promise = api . sendOnionRequest ( urlRequest , to : request . server , using: . v3 , with : publicKey )
promise . catch ( on : OpenGroupAPIV2 . workQueue ) { error in
// A 4 0 1 m e a n s t h a t w e d i d n ' t p r o v i d e a ( v a l i d ) a u t h t o k e n f o r a r o u t e
// t h a t r e q u i r e d o n e . W e u s e t h i s a s a n i n d i c a t i o n t h a t t h e t o k e n w e ' r e
@ -1238,7 +1408,7 @@ public final class OpenGroupAPIV2: NSObject {
}
}
return api . sendOnionRequest ( urlRequest , to : request . server , target: " /loki/v3/lsrpc " , using : publicKey )
return api . sendOnionRequest ( urlRequest , to : request . server , using: . v3 , with : publicKey )
}
preconditionFailure ( " It's currently not allowed to send non onion routed requests. " )