Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### Version: 3.16.0
#### Date: Sep-22-2025

##### Feature:
- Added support for AU and GCP-EU regions

### Version: 3.15.0
#### Date: Jun-16-2025

Expand Down
2 changes: 1 addition & 1 deletion Contentstack.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'Contentstack'
s.version = '3.15.0'
s.version = '3.16.0'
s.summary = 'Contentstack is a headless CMS with an API-first approach that puts content at the centre.'

s.description = <<-DESC
Expand Down
6 changes: 3 additions & 3 deletions Contentstack/Config.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ -(instancetype)init {
}
- (void)setRegion:(ContentstackRegion)region {
_region = region;
if ([[self hostURLS] containsObject:_host]) {
_host = [self hostURL:_region];
}
// Always update host when region changes and notify observers
_host = [self hostURL:_region]; // Update host based on region
}

- (NSDictionary<NSString *, NSString *> *)earlyAccessHeaders {
if (_setEarlyAccess.count > 0) {
NSString *earlyAccessString = [_setEarlyAccess componentsJoinedByString:@","];
Expand Down
4 changes: 3 additions & 1 deletion Contentstack/ContentstackDefinitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,11 @@
typedef NS_ENUM(NSUInteger, ContentstackRegion){
US = 0,
EU,
AU,
AZURE_NA,
AZURE_EU,
GCP_NA
GCP_NA,
GCP_EU
};

typedef NS_ENUM(NSUInteger, Language) {
Expand Down
6 changes: 6 additions & 0 deletions Contentstack/Stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ BUILT_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, copy, readonly) Config *config;

/**
* Readonly property to get the current host URL for the stack.
* This value is derived from the config's host and updates automatically when the region changes.
*/
@property (nonatomic, copy, readonly) NSString *hostURL;

- (instancetype)init UNAVAILABLE_ATTRIBUTE;

//MARK: - ContentType
Expand Down
21 changes: 9 additions & 12 deletions Contentstack/Stack.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,19 @@ @interface Stack ()
@property (nonatomic, copy) NSString *apiKey;
@property (nonatomic, copy) NSString *accessToken;
@property (nonatomic, copy) Config *config;

@property (nonatomic, copy) NSString *environment;

@end

@implementation Stack

- (NSString *)hostURL {
return self.config.host;
}

- (instancetype)initWithAPIKey:(NSString*)apiKey andaccessToken:(NSString *)accessToken andEnvironment:(NSString*)environment andConfig:(Config *)sConfig {
if (self = [super init]) {
_config = sConfig;

_hostURL = [sConfig.host copy];
if (_config.region != US) {
_hostURL = [NSString stringWithFormat:@"%@-%@", [self regionCode:_config.region], sConfig.host];
}
_version = [sConfig.version copy];
_environment = [environment copy];

Expand All @@ -49,19 +47,18 @@ - (instancetype)initWithAPIKey:(NSString*)apiKey andaccessToken:(NSString *)acce

_requestOperationSet = [NSMutableSet set];
// Add early access headers only if they exist
NSDictionary *earlyAccessHeaders = [_config earlyAccessHeaders];
if (earlyAccessHeaders.count > 0) {
[_stackHeaders addEntriesFromDictionary:earlyAccessHeaders];
}
NSDictionary *earlyAccessHeaders = [_config earlyAccessHeaders];
if (earlyAccessHeaders.count > 0) {
[_stackHeaders addEntriesFromDictionary:earlyAccessHeaders];
}


[self setHeader:_apiKey forKey:kCSIO_SiteApiKey];
[self setHeader:_accessToken forKey:kCSIO_Accesstoken];

}
return self;
}


//MARK: - Get ContentTypes
-(void)getContentTypes:(NSDictionary<NSString *,id> *)params completion:(void (^)(NSArray<NSString *> * _Nullable, NSError * _Nullable))completionBlock {
NSString *path = [CSIOAPIURLs fetchSchemaWithVersion:self.version];
Expand Down
17 changes: 11 additions & 6 deletions ContentstackInternal/NSObject+Extensions.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ -(NSArray*)hostURLS {
static NSArray* hostURLS;
static dispatch_once_t hostURLSOnceToken;
dispatch_once(&hostURLSOnceToken, ^{
hostURLS = @[@"cdn.contentstack.io",
@"cdn.contentstack.com",
@"cdn.contentstack.com",
@"cdn.contentstack.com",
@"cdn.contentstack.com"];
hostURLS = @[@"cdn.contentstack.io", // US
@"eu-cdn.contentstack.com", // EU
@"au-cdn.contentstack.com", // AU
@"azure-na-cdn.contentstack.com", // Azure NA
@"azure-eu-cdn.contentstack.com", // Azure EU
@"gcp-na-cdn.contentstack.com", // GCP NA
@"gcp-eu-cdn.contentstack.com"]; // GCP EU
});
return hostURLS;
}
Expand All @@ -61,9 +63,12 @@ -(NSArray*)regionCodes {
dispatch_once(&regionCodesOnceToken, ^{
regionCodes = @[@"us",
@"eu",
@"au",
@"azure-na",
@"azure-eu",
@"gcp-na"];
@"gcp-na",
@"gcp-eu"
];
});
return regionCodes;
}
Expand Down
100 changes: 96 additions & 4 deletions ContentstackTest/ContentstackTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ - (void)testFetchEntryEqualToField {

ContentType* csForm = [csStack contentTypeWithName:@"source"];
Query* csQuery = [csForm query];
__block NSString *objectValue = @"source";
__block NSString *objectValue = @"source1";
[csQuery whereKey:@"title" equalTo:objectValue];

[csQuery find:^(ResponseType type, QueryResult *result, NSError *error) {
Expand Down Expand Up @@ -2013,7 +2013,7 @@ - (void)testFetchTags {
ContentType* csForm = [csStack contentTypeWithName:@"source"];

Query* csQuery = [csForm query];
__block NSMutableArray *tags = [NSMutableArray arrayWithArray:@[@"tags1",@"tags2"]];
__block NSMutableArray *tags = [NSMutableArray arrayWithArray:@[@"tag1",@"tag2"]];
[csQuery tags:tags];
[csQuery find:^(ResponseType type, QueryResult *result, NSError *error) {

Expand Down Expand Up @@ -2122,7 +2122,7 @@ - (void)testFetchSkipEntries {

ContentType* csForm = [csStack contentTypeWithName:@"source"];

__block NSInteger skipObject = 4;
__block NSInteger skipObject = 1;
Query* csQuery = [csForm query];
[csQuery includeCount];
[csQuery skipObjects:@(skipObject)];
Expand All @@ -2133,7 +2133,7 @@ - (void)testFetchSkipEntries {
XCTFail(@"~ ERR: %@", error.userInfo);
} else {

XCTAssertTrue(([result totalCount]-skipObject) <= [result getResult].count, "query should skip 4 objects");
XCTAssertTrue(([result totalCount]-skipObject) <= [result getResult].count, "query should skip 1 objects");
}

[expectation fulfill];
Expand Down Expand Up @@ -2545,4 +2545,96 @@ - (void)testVariantHeaders {
}];
}

#pragma mark - Region Support Tests

- (void)testDefaultRegion {
XCTestExpectation *expectation = [self expectationWithDescription:@"DefaultRegionTest"];
config = [[Config alloc] init];
Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config];

// Verify default region is US
XCTAssertEqual(config.region, US, @"Default region should be US");
XCTAssertEqualObjects(config.host, @"cdn.contentstack.io", @"Config host should be US region");

[expectation fulfill];
[self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil];
}

- (void)testAllRegionsSupport {
XCTestExpectation *expectation = [self expectationWithDescription:@"AllRegionsTest"];
config = [[Config alloc] init];
Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config];

// Test all regions
NSDictionary *regionHosts = @{
@(US): @"cdn.contentstack.io",
@(EU): @"eu-cdn.contentstack.com",
@(AU): @"au-cdn.contentstack.com",
@(AZURE_NA): @"azure-na-cdn.contentstack.com",
@(AZURE_EU): @"azure-eu-cdn.contentstack.com",
@(GCP_NA): @"gcp-na-cdn.contentstack.com",
@(GCP_EU): @"gcp-eu-cdn.contentstack.com"
};

[regionHosts enumerateKeysAndObjectsUsingBlock:^(NSNumber *region, NSString *expectedHost, BOOL *stop) {
[config setRegion:region.intValue];
XCTAssertEqualObjects(config.host, expectedHost,
@"Config host should be updated for region %@", region);
}];

[expectation fulfill];
[self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil];
}

- (void)testCustomHostOverride {
XCTestExpectation *expectation = [self expectationWithDescription:@"CustomHostTest"];
config = [[Config alloc] init];
Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config];

// Set custom host
NSString *customHost = @"custom.contentstack.com";
config.host = customHost;
XCTAssertEqualObjects(config.host, customHost, @"Config should use custom host");

// Verify region change overrides custom host
[config setRegion:AU];
XCTAssertEqualObjects(config.host, @"au-cdn.contentstack.com", @"Region change should override custom host");

[expectation fulfill];
[self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil];
}

- (void)testRegionChangeReflection {
XCTestExpectation *expectation = [self expectationWithDescription:@"RegionChangeTest"];
config = [[Config alloc] init];
Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config];

// Change regions multiple times
[config setRegion:EU];
XCTAssertEqualObjects(config.host, @"eu-cdn.contentstack.com", @"Config should update to EU host");

[config setRegion:AU];
XCTAssertEqualObjects(config.host, @"au-cdn.contentstack.com", @"Config should update to AU host");

[config setRegion:US];
XCTAssertEqualObjects(config.host, @"cdn.contentstack.io", @"Config should update back to US host");

[expectation fulfill];
[self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil];
}

- (void)testAURegion {
XCTestExpectation *expectation = [self expectationWithDescription:@"DefaultRegionTest"];
config = [[Config alloc] init];
[config setRegion:AU];
Stack *stack = [Contentstack stackWithAPIKey:@"api_key" accessToken:@"delivery_token" environmentName:@"environment" config:config];

// Verify default region is US
XCTAssertEqual(config.region, AU, @"Default region should be AU");
XCTAssertEqualObjects(config.host, @"au-cdn.contentstack.com", @"Config host should be AU region");

[expectation fulfill];
[self waitForExpectationsWithTimeout:kRequestTimeOutInSeconds handler:nil];
}

@end