diff --git a/Source/ARTAuth.m b/Source/ARTAuth.m index 690c2c34b..554b0d277 100644 --- a/Source/ARTAuth.m +++ b/Source/ARTAuth.m @@ -493,21 +493,24 @@ - (void)handleAuthUrlResponse:(NSHTTPURLResponse *)response - (NSObject *)executeTokenRequest:(ARTTokenRequest *)tokenRequest callback:(ARTTokenDetailsCallback)callback { id encoder = _rest.defaultEncoder; - - NSURL *requestUrl = [NSURL URLWithString:[NSString stringWithFormat:@"/keys/%@/requestToken?format=%@", tokenRequest.keyName, [encoder formatAsString]] - relativeToURL:_rest.baseUrl]; + NSError *error = nil; + NSData *bodyData = [encoder encodeTokenRequest:tokenRequest error:&error]; + if (error) { + callback(nil, error); + return nil; + } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestUrl]; - request.HTTPMethod = @"POST"; - - NSError *encodeError = nil; - request.HTTPBody = [encoder encodeTokenRequest:tokenRequest error:&encodeError]; - if (encodeError) { - callback(nil, encodeError); + NSMutableURLRequest *request = [_rest buildRequest:@"POST" + path:[NSString stringWithFormat:@"/keys/%@/requestToken", tokenRequest.keyName] + baseUrl:nil + params:@{ @"format": encoder.formatAsString } + body:bodyData + headers:nil + error:&error]; + if (error) { + callback(nil, error); return nil; } - [request setValue:[encoder mimeType] forHTTPHeaderField:@"Accept"]; - [request setValue:[encoder mimeType] forHTTPHeaderField:@"Content-Type"]; return [_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOff wrapperSDKAgents:nil completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (error) { diff --git a/Source/ARTDataQuery.m b/Source/ARTDataQuery.m index af98d8da8..29d226b02 100755 --- a/Source/ARTDataQuery.m +++ b/Source/ARTDataQuery.m @@ -22,18 +22,18 @@ - (instancetype)init { } } -- (NSMutableArray *)asQueryItems:(NSError *_Nullable*)error { - NSMutableArray *items = [NSMutableArray array]; +- (NSStringDictionary *)asQueryParams { + NSMutableStringDictionary *items = [NSMutableStringDictionary dictionary]; if (self.start) { - [items addObject:[NSURLQueryItem queryItemWithName:@"start" value:[NSString stringWithFormat:@"%llu", dateToMilliseconds(self.start)]]]; + items[@"start"] = [NSString stringWithFormat:@"%llu", dateToMilliseconds(self.start)]; } if (self.end) { - [items addObject:[NSURLQueryItem queryItemWithName:@"end" value:[NSString stringWithFormat:@"%llu", dateToMilliseconds(self.end)]]]; + items[@"end"] = [NSString stringWithFormat:@"%llu", dateToMilliseconds(self.end)]; } - - [items addObject:[NSURLQueryItem queryItemWithName:@"limit" value:[NSString stringWithFormat:@"%hu", self.limit]]]; - [items addObject:[NSURLQueryItem queryItemWithName:@"direction" value:queryDirectionToString(self.direction)]]; + + items[@"limit"] = [NSString stringWithFormat:@"%hu", self.limit]; + items[@"direction"] = queryDirectionToString(self.direction); return items; } @@ -42,20 +42,10 @@ - (NSMutableArray *)asQueryItems:(NSError *_Nullable*)error { @implementation ARTRealtimeHistoryQuery -- (NSMutableArray *)asQueryItems:(NSError **)errorPtr { - NSMutableArray *items = [super asQueryItems:errorPtr]; - if (*errorPtr) { - return nil; - } - if (self.untilAttach) { - NSAssert(self.realtimeChannel, @"ARTRealtimeHistoryQuery used from outside ARTRealtimeChannel.history"); - if (self.realtimeChannel.state_nosync != ARTRealtimeChannelAttached) { - *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain code:ARTRealtimeHistoryErrorNotAttached userInfo:@{NSLocalizedDescriptionKey:@"ARTRealtimeHistoryQuery: untilAttach used in channel that isn't attached"}]; - return nil; - } - [items addObject:[NSURLQueryItem queryItemWithName:@"fromSerial" value:self.realtimeChannel.attachSerial]]; - } - return items; +- (NSStringDictionary *)asQueryParams { + NSMutableStringDictionary *params = super.asQueryParams.mutableCopy; + params[@"fromSerial"] = self.realtimeChannelAttachSerial; + return params; } @end diff --git a/Source/ARTJsonLikeEncoder.m b/Source/ARTJsonLikeEncoder.m index 69fad01d2..b41c242eb 100644 --- a/Source/ARTJsonLikeEncoder.m +++ b/Source/ARTJsonLikeEncoder.m @@ -617,7 +617,7 @@ - (ARTAuthDetails *)authDetailsFromDictionary:(NSDictionary *)input { return [[ARTAuthDetails alloc] initWithToken:[input artString:@"accessToken"]]; } -- (NSArray *)messagesToArray:(NSArray *)messages { +- (NSArray *)messagesToArray:(NSArray *)messages { NSMutableArray *output = [NSMutableArray array]; for (ARTMessage *message in messages) { diff --git a/Source/ARTNSString+ARTUtil.m b/Source/ARTNSString+ARTUtil.m index 53686cdf7..8dfe3559a 100644 --- a/Source/ARTNSString+ARTUtil.m +++ b/Source/ARTNSString+ARTUtil.m @@ -17,4 +17,18 @@ - (BOOL)isNotEmptyString { return ![self isEmptyString]; } +- (NSString *)encodePathSegment { + // Source: https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + // i.e. segment = unreserved / pct-encoded / sub-delims / ":" / "@", where + // unreserved = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~ + // pct-encoded = %XX + // sub-delims = !$&'()*+,;= + NSCharacterSet *allowedSet = [NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&'()*+,;=:@"]; + NSString *escaped = [self stringByAddingPercentEncodingWithAllowedCharacters:allowedSet]; + if (!escaped) { + [NSException raise:NSInternalInconsistencyException format:@"String '%@' can't be percent encoded.", self]; + } + return escaped; +} + @end diff --git a/Source/ARTPushActivationStateMachine.m b/Source/ARTPushActivationStateMachine.m index f3de57bfe..2d13be7fa 100644 --- a/Source/ARTPushActivationStateMachine.m +++ b/Source/ARTPushActivationStateMachine.m @@ -190,11 +190,19 @@ - (void)deviceRegistration:(ARTErrorInfo *)error { } void (^doDeviceRegistration)(void) = ^{ - // Asynchronous HTTP request - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"]]; - request.HTTPMethod = @"POST"; - request.HTTPBody = [[self->_rest defaultEncoder] encodeLocalDevice:local error:nil]; - [request setValue:[[self->_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"POST" + path:@"/push/deviceRegistrations" + baseUrl:self->_rest.baseUrl + params:nil + body:[self->_rest.defaultEncoder encodeLocalDevice:local error:nil] + headers:nil + error:&error]; + if (error) { + ARTLogError(self->_logger, @"%@: device registration failed (%@)", NSStringFromClass(self.class), error.localizedDescription); + [self sendEvent:[ARTPushActivationEventGettingDeviceRegistrationFailed newWithError:[ARTErrorInfo createFromNSError:error]]]; + return; + } ARTLogDebug(self->_logger, @"%@: device registration with request %@", NSStringFromClass(self.class), request); [self->_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:nil completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -252,14 +260,26 @@ - (void)deviceUpdateRegistration:(ARTErrorInfo *)error { return; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[[NSURL URLWithString:@"/push/deviceRegistrations"] URLByAppendingPathComponent:local.id]]; - request.HTTPMethod = @"PATCH"; - request.HTTPBody = [[_rest defaultEncoder] encode:@{ + NSDictionary *bodyDict = @{ @"push": @{ @"recipient": local.push.recipient } - } error:nil]; - [request setValue:[[_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + }; + + NSError *requestError = nil; + NSMutableURLRequest *request = [_rest buildRequest:@"PATCH" + path:[@"/push/deviceRegistrations" stringByAppendingPathComponent:local.id] + baseUrl:_rest.baseUrl + params:nil + body:bodyDict + headers:nil + error:&requestError]; + if (requestError) { + ARTLogError(self->_logger, @"%@: device update failed (%@)", NSStringFromClass(self.class), requestError.localizedDescription); + [self sendEvent:[ARTPushActivationEventSyncRegistrationFailed newWithError:[ARTErrorInfo createFromNSError:requestError]]]; + return; + } + [request setDeviceAuthentication:local]; ARTLogDebug(_logger, @"%@: update device with request %@", NSStringFromClass(self.class), request); @@ -303,17 +323,26 @@ - (void)syncDevice { void (^doDeviceSync)(void) = ^{ // Asynchronous HTTP request - NSString *const path = [@"/push/deviceRegistrations" stringByAppendingPathComponent:local.id]; - NSMutableURLRequest *const request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:path]]; - request.HTTPMethod = @"PUT"; - request.HTTPBody = [[self->_rest defaultEncoder] encodeDeviceDetails:local error:nil]; - [request setValue:[[self->_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + NSError *requestError = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"PUT" + path:[@"/push/deviceRegistrations" stringByAppendingPathComponent:local.id] + baseUrl:self->_rest.baseUrl + params:nil + body:[self->_rest.defaultEncoder encodeDeviceDetails:local error:nil] + headers:nil + error:&requestError]; + if (requestError) { + ARTLogError(self->_logger, @"%@: device sync failed (%@)", NSStringFromClass(self.class), requestError.localizedDescription); + [self sendEvent:[ARTPushActivationEventSyncRegistrationFailed newWithError:[ARTErrorInfo createFromNSError:requestError]]]; + return; + } + [request setDeviceAuthentication:local]; ARTLogDebug(self->_logger, @"%@: sync device with request %@", NSStringFromClass(self.class), request); [self->_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:nil completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (error) { - ARTLogError(self->_logger, @"%@: device registration failed (%@)", NSStringFromClass(self.class), error.localizedDescription); + ARTLogError(self->_logger, @"%@: device sync failed (%@)", NSStringFromClass(self.class), error.localizedDescription); [self sendEvent:[ARTPushActivationEventSyncRegistrationFailed newWithError:[ARTErrorInfo createFromNSError:error]]]; return; } @@ -361,8 +390,20 @@ - (void)deviceUnregistration:(ARTErrorInfo *)error { } // Asynchronous HTTP request - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[[NSURL URLWithString:@"/push/deviceRegistrations"] URLByAppendingPathComponent:local.id]]; - request.HTTPMethod = @"DELETE"; + NSError *requestError = nil; + NSMutableURLRequest *request = [_rest buildRequest:@"DELETE" + path:[@"/push/deviceRegistrations" stringByAppendingPathComponent:local.id] + baseUrl:_rest.baseUrl + params:nil + body:nil + headers:nil + error:&requestError]; + if (requestError) { + ARTLogError(self->_logger, @"%@: device deregistration failed (%@)", NSStringFromClass(self.class), requestError.localizedDescription); + [self sendEvent:[ARTPushActivationEventDeregistrationFailed newWithError:[ARTErrorInfo createFromNSError:requestError]]]; + return; + } + [request setDeviceAuthentication:local]; ARTLogDebug(_logger, @"%@: device deregistration with request %@", NSStringFromClass(self.class), request); diff --git a/Source/ARTPushAdmin.m b/Source/ARTPushAdmin.m index 81b110e44..b50aa8707 100644 --- a/Source/ARTPushAdmin.m +++ b/Source/ARTPushAdmin.m @@ -75,13 +75,21 @@ - (void)publish:(ARTPushRecipient *)recipient data:(ARTJsonObject *)data wrapper return; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/publish"]]; - request.HTTPMethod = @"POST"; NSMutableDictionary *body = [NSMutableDictionary dictionary]; [body setObject:recipient forKey:@"recipient"]; [body addEntriesFromDictionary:data]; - request.HTTPBody = [[self->_rest defaultEncoder] encode:body error:nil]; - [request setValue:[[self->_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"POST" + path:@"/push/publish" + baseUrl:self->_rest.baseUrl + params:nil + body:body + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; + } ARTLogDebug(self->_logger, @"push notification to a single device %@", request); [self->_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:wrapperSDKAgents completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { diff --git a/Source/ARTPushChannel.m b/Source/ARTPushChannel.m index 95bc551fe..8ff012da9 100644 --- a/Source/ARTPushChannel.m +++ b/Source/ARTPushChannel.m @@ -120,13 +120,24 @@ - (void)subscribeDeviceWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapp } NSString *deviceId = device.id; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; - request.HTTPMethod = @"POST"; - request.HTTPBody = [[self->_rest defaultEncoder] encode:@{ + NSDictionary *bodyDict = @{ @"deviceId": deviceId, @"channel": self->_channel.name, - } error:nil]; - [request setValue:[[self->_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + }; + + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"POST" + path:@"/push/channelSubscriptions" + baseUrl:self->_rest.baseUrl + params:nil + body:bodyDict + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; + } + [request setDeviceAuthentication:deviceId localDevice:device]; ARTLogDebug(self->_logger, @"subscribe notifications for device %@ in channel %@", deviceId, self->_channel.name); @@ -155,13 +166,23 @@ - (void)subscribeClientWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapp return; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"]]; - request.HTTPMethod = @"POST"; - request.HTTPBody = [[self->_rest defaultEncoder] encode:@{ + NSDictionary *bodyDict = @{ @"clientId": clientId, @"channel": self->_channel.name, - } error:nil]; - [request setValue:[[self->_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + }; + + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"POST" + path:@"/push/channelSubscriptions" + baseUrl:self->_rest.baseUrl + params:nil + body:bodyDict + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; + } ARTLogDebug(self->_logger, @"subscribe notifications for clientId %@ in channel %@", clientId, self->_channel.name); [self->_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:wrapperSDKAgents completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -190,14 +211,24 @@ - (void)unsubscribeDeviceWithWrapperSDKAgents:(nullable NSStringDictionary *)wra } NSString *deviceId = device.id; - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; - components.queryItems = @[ - [NSURLQueryItem queryItemWithName:@"deviceId" value:deviceId], - [NSURLQueryItem queryItemWithName:@"channel" value:self->_channel.name], - ]; + NSMutableDictionary *params = @{ + @"deviceId": deviceId, + @"channel": self->_channel.name, + }.mutableCopy; + + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"DELETE" + path:@"/push/channelSubscriptions" + baseUrl:self->_rest.baseUrl + params:params + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; + } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"DELETE"; [request setDeviceAuthentication:deviceId localDevice:device]; ARTLogDebug(self->_logger, @"unsubscribe notifications for device %@ in channel %@", deviceId, self->_channel.name); @@ -226,14 +257,23 @@ - (void)unsubscribeClientWithWrapperSDKAgents:(nullable NSStringDictionary *)wra return; } - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; - components.queryItems = @[ - [NSURLQueryItem queryItemWithName:@"clientId" value:clientId], - [NSURLQueryItem queryItemWithName:@"channel" value:self->_channel.name], - ]; - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"DELETE"; + NSMutableDictionary *params = @{ + @"clientId": clientId, + @"channel": self->_channel.name, + }.mutableCopy; + + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"DELETE" + path:@"/push/channelSubscriptions" + baseUrl:self->_rest.baseUrl + params:params + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; + } ARTLogDebug(self->_logger, @"unsubscribe notifications for clientId %@ in channel %@", clientId, self->_channel.name); [self->_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:wrapperSDKAgents completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -260,7 +300,7 @@ - (BOOL)listSubscriptions:(NSStringDictionary *)params __block BOOL ret; art_dispatch_sync(_queue, ^{ - NSMutableDictionary *mutableParams = params ? [NSMutableDictionary dictionaryWithDictionary:params] : [[NSMutableDictionary alloc] init]; + NSMutableDictionary *mutableParams = params.mutableCopy ?: [NSMutableDictionary dictionary]; if (!mutableParams[@"deviceId"] && !mutableParams[@"clientId"]) { if (errorPtr) { @@ -283,10 +323,21 @@ - (BOOL)listSubscriptions:(NSStringDictionary *)params mutableParams[@"concatFilters"] = @"true"; - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; - components.queryItems = [mutableParams art_asURLQueryItems]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"GET"; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"GET" + path:@"/push/channelSubscriptions" + baseUrl:nil + params:mutableParams + body:nil + headers:nil + error:&error]; + if (error) { + if (errorPtr) { + *errorPtr = error; + } + ret = NO; + return; + } ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data, NSError **error) { return [self->_rest.encoders[response.MIMEType] decodePushChannelSubscriptions:data error:error]; diff --git a/Source/ARTPushChannelSubscriptions.m b/Source/ARTPushChannelSubscriptions.m index 9dba08979..081b4898f 100644 --- a/Source/ARTPushChannelSubscriptions.m +++ b/Source/ARTPushChannelSubscriptions.m @@ -80,14 +80,26 @@ - (void)save:(ARTPushChannelSubscription *)channelSubscription wrapperSDKAgents: #endif art_dispatch_async(_queue, ^{ - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; + NSMutableDictionary *params = nil; if (self->_rest.options.pushFullWait) { - components.queryItems = @[[NSURLQueryItem queryItemWithName:@"fullWait" value:@"true"]]; + params = @{ + @"fullWait": @"true" + }.mutableCopy; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"POST"; - request.HTTPBody = [[self->_rest defaultEncoder] encodePushChannelSubscription:channelSubscription error:nil]; - [request setValue:[[self->_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + NSData *bodyData = [[self->_rest defaultEncoder] encodePushChannelSubscription:channelSubscription error:nil]; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"POST" + path:@"/push/channelSubscriptions" + baseUrl:self->_rest.baseUrl + params:params + body:bodyData + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; + } + [request setDeviceAuthentication:channelSubscription.deviceId localDevice:local]; ARTLogDebug(self->_logger, @"save channel subscription with request %@", request); @@ -121,9 +133,18 @@ - (void)listChannelsWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapperS } art_dispatch_async(_queue, ^{ - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channels"] resolvingAgainstBaseURL:NO]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"GET"; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"GET" + path:@"/push/channels" + baseUrl:nil + params:nil + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback(nil, [ARTErrorInfo createFromNSError:error]); + return; + } ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data, NSError **error) { return [self->_rest.encoders[response.MIMEType] decode:data error:error]; @@ -143,11 +164,19 @@ - (void)list:(NSStringDictionary *)params wrapperSDKAgents:(nullable NSStringDic } art_dispatch_async(_queue, ^{ - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; - components.queryItems = [params art_asURLQueryItems]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"GET"; - + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"GET" + path:@"/push/channelSubscriptions" + baseUrl:nil + params:params + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback(nil, [ARTErrorInfo createFromNSError:error]); + return; + } + ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data, NSError **error) { return [self->_rest.encoders[response.MIMEType] decodePushChannelSubscriptions:data error:error]; }; @@ -197,13 +226,22 @@ - (void)removeWhere:(NSStringDictionary *)params wrapperSDKAgents:(nullable NSSt } - (void)_removeWhere:(NSStringDictionary *)params wrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents callback:(ARTCallback)callback { - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/channelSubscriptions"] resolvingAgainstBaseURL:NO]; - components.queryItems = [params art_asURLQueryItems]; + NSMutableDictionary *queryParams = [params mutableCopy]; if (_rest.options.pushFullWait) { - components.queryItems = [components.queryItems arrayByAddingObject:[NSURLQueryItem queryItemWithName:@"fullWait" value:@"true"]]; + queryParams[@"fullWait"] = @"true"; + } + NSError *error = nil; + NSMutableURLRequest *request = [_rest buildRequest:@"DELETE" + path:@"/push/channelSubscriptions" + baseUrl:_rest.baseUrl + params:queryParams + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"DELETE"; #if TARGET_OS_IOS [request setDeviceAuthentication:[params objectForKey:@"deviceId"] localDevice:_rest.device_nosync]; #endif diff --git a/Source/ARTPushDeviceRegistrations.m b/Source/ARTPushDeviceRegistrations.m index 3454e5e30..22bb68b14 100644 --- a/Source/ARTPushDeviceRegistrations.m +++ b/Source/ARTPushDeviceRegistrations.m @@ -81,14 +81,26 @@ - (void)save:(ARTDeviceDetails *)deviceDetails wrapperSDKAgents:(nullable NSStri #endif art_dispatch_async(_queue, ^{ - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[[NSURL URLWithString:@"/push/deviceRegistrations"] URLByAppendingPathComponent:deviceDetails.id] resolvingAgainstBaseURL:NO]; + NSMutableDictionary *params = nil; if (self->_rest.options.pushFullWait) { - components.queryItems = @[[NSURLQueryItem queryItemWithName:@"fullWait" value:@"true"]]; + params = @{ + @"fullWait": @"true" + }.mutableCopy; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"PUT"; - request.HTTPBody = [[self->_rest defaultEncoder] encodeDeviceDetails:deviceDetails error:nil]; - [request setValue:[[self->_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; + NSData *bodyData = [[self->_rest defaultEncoder] encodeDeviceDetails:deviceDetails error:nil]; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"PUT" + path:[@"/push/deviceRegistrations" stringByAppendingPathComponent:deviceDetails.id] + baseUrl:self->_rest.baseUrl + params:params + body:bodyData + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; + } + [request setDeviceAuthentication:deviceDetails.id localDevice:local logger:self->_logger]; ARTLogDebug(self->_logger, @"save device with request %@", request); @@ -135,8 +147,19 @@ - (void)get:(ARTDeviceId *)deviceId wrapperSDKAgents:(nullable NSStringDictionar #endif art_dispatch_async(_queue, ^{ - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[[NSURL URLWithString:@"/push/deviceRegistrations"] URLByAppendingPathComponent:deviceId]]; - request.HTTPMethod = @"GET"; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"GET" + path:[@"/push/deviceRegistrations" stringByAppendingPathComponent:deviceId] + baseUrl:self->_rest.baseUrl + params:nil + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback(nil, [ARTErrorInfo createFromNSError:error]); + return; + } + [request setDeviceAuthentication:deviceId localDevice:local logger:self->_logger]; ARTLogDebug(self->_logger, @"get device with request %@", request); @@ -181,10 +204,18 @@ - (void)list:(NSStringDictionary *)params wrapperSDKAgents:(nullable NSStringDic } art_dispatch_async(_queue, ^{ - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; - components.queryItems = [params art_asURLQueryItems]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"GET"; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"GET" + path:@"/push/deviceRegistrations" + baseUrl:self->_rest.baseUrl + params:params + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback(nil, [ARTErrorInfo createFromNSError:error]); + return; + } ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data, NSError **error) { return [self->_rest.encoders[response.MIMEType] decodeDevicesDetails:data error:error]; @@ -204,13 +235,24 @@ - (void)remove:(NSString *)deviceId wrapperSDKAgents:(nullable NSStringDictionar } art_dispatch_async(_queue, ^{ - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[[NSURL URLWithString:@"/push/deviceRegistrations"] URLByAppendingPathComponent:deviceId] resolvingAgainstBaseURL:NO]; - if (self->_rest.options.pushFullWait) { - components.queryItems = @[[NSURLQueryItem queryItemWithName:@"fullWait" value:@"true"]]; + NSMutableDictionary *params = nil; + if (self->_rest.options.pushFullWait) { + params = @{ + @"fullWait": @"true" + }.mutableCopy; + } + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"DELETE" + path:[@"/push/deviceRegistrations" stringByAppendingPathComponent:deviceId] + baseUrl:self->_rest.baseUrl + params:params + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"DELETE"; - [request setValue:[[self->_rest defaultEncoder] mimeType] forHTTPHeaderField:@"Content-Type"]; ARTLogDebug(self->_logger, @"remove device with request %@", request); [self->_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:wrapperSDKAgents completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -248,13 +290,23 @@ - (void)removeWhere:(NSStringDictionary *)params wrapperSDKAgents:(nullable NSSt #endif art_dispatch_async(_queue, ^{ - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"/push/deviceRegistrations"] resolvingAgainstBaseURL:NO]; - components.queryItems = [params art_asURLQueryItems]; + NSMutableDictionary *queryParams = [params mutableCopy]; if (self->_rest.options.pushFullWait) { - components.queryItems = [components.queryItems arrayByAddingObject:[NSURLQueryItem queryItemWithName:@"fullWait" value:@"true"]]; + queryParams[@"fullWait"] = @"true"; + } + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"DELETE" + path:@"/push/deviceRegistrations" + baseUrl:self->_rest.baseUrl + params:queryParams + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"DELETE"; + [request setDeviceAuthentication:[params objectForKey:@"deviceId"] localDevice:local]; ARTLogDebug(self->_logger, @"remove devices with request %@", request); diff --git a/Source/ARTRealtimeChannel.m b/Source/ARTRealtimeChannel.m index 6a829fdcc..26e1d4318 100644 --- a/Source/ARTRealtimeChannel.m +++ b/Source/ARTRealtimeChannel.m @@ -1207,8 +1207,19 @@ - (void)historyWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAge [self history:[[ARTRealtimeHistoryQuery alloc] init] wrapperSDKAgents:wrapperSDKAgents callback:callback error:nil]; } -- (BOOL)history:(ARTRealtimeHistoryQuery *)query wrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents callback:(ARTPaginatedMessagesCallback)callback error:(NSError **)errorPtr { - query.realtimeChannel = self; +- (BOOL)history:(ARTRealtimeHistoryQuery *)query +wrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents + callback:(ARTPaginatedMessagesCallback)callback + error:(NSError **)errorPtr { + if (query.untilAttach) { // RTL10b + if (self.state_nosync != ARTRealtimeChannelAttached) { + if (errorPtr) { + *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain code:ARTRealtimeHistoryErrorNotAttached userInfo:@{NSLocalizedDescriptionKey:@"ARTRealtimeHistoryQuery: untilAttach used in channel that isn't attached"}]; + } + return false; + } + query.realtimeChannelAttachSerial = self.attachSerial; + } return [_restChannel history:query wrapperSDKAgents:wrapperSDKAgents callback:callback error:errorPtr]; } diff --git a/Source/ARTRealtimePresence.m b/Source/ARTRealtimePresence.m index e6a20129e..2211952c2 100644 --- a/Source/ARTRealtimePresence.m +++ b/Source/ARTRealtimePresence.m @@ -279,7 +279,6 @@ - (void)historyWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAge } - (BOOL)history:(ARTRealtimeHistoryQuery *)query wrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents callback:(ARTPaginatedPresenceCallback)callback error:(NSError **)errorPtr { - query.realtimeChannel = _channel; return [_channel.restChannel.presence history:query wrapperSDKAgents:wrapperSDKAgents callback:callback error:errorPtr]; } diff --git a/Source/ARTRest.m b/Source/ARTRest.m index 35d4862c4..f649432ad 100644 --- a/Source/ARTRest.m +++ b/Source/ARTRest.m @@ -495,7 +495,9 @@ - (NSString *)agentIdentifierWithWrapperSDKAgents:(nullable NSDictionary *)_timeWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents completion:(ARTDateTimeCallback)callback { - NSURL *requestUrl = [NSURL URLWithString:@"/time" relativeToURL:self.baseUrl]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestUrl]; - request.HTTPMethod = @"GET"; - NSString *accept = [[_encoders.allValues valueForKeyPath:@"mimeType"] componentsJoinedByString:@","]; - [request setValue:accept forHTTPHeaderField:@"Accept"]; + NSError *error = nil; + NSMutableURLRequest *request = [self buildRequest:@"GET" + path:@"/time" + baseUrl:self.baseUrl + params:nil + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback(nil, error); + return nil; + } return [self executeAblyRequest:request withAuthOption:ARTAuthenticationOff wrapperSDKAgents:wrapperSDKAgents completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (error) { @@ -631,58 +640,45 @@ - (void)timeWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents }]; } -- (BOOL)request:(NSString *)method - path:(NSString *)path - params:(nullable NSStringDictionary *)params - body:(nullable id)body - headers:(nullable NSStringDictionary *)headers -wrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents - callback:(ARTHTTPPaginatedCallback)callback - error:(NSError **)errorPtr { - - if (callback) { - void (^userCallback)(ARTHTTPPaginatedResponse *, ARTErrorInfo *) = callback; - callback = ^(ARTHTTPPaginatedResponse *r, ARTErrorInfo *e) { - art_dispatch_async(self->_userQueue, ^{ - userCallback(r, e); - }); - }; - } - - if (![[method lowercaseString] isEqualToString:@"get"] && - ![[method lowercaseString] isEqualToString:@"post"] && - ![[method lowercaseString] isEqualToString:@"patch"] && - ![[method lowercaseString] isEqualToString:@"put"] && - ![[method lowercaseString] isEqualToString:@"delete"]) { +- (NSMutableURLRequest * _Nullable)buildRequest:(NSString *)method + path:(NSString *)path + baseUrl:(nullable NSURL *)baseUrl + params:(nullable NSStringDictionary *)params + body:(nullable id)body + headers:(nullable NSStringDictionary *)headers + error:(NSError *_Nullable *_Nullable)errorPtr { + if (![@[@"get", @"post", @"patch", @"put", @"delete"] containsObject:method.lowercaseString]) { if (errorPtr) { *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain code:ARTCustomRequestErrorInvalidMethod - userInfo:@{NSLocalizedDescriptionKey:@"Method isn't valid."}]; + userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"HTTP method '%@' isn't valid.", method]}]; } - return NO; + return nil; } - if (body && - ![body isKindOfClass:[NSDictionary class]] && - ![body isKindOfClass:[NSArray class]]) { + if (body && ![body isKindOfClass:NSData.class] && ![body isKindOfClass:NSDictionary.class] && ![body isKindOfClass:NSArray.class]) { if (errorPtr) { *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain code:ARTCustomRequestErrorInvalidBody - userInfo:@{NSLocalizedDescriptionKey:@"Body should be a Dictionary or an Array."}]; + userInfo:@{NSLocalizedDescriptionKey:@"Body should be a NSData, NSDictionary or an NSArray."}]; } - return NO; + return nil; } - if ([[path stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] isEqualToString:@""]) { + if ([[path stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet] isEqualToString:@""]) { if (errorPtr) { *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain code:ARTCustomRequestErrorInvalidPath userInfo:@{NSLocalizedDescriptionKey:@"Path cannot be empty."}]; } - return NO; + return nil; + } + + if (baseUrl == nil) { + baseUrl = self.baseUrl; } - NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseUrl]; + NSURL *url = [NSURL URLWithString:path relativeToURL:baseUrl]; // Should not happen in iOS 17 and above. See explanation in the "Important" section here: // https://developer.apple.com/documentation/foundation/nsurl/1572047-urlwithstring if (!url) { @@ -691,7 +687,7 @@ - (BOOL)request:(NSString *)method code:ARTCustomRequestErrorInvalidPath userInfo:@{NSLocalizedDescriptionKey:@"Path isn't valid for an URL."}]; } - return NO; + return nil; } NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:YES]; @@ -705,25 +701,72 @@ - (BOOL)request:(NSString *)method components.queryItems = queryItems; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = method; + request.HTTPMethod = method.uppercaseString; [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { [request addValue:value forHTTPHeaderField:key]; }]; if (body != nil) { - NSError *encodeError = nil; - NSData *bodyData = [self.defaultEncoder encode:body error:&encodeError]; - + NSData *bodyData; + if ([body isKindOfClass:NSData.class]) { + bodyData = body; + } else { + NSError *encodeError = nil; + bodyData = [self.defaultEncoder encode:body error:&encodeError]; + if (encodeError) { + if (errorPtr) { + *errorPtr = encodeError; + } + return nil; + } + } request.HTTPBody = bodyData; - [request setValue:[self.defaultEncoder mimeType] forHTTPHeaderField:@"Content-Type"]; - if ([[method lowercaseString] isEqualToString:@"post"]) { + [request setValue:self.defaultEncoder.mimeType forHTTPHeaderField:@"Content-Type"]; + if ([@[@"post", @"patch", @"put"] containsObject:method.lowercaseString]) { [request setValue:[NSString stringWithFormat:@"%d", (unsigned int)bodyData.length] forHTTPHeaderField:@"Content-Length"]; } } + [request setTimeoutInterval:_options.httpRequestTimeout]; [request setAcceptHeader:self.defaultEncoder encoders:self.encoders]; + return request; +} + +- (BOOL)request:(NSString *)method + path:(NSString *)path + params:(nullable NSStringDictionary *)params + body:(nullable id)body + headers:(nullable NSStringDictionary *)headers +wrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents + callback:(ARTHTTPPaginatedCallback)callback + error:(NSError **)errorPtr { + + if (callback) { + void (^userCallback)(ARTHTTPPaginatedResponse *, ARTErrorInfo *) = callback; + callback = ^(ARTHTTPPaginatedResponse *r, ARTErrorInfo *e) { + art_dispatch_async(self->_userQueue, ^{ + userCallback(r, e); + }); + }; + } + + NSError *error = nil; + NSMutableURLRequest *request = [self buildRequest:method + path:path + baseUrl:self.baseUrl + params:params + body:body + headers:headers + error:&error]; + if (error) { + if (errorPtr) { + *errorPtr = error; + } + return NO; + } + ARTLogDebug(self.logger, @"request %@ %@", method, path); art_dispatch_async(_queue, ^{ [ARTHTTPPaginatedResponse executePaginated:self withRequest:request wrapperSDKAgents:wrapperSDKAgents logger:self.logger callback:callback]; @@ -750,7 +793,6 @@ - (BOOL)statsWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgent completion:(ARTPaginatedStatsCallback)callback { return [self stats:[[ARTStatsQuery alloc] init] wrapperSDKAgents:wrapperSDKAgents callback:callback error:nil]; } - - (BOOL)stats:(ARTStatsQuery *)query wrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents callback:(ARTPaginatedStatsCallback)callback error:(NSError **)errorPtr { if (callback) { ARTPaginatedStatsCallback userCallback = callback; @@ -778,16 +820,20 @@ - (BOOL)stats:(ARTStatsQuery *)query wrapperSDKAgents:(nullable NSStringDictiona return NO; } - NSURLComponents *requestUrl = [NSURLComponents componentsWithString:@"/stats"]; NSError *error = nil; - requestUrl.queryItems = [query asQueryItems:&error]; + NSMutableURLRequest *request = [self buildRequest:@"GET" + path:@"/stats" + baseUrl:self.baseUrl + params:query.asQueryParams + body:nil + headers:nil + error:&error]; if (error) { if (errorPtr) { *errorPtr = error; } return NO; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[requestUrl URLRelativeToURL:self.baseUrl]]; // Override X-Ably-Version header to use protocol version 2 for stats [request setValue:@"2" forHTTPHeaderField:@"X-Ably-Version"]; @@ -933,3 +979,4 @@ - (NSString *)deviceTokenString { } @end + diff --git a/Source/ARTRestAnnotations.m b/Source/ARTRestAnnotations.m index 346003cc6..102a3e0e7 100644 --- a/Source/ARTRestAnnotations.m +++ b/Source/ARTRestAnnotations.m @@ -5,6 +5,7 @@ #import "ARTDataQuery+Private.h" #import "ARTJsonEncoder.h" #import "ARTNSArray+ARTFunctional.h" +#import "ARTNSString+ARTUtil.h" #import "ARTChannel+Private.h" #import "ARTBaseMessage+Private.h" #import "ARTInternalLog.h" @@ -37,12 +38,10 @@ - (instancetype)initWithLimit:(NSUInteger)limit { return self; } -- (NSMutableArray *)asQueryItems { - NSMutableArray *items = [NSMutableArray array]; - - [items addObject:[NSURLQueryItem queryItemWithName:@"limit" value:[NSString stringWithFormat:@"%lu", (unsigned long)self.limit]]]; - - return items; +- (NSStringDictionary *)asQueryParams { + NSMutableDictionary *params = [NSMutableDictionary dictionary]; + params[@"limit"] = [NSString stringWithFormat:@"%lu", (unsigned long)self.limit]; + return params; } @end @@ -187,15 +186,19 @@ - (void)publishAnnotation:(ARTOutboundAnnotation *)outboundAnnotation } // Construct URL for the annotation endpoint - NSString *path = [NSString stringWithFormat:@"%@/messages/%@/annotations", [self->_channel getBasePath], [messageSerial stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]]; + NSString *path = [NSString stringWithFormat:@"%@/messages/%@/annotations", [self->_channel getBasePath], [messageSerial encodePathSegment]]; - NSURL *url = [NSURL URLWithString:path]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; - request.HTTPMethod = @"POST"; - request.HTTPBody = encodedAnnotation; - - if (self->_channel.rest.defaultEncoding) { - [request setValue:self->_channel.rest.defaultEncoding forHTTPHeaderField:@"Content-Type"]; + NSError *error = nil; + NSMutableURLRequest *request = [self->_channel.rest buildRequest:@"POST" + path:path + baseUrl:nil + params:nil + body:encodedAnnotation + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; } ARTLogDebug(self->_logger, @"RS:%p CH:%p (%@) publish annotation %@", @@ -245,20 +248,21 @@ - (void)getForMessageSerial:(NSString *)messageSerial query:(ARTAnnotationsQuery } art_dispatch_async(_queue, ^{ - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString: [NSString stringWithFormat:@"%@/messages/%@/annotations", [self->_channel getBasePath], [messageSerial stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet]]] resolvingAgainstBaseURL:YES]; + NSString *path = [NSString stringWithFormat:@"%@/messages/%@/annotations", [self->_channel getBasePath], [messageSerial encodePathSegment]]; - // Add query parameters - NSMutableArray *queryItems = [NSMutableArray new]; - if (query) { - [queryItems addObjectsFromArray:[query asQueryItems]]; - } - if (queryItems.count > 0) { - components.queryItems = queryItems; + NSError *error = nil; + NSMutableURLRequest *request = [self->_channel.rest buildRequest:@"GET" + path:path + baseUrl:nil + params:[query asQueryParams] + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback(nil, [ARTErrorInfo createFromNSError:error]); + return; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:components.URL]; - request.HTTPMethod = @"GET"; - ARTLogDebug(self->_logger, @"RS:%p CH:%p (%@) get annotations request %@", self->_channel.rest, self->_channel, self->_channel.name, request); diff --git a/Source/ARTRestChannel.m b/Source/ARTRestChannel.m index b6ac49015..29ad93bb2 100644 --- a/Source/ARTRestChannel.m +++ b/Source/ARTRestChannel.m @@ -14,6 +14,8 @@ #import "ARTAuth+Private.h" #import "ARTTokenDetails.h" #import "ARTNSArray+ARTFunctional.h" +#import "ARTNSDictionary+ARTDictionaryUtil.h" +#import "ARTNSString+ARTUtil.h" #import "ARTPushChannel+Private.h" #import "ARTCrypto+Private.h" #import "ARTClientOptions.h" @@ -191,16 +193,13 @@ - (BOOL)history:(ARTDataQuery *)query wrapperSDKAgents:(nullable NSStringDiction }; } - __block BOOL ret; -art_dispatch_sync(_queue, ^{ if (query.limit > 1000) { if (errorPtr) { *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain code:ARTDataQueryErrorLimit userInfo:@{NSLocalizedDescriptionKey:@"Limit supports up to 1000 results only"}]; } - ret = NO; - return; + return NO; } if ([query.start compare:query.end] == NSOrderedDescending) { if (errorPtr) { @@ -208,23 +207,24 @@ - (BOOL)history:(ARTDataQuery *)query wrapperSDKAgents:(nullable NSStringDiction code:ARTDataQueryErrorTimestampRange userInfo:@{NSLocalizedDescriptionKey:@"Start must be equal to or less than end"}]; } - ret = NO; - return; + return NO; } - NSURLComponents *componentsUrl = [NSURLComponents componentsWithString:[self->_basePath stringByAppendingPathComponent:@"messages"]]; NSError *error = nil; - componentsUrl.queryItems = [query asQueryItems:&error]; + NSMutableURLRequest *request = [self->_rest buildRequest:@"GET" + path:[self->_basePath stringByAppendingPathComponent:@"messages"] + baseUrl:nil + params:query.asQueryParams + body:nil + headers:nil + error:&error]; if (error) { if (errorPtr) { *errorPtr = error; } - ret = NO; - return; + return NO; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:componentsUrl.URL]; - ARTPaginatedResultResponseProcessor responseProcessor = ^NSArray *(NSHTTPURLResponse *response, NSData *data, NSError **errorPtr) { id encoder = [self->_rest.encoders objectForKey:response.MIMEType]; return [[encoder decodeMessages:data error:errorPtr] artMap:^(ARTMessage *message) { @@ -237,12 +237,11 @@ - (BOOL)history:(ARTDataQuery *)query wrapperSDKAgents:(nullable NSStringDiction return message; }]; }; - - ARTLogDebug(self.logger, @"RS:%p C:%p (%@) stats request %@", self->_rest, self, self.name, request); + +art_dispatch_sync(_queue, ^{ [ARTPaginatedResult executePaginated:self->_rest withRequest:request andResponseProcessor:responseProcessor wrapperSDKAgents:wrapperSDKAgents logger:self.logger callback:callback]; - ret = YES; }); - return ret; + return YES; } - (void)status:(ARTChannelDetailsCallback)callback { @@ -255,8 +254,18 @@ - (void)status:(ARTChannelDetailsCallback)callback { }; } art_dispatch_async(_queue, ^{ - NSURL *url = [NSURL URLWithString:self->_basePath]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"GET" + path:self->_basePath + baseUrl:nil + params:nil + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback(nil, [ARTErrorInfo createFromNSError:error]); + return; + } ARTLogDebug(self.logger, @"RS:%p C:%p (%@) channel details request %@", self->_rest, self, self.name, request); @@ -317,78 +326,61 @@ - (void)internalPostMessages:(id)data callback:(ARTCallback)callback { } art_dispatch_async(_queue, ^{ - NSData *encodedMessage = nil; + NSArray *messages = nil; + BOOL dataIsArray = NO; - if ([data isKindOfClass:[ARTMessage class]]) { - ARTMessage *message = (ARTMessage *)data; - - NSString *baseId = nil; - if (self.rest.options.idempotentRestPublishing && message.isIdEmpty) { + if ([data isKindOfClass:ARTMessage.class]) { + messages = @[data]; + } + else if ([data isKindOfClass:[NSArray class]]) { + messages = data; + dataIsArray = YES; + } + else { + if (callback) callback([ARTErrorInfo createWithCode:ARTStateInvalidArgs message:@"Unknown data type for publishing messages. Expected ARTMessage or [ARTMessage]."]); + return; + } + + NSString *baseId = nil; + if (self.rest.options.idempotentRestPublishing) { + BOOL messagesHaveEmptyId = [messages artFilter:^BOOL(ARTMessage *m) { return !m.isIdEmpty; }].count <= 0; + if (messagesHaveEmptyId) { NSData *baseIdData = [ARTCrypto generateSecureRandomData:ARTIdempotentLibraryGeneratedIdLength]; baseId = [baseIdData base64EncodedStringWithOptions:0]; - message.id = [NSString stringWithFormat:@"%@:0", baseId]; } - + } + + NSInteger serial = 0; + for (ARTMessage *message in messages) { if (message.clientId && self.rest.auth.clientId_nosync && ![message.clientId isEqualToString:self.rest.auth.clientId_nosync]) { - callback([ARTErrorInfo createWithCode:ARTStateMismatchedClientId message:@"attempted to publish message with an invalid clientId"]); - return; - } - - NSError *encodeError = nil; - encodedMessage = [self.rest.defaultEncoder encodeMessage:message error:&encodeError]; - if (encodeError) { - callback([ARTErrorInfo createFromNSError:encodeError]); + if (callback) callback([ARTErrorInfo createWithCode:ARTStateMismatchedClientId message:@"attempted to publish message with an invalid clientId"]); return; } - } - else if ([data isKindOfClass:[NSArray class]]) { - NSArray *messages = (NSArray *)data; - - NSString *baseId = nil; - if (self.rest.options.idempotentRestPublishing) { - BOOL messagesHaveEmptyId = [messages artFilter:^BOOL(ARTMessage *m) { return !m.isIdEmpty; }].count <= 0; - if (messagesHaveEmptyId) { - NSData *baseIdData = [ARTCrypto generateSecureRandomData:ARTIdempotentLibraryGeneratedIdLength]; - baseId = [baseIdData base64EncodedStringWithOptions:0]; - } - } - - NSInteger serial = 0; - for (ARTMessage *message in messages) { - if (message.clientId && self.rest.auth.clientId_nosync && ![message.clientId isEqualToString:self.rest.auth.clientId_nosync]) { - callback([ARTErrorInfo createWithCode:ARTStateMismatchedClientId message:@"attempted to publish message with an invalid clientId"]); - return; - } - if (baseId) { - message.id = [NSString stringWithFormat:@"%@:%ld", baseId, (long)serial]; - } - serial += 1; - } - - NSError *encodeError = nil; - encodedMessage = [self.rest.defaultEncoder encodeMessages:data error:&encodeError]; - if (encodeError) { - callback([ARTErrorInfo createFromNSError:encodeError]); - return; + if (baseId) { + message.id = [NSString stringWithFormat:@"%@:%ld", baseId, (long)serial]; } + serial += 1; } - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:[self->_basePath stringByAppendingPathComponent:@"messages"]] resolvingAgainstBaseURL:YES]; - NSMutableArray *queryItems = [NSMutableArray new]; + NSArray *jsonMessages = [self.rest.defaultEncoder messagesToArray:messages]; - if (queryItems.count > 0) { - components.queryItems = queryItems; - } - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"POST"; - request.HTTPBody = encodedMessage; + // unwrap single item array back to the object, because many tests rely on that + id bodyData = jsonMessages.count > 1 || dataIsArray ? jsonMessages : jsonMessages.firstObject; - if (self.rest.defaultEncoding) { - [request setValue:self.rest.defaultEncoding forHTTPHeaderField:@"Content-Type"]; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"POST" + path:[self->_basePath stringByAppendingPathComponent:@"messages"] + baseUrl:nil + params:nil + body:bodyData + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; } - - ARTLogDebug(self.logger, @"RS:%p C:%p (%@) post message %@", self->_rest, self, self.name, [[NSString alloc] initWithData:encodedMessage ?: [NSData data] encoding:NSUTF8StringEncoding]); + + ARTLogDebug(self.logger, @"RS:%p C:%p (%@) post message(s):\n%@", self->_rest, self, self.name, jsonMessages); [self->_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:nil completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (callback) { @@ -469,14 +461,6 @@ - (void)_updateMessage:(ARTMessage *)message requestBody[@"extras"] = message.extras; } - // Encode the request body - NSError *encodeError = nil; - NSData *requestBodyData = [self.rest.defaultEncoder encode:requestBody error:&encodeError]; - if (encodeError) { - if (callback) callback([ARTErrorInfo createFromNSError:encodeError]); - return; - } - // RSL12b - PATCH to /channels/{channelName}/messages/{serial} // RSL13b - POST to /channels/{channelName}/messages/{serial}/delete NSString *messagePath; @@ -484,35 +468,34 @@ - (void)_updateMessage:(ARTMessage *)message if (isDelete) { // RSL13b - POST to /channels/{channelName}/messages/{serial}/delete - messagePath = [NSString stringWithFormat:@"messages/%@/delete", [message.serial stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]]; + messagePath = [NSString stringWithFormat:@"%@/messages/%@/delete", self->_basePath, [message.serial encodePathSegment]]; httpMethod = @"POST"; } else { // RSL12b - PATCH to /channels/{channelName}/messages/{serial} - messagePath = [NSString stringWithFormat:@"messages/%@", [message.serial stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]]; + messagePath = [NSString stringWithFormat:@"%@/messages/%@", self->_basePath, [message.serial encodePathSegment]]; httpMethod = @"PATCH"; } - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:[self->_basePath stringByAppendingPathComponent:messagePath]] resolvingAgainstBaseURL:YES]; - // RSL12a/RSL13a - params in querystring - if (params && params.count > 0) { - NSMutableArray *queryItems = [NSMutableArray array]; - for (NSString *key in params) { - [queryItems addObject:[NSURLQueryItem queryItemWithName:key value:params[key].stringValue]]; - } - components.queryItems = queryItems; - } - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = httpMethod; - request.HTTPBody = requestBodyData; + NSStringDictionary *queryParams = [params artMap:^id(NSString *key, ARTStringifiable *value) { + return value.stringValue; + }]; - if (self.rest.defaultEncoding) { - [request setValue:self.rest.defaultEncoding forHTTPHeaderField:@"Content-Type"]; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:httpMethod + path:messagePath + baseUrl:nil + params:queryParams + body:requestBody + headers:nil + error:&error]; + if (error) { + if (callback) callback([ARTErrorInfo createFromNSError:error]); + return; } NSString *logOperation = isDelete ? @"delete" : @"update"; - ARTLogDebug(self.logger, @"RS:%p C:%p (%@) %@ message %@", self->_rest, self, self.name, logOperation, [[NSString alloc] initWithData:requestBodyData ?: [NSData data] encoding:NSUTF8StringEncoding]); + ARTLogDebug(self.logger, @"RS:%p C:%p (%@) %@ message:\n%@", self->_rest, self, self.name, logOperation, requestBody); [self->_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:wrapperSDKAgents completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { if (callback) { @@ -561,12 +544,21 @@ - (void)getMessageWithSerial:(NSString *)serial } art_dispatch_async(_queue, ^{ - NSString *messagePath = [NSString stringWithFormat:@"messages/%@", [serial stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]]; - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:[self->_basePath stringByAppendingPathComponent:messagePath]] resolvingAgainstBaseURL:YES]; - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"GET"; + NSString *messagePath = [NSString stringWithFormat:@"%@/messages/%@", self->_basePath, [serial encodePathSegment]]; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"GET" + path:messagePath + baseUrl:nil + params:nil + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback(nil, [ARTErrorInfo createFromNSError:error]); + return; + } + ARTLogDebug(self.logger, @"RS:%p C:%p (%@) get message with serial %@", self->_rest, self, self.name, serial); [self->_rest executeAblyRequest:request withAuthOption:ARTAuthenticationOn wrapperSDKAgents:wrapperSDKAgents completion:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { @@ -636,11 +628,20 @@ - (void)getMessageVersionsWithSerial:(NSString *)serial } art_dispatch_async(_queue, ^{ - NSString *messagePath = [NSString stringWithFormat:@"messages/%@/versions", [serial stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]]; - NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:[self->_basePath stringByAppendingPathComponent:messagePath]] resolvingAgainstBaseURL:YES]; + NSString *path = [NSString stringWithFormat:@"%@/messages/%@/versions", self->_basePath, [serial encodePathSegment]]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[components URL]]; - request.HTTPMethod = @"GET"; + NSError *error = nil; + NSMutableURLRequest *request = [self->_rest buildRequest:@"GET" + path:path + baseUrl:nil + params:nil + body:nil + headers:nil + error:&error]; + if (error) { + if (callback) callback(nil, [ARTErrorInfo createFromNSError:error]); + return; + } ARTLogDebug(self.logger, @"RS:%p C:%p (%@) get message versions for serial %@", self->_rest, self, self.name, serial); diff --git a/Source/ARTRestPresence.m b/Source/ARTRestPresence.m index 06bd53764..71183603d 100644 --- a/Source/ARTRestPresence.m +++ b/Source/ARTRestPresence.m @@ -35,17 +35,17 @@ - (instancetype)initWithLimit:(NSUInteger)limit clientId:(NSString *)clientId co return self; } -- (NSMutableArray *)asQueryItems { - NSMutableArray *items = [NSMutableArray array]; +- (NSStringDictionary *)asQueryParams { + NSMutableDictionary *items = [NSMutableDictionary dictionary]; if (self.clientId) { - [items addObject:[NSURLQueryItem queryItemWithName:@"clientId" value:self.clientId]]; + items[@"clientId"] = self.clientId; } if (self.connectionId) { - [items addObject:[NSURLQueryItem queryItemWithName:@"connectionId" value:self.connectionId]]; + items[@"connectionId"] = self.connectionId; } - - [items addObject:[NSURLQueryItem queryItemWithName:@"limit" value:[NSString stringWithFormat:@"%lu", (unsigned long)self.limit]]]; + + items[@"limit"] = [NSString stringWithFormat:@"%lu", (unsigned long)self.limit]; return items; } @@ -139,10 +139,21 @@ - (BOOL)get:(ARTPresenceQuery *)query callback:(ARTPaginatedPresenceCallback)cal } return NO; } - - NSURLComponents *requestUrl = [NSURLComponents componentsWithString:[_channel.basePath stringByAppendingPathComponent:@"presence"]]; - requestUrl.queryItems = [query asQueryItems]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestUrl.URL]; + + NSError *error = nil; + NSMutableURLRequest *request = [self->_channel.rest buildRequest:@"GET" + path:[_channel.basePath stringByAppendingPathComponent:@"presence"] + baseUrl:nil + params:query.asQueryParams + body:nil + headers:nil + error:&error]; + if (error) { + if (errorPtr) { + *errorPtr = error; + } + return NO; + } ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data, NSError **errorPtr) { id encoder = [self->_channel.rest.encoders objectForKey:response.MIMEType]; @@ -194,16 +205,20 @@ - (BOOL)history:(ARTDataQuery *)query wrapperSDKAgents:(nullable NSStringDiction return NO; } - NSURLComponents *requestUrl = [NSURLComponents componentsWithString:[_channel.basePath stringByAppendingPathComponent:@"presence/history"]]; NSError *error = nil; - requestUrl.queryItems = [query asQueryItems:&error]; + NSMutableURLRequest *request = [self->_channel.rest buildRequest:@"GET" + path:[_channel.basePath stringByAppendingPathComponent:@"presence/history"] + baseUrl:nil + params:query.asQueryParams + body:nil + headers:nil + error:&error]; if (error) { if (errorPtr) { *errorPtr = error; } return NO; } - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestUrl.URL]; ARTPaginatedResultResponseProcessor responseProcessor = ^(NSHTTPURLResponse *response, NSData *data, NSError **errorPtr) { id encoder = [self->_channel.rest.encoders objectForKey:response.MIMEType]; @@ -225,3 +240,4 @@ - (BOOL)history:(ARTDataQuery *)query wrapperSDKAgents:(nullable NSStringDiction } @end + diff --git a/Source/ARTStats.m b/Source/ARTStats.m index 0383ca163..c5f0cb346 100644 --- a/Source/ARTStats.m +++ b/Source/ARTStats.m @@ -25,12 +25,9 @@ - (instancetype)init { } } -- (NSMutableArray *)asQueryItems:(NSError **)error { - NSMutableArray *items = [super asQueryItems:error]; - if (*error) { - return nil; - } - [items addObject:[NSURLQueryItem queryItemWithName:@"unit" value:statsUnitToString(self.unit)]]; +- (NSStringDictionary *)asQueryParams { + NSMutableDictionary *items = (NSMutableDictionary *)[super asQueryParams].mutableCopy; + items[@"unit"] = statsUnitToString(self.unit); return items; } diff --git a/Source/ARTTypes.m b/Source/ARTTypes.m index ec895744c..e68bbbba0 100644 --- a/Source/ARTTypes.m +++ b/Source/ARTTypes.m @@ -323,21 +323,6 @@ - (NSString *)description { } } -@implementation NSDictionary (ARTURLQueryItemAdditions) - -- (NSArray *)art_asURLQueryItems { - NSMutableArray *items = [NSMutableArray new]; - for (id key in [self allKeys]) { - id value = [self valueForKey:key]; - if ([key isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) { - [items addObject:[NSURLQueryItem queryItemWithName:key value:value]]; - } - } - return items; -} - -@end - @implementation NSMutableArray (ARTQueueAdditions) - (void)art_enqueue:(id)object { diff --git a/Source/PrivateHeaders/Ably/ARTDataQuery+Private.h b/Source/PrivateHeaders/Ably/ARTDataQuery+Private.h index 1c2c55fe6..4af44141f 100755 --- a/Source/PrivateHeaders/Ably/ARTDataQuery+Private.h +++ b/Source/PrivateHeaders/Ably/ARTDataQuery+Private.h @@ -5,13 +5,13 @@ NS_ASSUME_NONNULL_BEGIN @interface ARTDataQuery (Private) -- (nullable NSMutableArray /* */ *)asQueryItems:(NSError *_Nullable *)error; +- (NSStringDictionary *)asQueryParams; @end @interface ARTRealtimeHistoryQuery () -@property (readwrite) ARTRealtimeChannelInternal *realtimeChannel; +@property (readwrite, copy) NSString *realtimeChannelAttachSerial; @end diff --git a/Source/PrivateHeaders/Ably/ARTEncoder.h b/Source/PrivateHeaders/Ably/ARTEncoder.h index 8295c851b..0b6108426 100644 --- a/Source/PrivateHeaders/Ably/ARTEncoder.h +++ b/Source/PrivateHeaders/Ably/ARTEncoder.h @@ -48,6 +48,7 @@ NS_ASSUME_NONNULL_BEGIN // Message - (nullable NSData *)encodeMessage:(ARTMessage *)message error:(NSError *_Nullable *_Nullable)error; - (nullable ARTMessage *)decodeMessage:(NSData *)data error:(NSError *_Nullable *_Nullable)error; +- (NSDictionary *)messageToDictionary:(ARTMessage *)message; // Annotation - (nullable NSData *)encodeAnnotation:(ARTAnnotation *)annotation error:(NSError *_Nullable *_Nullable)error; @@ -56,6 +57,7 @@ NS_ASSUME_NONNULL_BEGIN // Message list - (nullable NSData *)encodeMessages:(NSArray *)messages error:(NSError *_Nullable *_Nullable)error; - (nullable NSArray *)decodeMessages:(NSData *)data error:(NSError *_Nullable *_Nullable)error; +- (NSArray *)messagesToArray:(NSArray *)messages; // Annotation list - (nullable NSData *)encodeAnnotations:(NSArray *)annotations error:(NSError *_Nullable *_Nullable)error; diff --git a/Source/PrivateHeaders/Ably/ARTNSString+ARTUtil.h b/Source/PrivateHeaders/Ably/ARTNSString+ARTUtil.h index 515e5797a..049e18ac7 100644 --- a/Source/PrivateHeaders/Ably/ARTNSString+ARTUtil.h +++ b/Source/PrivateHeaders/Ably/ARTNSString+ARTUtil.h @@ -7,5 +7,6 @@ + (NSString *)nilToEmpty:(NSString*)aString; - (BOOL)isEmptyString; - (BOOL)isNotEmptyString; +- (NSString *)encodePathSegment; @end diff --git a/Source/PrivateHeaders/Ably/ARTPresence+Private.h b/Source/PrivateHeaders/Ably/ARTPresence+Private.h index 2ed287257..ed2e765a1 100644 --- a/Source/PrivateHeaders/Ably/ARTPresence+Private.h +++ b/Source/PrivateHeaders/Ably/ARTPresence+Private.h @@ -1,12 +1,6 @@ #import #import "ARTChannel.h" -@interface ARTPresenceQuery () - -- (NSMutableArray *)asQueryItems; - -@end - @interface ARTPresence () @property (readonly, getter=getChannel) ARTChannel *channel; diff --git a/Source/PrivateHeaders/Ably/ARTRest+Private.h b/Source/PrivateHeaders/Ably/ARTRest+Private.h index 2cbb66500..b45c840b7 100644 --- a/Source/PrivateHeaders/Ably/ARTRest+Private.h +++ b/Source/PrivateHeaders/Ably/ARTRest+Private.h @@ -100,6 +100,14 @@ NS_ASSUME_NONNULL_BEGIN - (void)timeWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents completion:(ARTDateTimeCallback)callback; +- (NSMutableURLRequest * _Nullable)buildRequest:(NSString *)method + path:(NSString *)path + baseUrl:(nullable NSURL *)baseUrl + params:(nullable NSStringDictionary *)params + body:(nullable id)body + headers:(nullable NSStringDictionary *)headers + error:(NSError *_Nullable *_Nullable)errorPtr; + - (BOOL)request:(NSString *)method path:(NSString *)path params:(nullable NSStringDictionary *)params diff --git a/Source/include/Ably/ARTTypes.h b/Source/include/Ably/ARTTypes.h index 756decff3..1538d4cdf 100644 --- a/Source/include/Ably/ARTTypes.h +++ b/Source/include/Ably/ARTTypes.h @@ -188,7 +188,7 @@ typedef NS_ENUM(NSInteger, ARTDataQueryError) { /// :nodoc: NS_SWIFT_SENDABLE typedef NS_ENUM(NSInteger, ARTRealtimeHistoryError) { - ARTRealtimeHistoryErrorNotAttached = ARTDataQueryErrorTimestampRange + 1 + ARTRealtimeHistoryErrorNotAttached = ARTDataQueryErrorTimestampRange + 1 // TODO: overlaps with ARTDataQueryErrorMissingRequiredFields }; /// :nodoc: @@ -467,11 +467,6 @@ NS_SWIFT_SENDABLE @interface NSURL (ARTLog) @end -/// :nodoc: -@interface NSDictionary (ARTURLQueryItemAdditions) -@property (nonatomic, readonly) NSArray *art_asURLQueryItems; -@end - /// :nodoc: @interface NSMutableArray (ARTQueueAdditions) - (void)art_enqueue:(id)object; @@ -485,6 +480,7 @@ NS_SWIFT_SENDABLE /// :nodoc: typedef NSDictionary NSStringDictionary; +typedef NSMutableDictionary NSMutableStringDictionary; // Below are the typedefs of completion handlers to improve readability and maintainability in properties and method parameters. // Either result/response or error can be nil but not both. diff --git a/Test/AblyTests/Tests/RestClientTests.swift b/Test/AblyTests/Tests/RestClientTests.swift index 8d1002aba..bc3686d01 100644 --- a/Test/AblyTests/Tests/RestClientTests.swift +++ b/Test/AblyTests/Tests/RestClientTests.swift @@ -1907,7 +1907,7 @@ class RestClientTests: XCTestCase { } } catch let error as NSError { XCTAssertEqual(error.code, ARTCustomRequestError.invalidMethod.rawValue) - expect(error.localizedDescription).to(contain("Method isn't valid")) + expect(error.localizedDescription).to(contain("HTTP method 'A' isn't valid")) } do { @@ -1916,7 +1916,7 @@ class RestClientTests: XCTestCase { } } catch let error as NSError { XCTAssertEqual(error.code, ARTCustomRequestError.invalidMethod.rawValue) - expect(error.localizedDescription).to(contain("Method isn't valid")) + expect(error.localizedDescription).to(contain("HTTP method '' isn't valid")) } } @@ -1946,7 +1946,7 @@ class RestClientTests: XCTestCase { } } catch let error as NSError { XCTAssertEqual(error.code, ARTCustomRequestError.invalidBody.rawValue) - expect(error.localizedDescription).to(contain("should be a Dictionary or an Array")) + expect(error.localizedDescription).to(contain("should be a NSData, NSDictionary or an NSArray")) } }