From d7600f58898b3980d56cb4b58c97245b93e0dc5c Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Wed, 11 Dec 2024 19:18:12 +0100 Subject: [PATCH 01/13] feat: migrate QueueITLib to Swift - Modified .gitignore and Package.swift - Deleted old Objective-C files from QueueITLib - Added new Swift files to Sources/QueueITLib --- .gitignore | 3 +- Package.swift | 10 +- QueueITLib/IOSUtils.h | 12 - QueueITLib/IOSUtils.m | 55 ---- QueueITLib/PrivacyInfo.xcprivacy | 35 --- QueueITLib/QueueConsts.h | 8 - QueueITLib/QueueDisabledInfo.h | 9 - QueueITLib/QueueDisabledInfo.m | 14 - QueueITLib/QueueITApiClient.h | 24 -- QueueITLib/QueueITApiClient.m | 118 ------- QueueITLib/QueueITApiClient_NSURLConnection.h | 6 - QueueITLib/QueueITApiClient_NSURLConnection.m | 46 --- .../QueueITApiClient_NSURLConnectionRequest.h | 20 -- .../QueueITApiClient_NSURLConnectionRequest.m | 133 -------- QueueITLib/QueueITEngine.h | 98 ------ QueueITLib/QueueITEngine.m | 125 -------- QueueITLib/QueueITReachability.h | 100 ------ QueueITLib/QueueITReachability.m | 297 ------------------ QueueITLib/QueueITWKViewController.h | 30 -- QueueITLib/QueueITWKViewController.m | 235 -------------- QueueITLib/QueueITWaitingRoomProvider.h | 34 -- QueueITLib/QueueITWaitingRoomProvider.m | 207 ------------ QueueITLib/QueueITWaitingRoomView.h | 29 -- QueueITLib/QueueITWaitingRoomView.m | 100 ------ QueueITLib/QueuePassedInfo.h | 9 - QueueITLib/QueuePassedInfo.m | 14 - QueueITLib/QueueStatus.h | 12 - QueueITLib/QueueStatus.m | 59 ---- QueueITLib/QueueTryPassResult.h | 19 -- QueueITLib/QueueTryPassResult.m | 24 -- Sources/QueueITLib/NewConnection.swift | 44 +++ Sources/QueueITLib/NewConnectionRequest.swift | 97 ++++++ Sources/QueueITLib/NewIOSUtils.swift | 40 +++ Sources/QueueITLib/NewQueueConsts.swift | 5 + Sources/QueueITLib/NewQueueITApiClient.swift | 122 +++++++ Sources/QueueITLib/NewQueueITEngine.swift | 152 +++++++++ .../QueueITLib/NewQueueITReachability.swift | 118 +++++++ .../NewQueueITWKViewController.swift | 193 ++++++++++++ .../NewQueueITWaitingRoomProvider.swift | 199 ++++++++++++ .../NewQueueITWaitingRoomView.swift | 100 ++++++ Sources/QueueITLib/NewQueueInfo.swift | 17 + Sources/QueueITLib/NewQueueStatus.swift | 31 ++ .../QueueITLib/NewQueueTryPassResult.swift | 9 + 43 files changed, 1134 insertions(+), 1878 deletions(-) delete mode 100644 QueueITLib/IOSUtils.h delete mode 100644 QueueITLib/IOSUtils.m delete mode 100644 QueueITLib/PrivacyInfo.xcprivacy delete mode 100644 QueueITLib/QueueConsts.h delete mode 100644 QueueITLib/QueueDisabledInfo.h delete mode 100644 QueueITLib/QueueDisabledInfo.m delete mode 100644 QueueITLib/QueueITApiClient.h delete mode 100644 QueueITLib/QueueITApiClient.m delete mode 100644 QueueITLib/QueueITApiClient_NSURLConnection.h delete mode 100644 QueueITLib/QueueITApiClient_NSURLConnection.m delete mode 100644 QueueITLib/QueueITApiClient_NSURLConnectionRequest.h delete mode 100644 QueueITLib/QueueITApiClient_NSURLConnectionRequest.m delete mode 100644 QueueITLib/QueueITEngine.h delete mode 100644 QueueITLib/QueueITEngine.m delete mode 100644 QueueITLib/QueueITReachability.h delete mode 100644 QueueITLib/QueueITReachability.m delete mode 100644 QueueITLib/QueueITWKViewController.h delete mode 100644 QueueITLib/QueueITWKViewController.m delete mode 100644 QueueITLib/QueueITWaitingRoomProvider.h delete mode 100644 QueueITLib/QueueITWaitingRoomProvider.m delete mode 100644 QueueITLib/QueueITWaitingRoomView.h delete mode 100644 QueueITLib/QueueITWaitingRoomView.m delete mode 100644 QueueITLib/QueuePassedInfo.h delete mode 100644 QueueITLib/QueuePassedInfo.m delete mode 100644 QueueITLib/QueueStatus.h delete mode 100644 QueueITLib/QueueStatus.m delete mode 100644 QueueITLib/QueueTryPassResult.h delete mode 100644 QueueITLib/QueueTryPassResult.m create mode 100644 Sources/QueueITLib/NewConnection.swift create mode 100644 Sources/QueueITLib/NewConnectionRequest.swift create mode 100644 Sources/QueueITLib/NewIOSUtils.swift create mode 100644 Sources/QueueITLib/NewQueueConsts.swift create mode 100644 Sources/QueueITLib/NewQueueITApiClient.swift create mode 100644 Sources/QueueITLib/NewQueueITEngine.swift create mode 100644 Sources/QueueITLib/NewQueueITReachability.swift create mode 100644 Sources/QueueITLib/NewQueueITWKViewController.swift create mode 100644 Sources/QueueITLib/NewQueueITWaitingRoomProvider.swift create mode 100644 Sources/QueueITLib/NewQueueITWaitingRoomView.swift create mode 100644 Sources/QueueITLib/NewQueueInfo.swift create mode 100644 Sources/QueueITLib/NewQueueStatus.swift create mode 100644 Sources/QueueITLib/NewQueueTryPassResult.swift diff --git a/.gitignore b/.gitignore index d5796ae..abca1e5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ xcuserdata/ /.idea build QueueITLibSwift.xcframework.zip -.DS_Store \ No newline at end of file +.DS_Store +.index-build diff --git a/Package.swift b/Package.swift index db710e0..0a4b44b 100644 --- a/Package.swift +++ b/Package.swift @@ -6,18 +6,18 @@ import PackageDescription let package = Package( name: "QueueITLibrary", platforms: [ - .iOS(.v11) + .iOS(.v11), ], products: [ .library( name: "QueueITLibrary", - targets: ["QueueITLibrary"]), + targets: ["QueueITLibrary"] + ), ], targets: [ .target( name: "QueueITLibrary", - path: "QueueItLib/", - publicHeadersPath: "" - ) + path: "Sources/QueueITLib" + ), ] ) diff --git a/QueueITLib/IOSUtils.h b/QueueITLib/IOSUtils.h deleted file mode 100644 index 758480b..0000000 --- a/QueueITLib/IOSUtils.h +++ /dev/null @@ -1,12 +0,0 @@ -#import -#import "QueueConsts.h" - -@interface IOSUtils : NSObject - -+(NSString*)getUserId; -+(void)getUserAgent:(void (^)(NSString*))completionHandler; -+(NSString*)getLibraryVersion; -+(NSString*)getSdkVersion; -+(NSString*)convertTtlMinutesToSecondsString:(int)ttlMinutes; - -@end diff --git a/QueueITLib/IOSUtils.m b/QueueITLib/IOSUtils.m deleted file mode 100644 index b2266cf..0000000 --- a/QueueITLib/IOSUtils.m +++ /dev/null @@ -1,55 +0,0 @@ -#import -#import "IOSUtils.h" - -@implementation IOSUtils - -WKWebView* webView; - -+(NSString*)getUserId{ - UIDevice* device = [[UIDevice alloc]init]; - NSUUID* deviceid = [device identifierForVendor]; - NSString* uuid = [deviceid UUIDString]; - return uuid; -} - -+(void)getUserAgent:(void (^)(NSString*))completionHandler{ - dispatch_async(dispatch_get_main_queue(), ^{ - WKWebView* view = [[WKWebView alloc] initWithFrame:CGRectZero]; - [view evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable userAgent, NSError * _Nullable error) { - if (error == nil) { - completionHandler(userAgent); - } - else { - completionHandler(@""); - } - webView = nil; - }]; - webView = view; - }); -} - -+(NSString*)getLibraryVersion{ - NSDictionary *infoDictionary = [[NSBundle mainBundle]infoDictionary]; - - NSString *libName = infoDictionary[(NSString *)kCFBundleNameKey]; - NSString * major = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - NSString *minor = infoDictionary[(NSString*)kCFBundleVersionKey]; - NSString* libversion = [NSString stringWithFormat:@"%@-%@.%@", libName, major, minor]; - - return libversion; -} - -+(NSString*)getSdkVersion{ - return SDKVersion; -} - -+(NSString*)convertTtlMinutesToSecondsString:(int)ttlMinutes -{ - long currentTime = (long)(NSTimeInterval)([[NSDate date] timeIntervalSince1970]); - int secondsToAdd = ttlMinutes * 60.0; - long timeStamp = currentTime + secondsToAdd; - NSString* urlTtlString = [NSString stringWithFormat:@"%li", timeStamp]; - return urlTtlString; -} - -@end diff --git a/QueueITLib/PrivacyInfo.xcprivacy b/QueueITLib/PrivacyInfo.xcprivacy deleted file mode 100644 index 5139ebc..0000000 --- a/QueueITLib/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,35 +0,0 @@ - - - - - NSPrivacyCollectedDataTypes - - - NSPrivacyCollectedDataType - NSPrivacyCollectedDataTypeOtherDataTypes - NSPrivacyCollectedDataTypeLinked - - NSPrivacyCollectedDataTypeTracking - - NSPrivacyCollectedDataTypePurposes - - NSPrivacyCollectedDataTypePurposeAppFunctionality - - - - NSPrivacyCollectedDataType - NSPrivacyCollectedDataTypeDeviceID - NSPrivacyCollectedDataTypeLinked - - NSPrivacyCollectedDataTypeTracking - - NSPrivacyCollectedDataTypePurposes - - NSPrivacyCollectedDataTypePurposeAppFunctionality - - - - NSPrivacyTracking - - - diff --git a/QueueITLib/QueueConsts.h b/QueueITLib/QueueConsts.h deleted file mode 100644 index eeee409..0000000 --- a/QueueITLib/QueueConsts.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef QueueConsts_h -#define QueueConsts_h - -#define QueueCloseUrl @"queueit://close" -#define QueueRestartSessionUrl @"queueit://restartSession" -#define SDKVersion @"iOS-3.4.4"; - -#endif diff --git a/QueueITLib/QueueDisabledInfo.h b/QueueITLib/QueueDisabledInfo.h deleted file mode 100644 index 428f707..0000000 --- a/QueueITLib/QueueDisabledInfo.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@interface QueueDisabledInfo : NSObject - -@property (nonatomic, strong) NSString* _Nullable queueitToken; - --(instancetype _Nonnull )initWithQueueitToken:(NSString* _Nullable) queueitToken; - -@end diff --git a/QueueITLib/QueueDisabledInfo.m b/QueueITLib/QueueDisabledInfo.m deleted file mode 100644 index f0c4935..0000000 --- a/QueueITLib/QueueDisabledInfo.m +++ /dev/null @@ -1,14 +0,0 @@ -#import "QueueDisabledInfo.h" - -@implementation QueueDisabledInfo - --(instancetype)initWithQueueitToken:(NSString *)queueitToken -{ - if(self = [super init]) { - self.queueitToken = queueitToken; - } - - return self; -} - -@end diff --git a/QueueITLib/QueueITApiClient.h b/QueueITLib/QueueITApiClient.h deleted file mode 100644 index 37448d2..0000000 --- a/QueueITLib/QueueITApiClient.h +++ /dev/null @@ -1,24 +0,0 @@ -#import -#import "QueueStatus.h" - -typedef void (^QueueServiceSuccess)(NSData *data); -typedef void (^QueueServiceFailure)(NSError *error, NSString* errorMessage); - -@interface QueueITApiClient: NSObject - -+ (QueueITApiClient *)getInstance; -+ (void) setTesting:(bool)enabled; - --(NSString*)enqueue:(NSString*)customerId - eventOrAliasId:(NSString*)eventorAliasId - userId:(NSString*)userId - userAgent:(NSString*)userAgent - sdkVersion:(NSString*)sdkVersion - layoutName:(NSString*)layoutName - language:(NSString*)language - enqueueToken:(NSString*)enqueueToken - enqueueKey:(NSString*)enqueueKey - success:(void(^)(QueueStatus* queueStatus))success - failure:(QueueServiceFailure)failure; - -@end diff --git a/QueueITLib/QueueITApiClient.m b/QueueITLib/QueueITApiClient.m deleted file mode 100644 index f40752e..0000000 --- a/QueueITLib/QueueITApiClient.m +++ /dev/null @@ -1,118 +0,0 @@ -#import "QueueITApiClient.h" -#import "QueueITApiClient_NSURLConnection.h" - -static QueueITApiClient *SharedInstance; - -static NSString * const API_ROOT = @"https://%@.queue-it.net/api/mobileapp/queue"; -static NSString * const TESTING_API_ROOT = @"https://%@.test.queue-it.net/api/mobileapp/queue"; -static bool testingIsEnabled = NO; - -@implementation QueueITApiClient - -+ (QueueITApiClient *)getInstance -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - SharedInstance = [[QueueITApiClient_NSURLConnection alloc] init]; - }); - - return SharedInstance; -} - -+ (void) setTesting:(bool)enabled -{ - testingIsEnabled = enabled; -} - --(NSString*)enqueue:(NSString *)customerId - eventOrAliasId:(NSString *)eventorAliasId - userId:(NSString *)userId - userAgent:(NSString *)userAgent - sdkVersion:(NSString*)sdkVersion - layoutName:(NSString*)layoutName - language:(NSString*)language - enqueueToken:(NSString*)enqueueToken - enqueueKey:(NSString*)enqueueKey - success:(void (^)(QueueStatus *))success - failure:(QueueServiceFailure)failure -{ - NSMutableDictionary* bodyDict = [[NSMutableDictionary alloc] init]; - [bodyDict setObject:userId forKey:@"userId"]; - [bodyDict setObject:userAgent forKey:@"userAgent"]; - [bodyDict setObject:sdkVersion forKey:@"sdkVersion"]; - - if(layoutName){ - [bodyDict setObject:layoutName forKey:@"layoutName"]; - } - - if(language){ - [bodyDict setObject:language forKey:@"language"]; - } - - if(enqueueToken){ - [bodyDict setObject:enqueueToken forKey:@"enqueueToken"]; - } - - if(enqueueKey){ - [bodyDict setObject:enqueueKey forKey:@"enqueueKey"]; - } - - NSString* urlAsString; - if(testingIsEnabled){ - urlAsString = [NSString stringWithFormat:TESTING_API_ROOT, customerId]; - }else{ - urlAsString = [NSString stringWithFormat:API_ROOT, customerId]; - } - urlAsString = [urlAsString stringByAppendingString:[NSString stringWithFormat:@"/%@", customerId]]; - urlAsString = [urlAsString stringByAppendingString:[NSString stringWithFormat:@"/%@", eventorAliasId]]; - urlAsString = [urlAsString stringByAppendingString:[NSString stringWithFormat:@"/enqueue"]]; - - return [self submitPOSTPath:urlAsString body:bodyDict - success:^(NSData *data) - { - NSError *error = nil; - NSDictionary *userDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - if (userDict && [userDict isKindOfClass:[NSDictionary class]]) - { - QueueStatus* queueStatus = [[QueueStatus alloc] initWithDictionary:userDict]; - - if (success != NULL) { - success(queueStatus); - } - } else if (success != NULL) { - success(NULL); - } - } - failure:^(NSError *error, NSString* errorMessage) - { - failure(error, errorMessage); - } - ]; -} - -- (NSString *)submitPOSTPath:(NSString *)path - body:(NSDictionary *)bodyDict - success:(QueueServiceSuccess)success - failure:(QueueServiceFailure)failure -{ - NSURL *url = [NSURL URLWithString:path]; - return [self submitRequestWithURL:url - method:@"POST" - body:bodyDict - expectedStatus:200 - success:success - failure:failure]; -} - -#pragma mark - Abstract methods -- (NSString *)submitRequestWithURL:(NSURL *)URL - method:(NSString *)httpMethod - body:(NSDictionary *)bodyDict - expectedStatus:(NSInteger)expectedStatus - success:(QueueServiceSuccess)success - failure:(QueueServiceFailure)failure -{ - return nil; -} - -@end diff --git a/QueueITLib/QueueITApiClient_NSURLConnection.h b/QueueITLib/QueueITApiClient_NSURLConnection.h deleted file mode 100644 index 01071bf..0000000 --- a/QueueITLib/QueueITApiClient_NSURLConnection.h +++ /dev/null @@ -1,6 +0,0 @@ -#import -#import "QueueITApiClient.h" - -@interface QueueITApiClient_NSURLConnection : QueueITApiClient - -@end diff --git a/QueueITLib/QueueITApiClient_NSURLConnection.m b/QueueITLib/QueueITApiClient_NSURLConnection.m deleted file mode 100644 index 21edb11..0000000 --- a/QueueITLib/QueueITApiClient_NSURLConnection.m +++ /dev/null @@ -1,46 +0,0 @@ -#import "QueueITApiClient_NSURLConnection.h" -#import "QueueITApiClient_NSURLConnectionRequest.h" - -@interface QueueITApiClient_NSURLConnection() -@end - - -@implementation QueueITApiClient_NSURLConnection - -- (NSString *)submitRequestWithURL:(NSURL *)URL - method:(NSString *)httpMethod - body:(NSDictionary *)bodyDict - expectedStatus:(NSInteger)expectedStatus - success:(QueueServiceSuccess)success - failure:(QueueServiceFailure)failure -{ - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; - [request setHTTPMethod:httpMethod]; - - NSError *error; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:bodyDict - options:0 - error:&error]; - [request setHTTPBody: jsonData]; - [request addValue:@"application/json" forHTTPHeaderField:@"Accept"]; - [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - - QueueITApiClient_NSURLConnectionRequest *connectionRequest; - connectionRequest = [[QueueITApiClient_NSURLConnectionRequest alloc] initWithRequest:request - expectedStatusCode:expectedStatus - success:success - failure:failure - delegate:self]; - - NSString *connectionID = [connectionRequest uniqueIdentifier]; - - return connectionID; -} - -#pragma mark - NSURLConnectionRequestDelegate - -- (void)requestDidComplete:(QueueITApiClient_NSURLConnectionRequest *)request -{ -} - -@end diff --git a/QueueITLib/QueueITApiClient_NSURLConnectionRequest.h b/QueueITLib/QueueITApiClient_NSURLConnectionRequest.h deleted file mode 100644 index 6b5013d..0000000 --- a/QueueITLib/QueueITApiClient_NSURLConnectionRequest.h +++ /dev/null @@ -1,20 +0,0 @@ -#import -#import "QueueITApiClient.h" - -@protocol QueueService_NSURLConnectionRequestDelegate; - -@interface QueueITApiClient_NSURLConnectionRequest : NSObject - -- (NSString *)uniqueIdentifier; - -- (instancetype)initWithRequest:(NSURLRequest *)request - expectedStatusCode:(NSInteger)statusCode - success:(QueueServiceSuccess)success - failure:(QueueServiceFailure)failure - delegate:(id)delegate; - -@end - -@protocol QueueService_NSURLConnectionRequestDelegate -- (void)requestDidComplete:(QueueITApiClient_NSURLConnectionRequest *)request; -@end diff --git a/QueueITLib/QueueITApiClient_NSURLConnectionRequest.m b/QueueITLib/QueueITApiClient_NSURLConnectionRequest.m deleted file mode 100644 index 13cdcc5..0000000 --- a/QueueITLib/QueueITApiClient_NSURLConnectionRequest.m +++ /dev/null @@ -1,133 +0,0 @@ -#import "QueueITApiClient_NSURLConnectionRequest.h" - - -@interface QueueITApiClient_NSURLConnectionRequest() - -@property (nonatomic, strong) NSURLConnection *connection; -@property (nonatomic, strong) NSURLRequest *request; -@property (nonatomic, strong) NSURLResponse *response; -@property (nonatomic, strong) NSMutableData *data; -@property (nonatomic, copy) QueueServiceSuccess successCallback; -@property (nonatomic, copy) QueueServiceFailure failureCallback; -@property (nonatomic, weak) id delegate; -@property (nonatomic, strong) NSString *uniqueIdentifier; -@property (nonatomic, assign) NSInteger expectedStatusCode; -@property (nonatomic, assign) NSInteger actualStatusCode; - -@end - -@implementation QueueITApiClient_NSURLConnectionRequest - -- (instancetype)initWithRequest:(NSURLRequest *)request - expectedStatusCode:(NSInteger)statusCode - success:(QueueServiceSuccess)success - failure:(QueueServiceFailure)failure - delegate:(id)delegate -{ - if ((self = [super init])) { - self.request = request; - self.expectedStatusCode = statusCode; - self.successCallback = success; - self.failureCallback = failure; - self.uniqueIdentifier = [[NSUUID UUID] UUIDString]; - self.delegate = delegate; - - [self initiateRequest]; - } - - return self; -} - -- (void)initiateRequest -{ - self.response = nil; - self.data = [NSMutableData data]; - self.actualStatusCode = NSNotFound; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self]; -#pragma GCC diagnostic pop -} - -#pragma mark - NSURLConnectionDelegate - -- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error -{ - dispatch_async(dispatch_get_main_queue(), ^{ - self.failureCallback(error, @"Unexpected failure occured."); - }); - - [self.delegate requestDidComplete:self]; -} - -#pragma mark - NSURLConnectionDataDelegate - -- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response -{ - self.response = response; - NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode]; - self.actualStatusCode = responseCode; -} - -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data -{ - [self appendData:data]; -} - -- (void)connectionDidFinishLoading:(NSURLConnection *)connection -{ - if ([self hasExpectedStatusCode]) { - dispatch_async(dispatch_get_main_queue(), ^{ - self.successCallback(self.data); - }); - } - else { - NSString *message = [NSString stringWithFormat:@"Unexpected response code: %li", (long)self.actualStatusCode]; - - if (self.actualStatusCode >= 400 && self.actualStatusCode < 500) - { - message = [NSString stringWithCString:[self.data bytes] encoding:NSASCIIStringEncoding]; - } - else - { - if (self.data) { - NSError *jsonError = nil; - id json = [NSJSONSerialization JSONObjectWithData:self.data options:0 error:&jsonError]; - if (json && [json isKindOfClass:[NSDictionary class]]) { - NSString *errorMessage = [(NSDictionary *)json valueForKey:@"error"]; - if (errorMessage) { - message = errorMessage; - } - } - } - } - - NSError *error = [NSError errorWithDomain:@"QueueService" - code:self.actualStatusCode - userInfo:@{ NSLocalizedDescriptionKey: message }]; - - dispatch_async(dispatch_get_main_queue(), ^{ - self.failureCallback(error, message); - }); - } - - [self.delegate requestDidComplete:self]; -} - -#pragma mark - Private helpers - -- (void)appendData:(NSData *)data -{ - [self.data appendData:data]; -} - -- (BOOL)hasExpectedStatusCode -{ - if (self.actualStatusCode != NSNotFound) { - return self.expectedStatusCode == self.actualStatusCode; - } - - return NO; -} - -@end diff --git a/QueueITLib/QueueITEngine.h b/QueueITLib/QueueITEngine.h deleted file mode 100644 index 8931119..0000000 --- a/QueueITLib/QueueITEngine.h +++ /dev/null @@ -1,98 +0,0 @@ -#import -#import "QueuePassedInfo.h" -#import "QueueDisabledInfo.h" -#import "QueueTryPassResult.h" -#import "QueueConsts.h" -#import "QueueITWaitingRoomView.h" -#import "QueueITWaitingRoomProvider.h" - -@protocol QueuePassedDelegate; -@protocol QueueViewWillOpenDelegate; -@protocol QueueDisabledDelegate; -@protocol QueueITUnavailableDelegate; -@protocol QueueUserExitedDelegate; -@protocol QueueITErrorDelegate; -@protocol QueueViewClosedDelegate; -@protocol QueueSessionRestartDelegate; -@protocol QueueUrlChangedDelegate; - -@protocol QueueViewDidAppearDelegate; - -@interface QueueITEngine : NSObject - -@property (nonatomic, weak)id _Nullable queuePassedDelegate; -@property (nonatomic, weak)id _Nullable queueViewWillOpenDelegate; -@property (nonatomic, weak)id _Nullable queueDisabledDelegate; -@property (nonatomic, weak)id _Nullable queueITUnavailableDelegate; -@property (nonatomic, weak)id _Nullable queueErrorDelegate; -@property (nonatomic, weak)id _Nullable queueViewClosedDelegate; -@property (nonatomic, weak)id _Nullable queueUserExitedDelegate; -@property (nonatomic, weak)id _Nullable queueSessionRestartDelegate; -@property (nonatomic, weak)id _Nullable queueUrlChangedDelegate; - -@property (nonatomic, weak)id _Nullable queueViewDidAppearDelegate; - -@property (nonatomic, strong)NSString* _Nullable errorMessage; -@property (nonatomic, copy)NSString* _Nonnull customerId; -@property (nonatomic, copy)NSString* _Nonnull eventId; -@property (nonatomic, copy)NSString* _Nullable layoutName; -@property (nonatomic, copy)NSString* _Nullable language; - --(instancetype _Nonnull )initWithHost:(UIViewController* _Nonnull)host - customerId:(NSString* _Nonnull)customerId - eventOrAliasId:(NSString* _Nonnull)eventOrAliasId - layoutName:(NSString* _Nullable)layoutName - language:(NSString* _Nullable)language; - --(void)setViewDelay:(int)delayInterval; - --(BOOL)run:(NSError* _Nullable* _Nullable)error; --(BOOL)runWithEnqueueToken:(NSString* _Nonnull) enqueueToken - error:(NSError* _Nullable*_Nullable) error; --(BOOL)runWithEnqueueKey:(NSString* _Nonnull) enqueueKey - error:(NSError* _Nullable*_Nullable) error; --(BOOL)isRequestInProgress; - -@end - -@protocol QueuePassedDelegate --(void)notifyYourTurn:(QueuePassedInfo* _Nullable) queuePassedInfo; -@end - - -@protocol QueueViewWillOpenDelegate --(void)notifyQueueViewWillOpen; -@end - -@protocol QueueDisabledDelegate --(void)notifyQueueDisabled:(QueueDisabledInfo* _Nullable) queueDisabledInfo; -@end - -@protocol QueueITUnavailableDelegate --(void)notifyQueueITUnavailable:(NSString* _Nonnull) errorMessage; -@end - -@protocol QueueITErrorDelegate --(void)notifyQueueError:(NSString* _Nonnull) errorMessage errorCode:(long)errorCode; -@end - -@protocol QueueViewClosedDelegate --(void)notifyViewClosed; -@end - -@protocol QueueUserExitedDelegate --(void)notifyUserExited; -@end - -@protocol QueueSessionRestartDelegate --(void)notifySessionRestart; -@end - -@protocol QueueUrlChangedDelegate --(void)notifyQueueUrlChanged:(NSString* _Nonnull) url; -@end - - -@protocol QueueViewDidAppearDelegate --(void)notifyQueueViewDidAppear; -@end diff --git a/QueueITLib/QueueITEngine.m b/QueueITLib/QueueITEngine.m deleted file mode 100644 index 6778bf7..0000000 --- a/QueueITLib/QueueITEngine.m +++ /dev/null @@ -1,125 +0,0 @@ -#import "QueueITEngine.h" -#import "QueueITApiClient.h" -#import "QueueStatus.h" -#import "IOSUtils.h" -#import "QueueITWaitingRoomView.h" -#import "QueueITWaitingRoomProvider.h" - -@interface QueueITEngine() -@property (nonatomic, weak)UIViewController* host; - -@property QueueITWaitingRoomProvider* waitingRoomProvider; -@property QueueITWaitingRoomView* waitingRoomView; -@end - -@implementation QueueITEngine - --(instancetype)initWithHost:(UIViewController *)host customerId:(NSString*)customerId eventOrAliasId:(NSString*)eventOrAliasId layoutName:(NSString*)layoutName language:(NSString*)language -{ - self = [super init]; - if(self) { - self.waitingRoomProvider = [[QueueITWaitingRoomProvider alloc] initWithCustomerId:customerId - eventOrAliasId:eventOrAliasId - layoutName:layoutName - language:language]; - - self.waitingRoomView = [[QueueITWaitingRoomView alloc] initWithHost: host customerId: customerId eventId: eventOrAliasId]; - self.host = host; - self.customerId = customerId; - self.eventId = eventOrAliasId; - self.layoutName = layoutName; - self.language = language; - - self.waitingRoomView.delegate = self; - self.waitingRoomProvider.delegate = self; - } - return self; -} - --(void)setViewDelay:(int)delayInterval { - [self.waitingRoomView setViewDelay:delayInterval]; -} - --(BOOL)isRequestInProgress { - return [self.waitingRoomProvider IsRequestInProgress]; -} - --(BOOL)runWithEnqueueKey:(NSString *)enqueueKey - error:(NSError *__autoreleasing *)error -{ - return [self.waitingRoomProvider TryPassWithEnqueueKey:enqueueKey error:error]; -} - --(BOOL)runWithEnqueueToken:(NSString *)enqueueToken - error:(NSError *__autoreleasing *)error -{ - return [self.waitingRoomProvider TryPassWithEnqueueToken:enqueueToken error:error]; -} - --(BOOL)run:(NSError **)error -{ - return [self.waitingRoomProvider TryPass:error]; -} - - - --(void)showQueue:(NSString*)queueUrl targetUrl:(NSString*)targetUrl -{ - [self.waitingRoomView show:queueUrl targetUrl:targetUrl]; -} - - -- (void)waitingRoomView:(nonnull QueueITWaitingRoomView *)view notifyViewPassedQueue:(QueuePassedInfo * _Nullable)queuePassedInfo { - [self.queuePassedDelegate notifyYourTurn:queuePassedInfo]; -} - -- (void)notifyViewQueueWillOpen:(nonnull QueueITWaitingRoomView *)view { - [self.queueViewWillOpenDelegate notifyQueueViewWillOpen]; -} - -- (void)waitingRoomProvider:(nonnull QueueITWaitingRoomProvider *)provider notifyProviderFailure:(NSString * _Nullable)errorMessage errorCode:(long)errorCode { - if(errorCode == 3) { - [self.queueITUnavailableDelegate notifyQueueITUnavailable:errorMessage]; - } - - [self.queueErrorDelegate notifyQueueError:errorMessage errorCode:errorCode]; -} - -- (void)notifyViewSessionRestart:(nonnull QueueITWaitingRoomView *)view { - [self.queueSessionRestartDelegate notifySessionRestart]; -} - -- (void)notifyViewUserExited:(nonnull QueueITWaitingRoomView *)view { - [self.queueUserExitedDelegate notifyUserExited]; -} - -- (void)notifyViewUserClosed:(nonnull QueueITWaitingRoomView *)view { - [self.queueViewClosedDelegate notifyViewClosed]; -} - -- (void)waitingRoomView:(nonnull QueueITWaitingRoomView *)view notifyViewUpdatePageUrl:(NSString * _Nullable)urlString { - [self.queueUrlChangedDelegate notifyQueueUrlChanged:urlString]; -} - --(void)notifyViewQueueDidAppear:(nonnull QueueITWaitingRoomView *)view { - [self.queueViewDidAppearDelegate notifyQueueViewDidAppear]; -} - -- (void)waitingRoomProvider:(nonnull QueueITWaitingRoomProvider *)provider notifyProviderSuccess:(QueueTryPassResult * _Nonnull)queuePassResult { - if([[queuePassResult redirectType] isEqual: @"safetynet"]) - { - QueuePassedInfo* queuePassedInfo = [[QueuePassedInfo alloc] initWithQueueitToken:queuePassResult.queueToken]; - [self.queuePassedDelegate notifyYourTurn:queuePassedInfo]; - return; - } - else if([[queuePassResult redirectType] isEqual: @"disabled"] || [[queuePassResult redirectType] isEqual: @"idle"] || [[queuePassResult redirectType] isEqual: @"afterevent"]) - { - QueueDisabledInfo* queueDisabledInfo = [[QueueDisabledInfo alloc]initWithQueueitToken:queuePassResult.queueToken]; - [self.queueDisabledDelegate notifyQueueDisabled:queueDisabledInfo]; - return; - } - - [self showQueue:queuePassResult.queueUrl targetUrl:queuePassResult.targetUrl]; - -} -@end diff --git a/QueueITLib/QueueITReachability.h b/QueueITLib/QueueITReachability.h deleted file mode 100644 index d9c8fbb..0000000 --- a/QueueITLib/QueueITReachability.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - File: Reachability.h - Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. - Version: 3.5 - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple - Inc. ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or redistribution of - this Apple software constitutes acceptance of these terms. If you do - not agree with these terms, please do not use, install, modify or - redistribute this Apple software. - - In consideration of your agreement to abide by the following terms, and - subject to these terms, Apple grants you a personal, non-exclusive - license, under Apple's copyrights in this original Apple software (the - "Apple Software"), to use, reproduce, modify and redistribute the Apple - Software, with or without modifications, in source and/or binary forms; - provided that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the following - text and disclaimers in all such redistributions of the Apple Software. - Neither the name, trademarks, service marks or logos of Apple Inc. may - be used to endorse or promote products derived from the Apple Software - without specific prior written permission from Apple. Except as - expressly stated in this notice, no other rights or licenses, express or - implied, are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or by other - works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. APPLE - MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION - THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND - OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, - MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED - AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), - STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - Copyright (C) 2014 Apple Inc. All Rights Reserved. - - */ - -#import -#import -#import - - -typedef enum : NSInteger { - NotReachable = 0, - ReachableViaWiFi, - ReachableViaWWAN -} NetworkStatus; - - -extern NSString *kReachabilityChangedNotification; - - -@interface QueueITReachability : NSObject - -/*! - * Use to check the reachability of a given host name. - */ -+ (instancetype)reachabilityWithHostName:(NSString *)hostName; - -/*! - * Use to check the reachability of a given IP address. - */ -+ (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress; - -/*! - * Checks whether the default route is available. Should be used by applications that do not connect to a particular host. - */ -+ (instancetype)reachabilityForInternetConnection; - -/*! - * Checks whether a local WiFi connection is available. - */ -+ (instancetype)reachabilityForLocalWiFi; - -/*! - * Start listening for reachability notifications on the current run loop. - */ -- (BOOL)startNotifier; -- (void)stopNotifier; - -- (NetworkStatus)currentReachabilityStatus; - -/*! - * WWAN may be available, but not active until a connection has been established. WiFi may require a connection for VPN on Demand. - */ -- (BOOL)connectionRequired; - -@end - - diff --git a/QueueITLib/QueueITReachability.m b/QueueITLib/QueueITReachability.m deleted file mode 100644 index 0bf7155..0000000 --- a/QueueITLib/QueueITReachability.m +++ /dev/null @@ -1,297 +0,0 @@ -/* - File: Reachability.m - Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. - Version: 3.5 - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple - Inc. ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or redistribution of - this Apple software constitutes acceptance of these terms. If you do - not agree with these terms, please do not use, install, modify or - redistribute this Apple software. - - In consideration of your agreement to abide by the following terms, and - subject to these terms, Apple grants you a personal, non-exclusive - license, under Apple's copyrights in this original Apple software (the - "Apple Software"), to use, reproduce, modify and redistribute the Apple - Software, with or without modifications, in source and/or binary forms; - provided that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the following - text and disclaimers in all such redistributions of the Apple Software. - Neither the name, trademarks, service marks or logos of Apple Inc. may - be used to endorse or promote products derived from the Apple Software - without specific prior written permission from Apple. Except as - expressly stated in this notice, no other rights or licenses, express or - implied, are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or by other - works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. APPLE - MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION - THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND - OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, - MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED - AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), - STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - Copyright (C) 2014 Apple Inc. All Rights Reserved. - - */ - -#import -#import -#import -#import - -#import - -#import "QueueITReachability.h" - - -NSString *kReachabilityChangedNotification = @"kNetworkReachabilityChangedNotification"; - - -#pragma mark - Supporting functions - -#define kShouldPrintReachabilityFlags 1 - -static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) -{ -#if kShouldPrintReachabilityFlags -#endif -} - - -static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) -{ -#pragma unused (target, flags) - NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); - NSCAssert([(__bridge NSObject*) info isKindOfClass: [QueueITReachability class]], @"info was wrong class in ReachabilityCallback"); - - QueueITReachability* noteObject = (__bridge QueueITReachability *)info; - // Post a notification to notify the client that the network reachability changed. - [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; -} - - -#pragma mark - Reachability implementation - -@implementation QueueITReachability -{ - BOOL _alwaysReturnLocalWiFiStatus; //default is NO - SCNetworkReachabilityRef _reachabilityRef; -} - -+ (instancetype)reachabilityWithHostName:(NSString *)hostName -{ - QueueITReachability* returnValue = NULL; - SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); - if (reachability != NULL) - { - returnValue= [[self alloc] init]; - if (returnValue != NULL) - { - returnValue->_reachabilityRef = reachability; - returnValue->_alwaysReturnLocalWiFiStatus = NO; - } - } - return returnValue; -} - - -+ (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress -{ - SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)hostAddress); - - QueueITReachability* returnValue = NULL; - - if (reachability != NULL) - { - returnValue = [[self alloc] init]; - if (returnValue != NULL) - { - returnValue->_reachabilityRef = reachability; - returnValue->_alwaysReturnLocalWiFiStatus = NO; - } - } - return returnValue; -} - - - -+ (instancetype)reachabilityForInternetConnection -{ - struct sockaddr_in zeroAddress; - bzero(&zeroAddress, sizeof(zeroAddress)); - zeroAddress.sin_len = sizeof(zeroAddress); - zeroAddress.sin_family = AF_INET; - - return [self reachabilityWithAddress:&zeroAddress]; -} - - -+ (instancetype)reachabilityForLocalWiFi -{ - struct sockaddr_in localWifiAddress; - bzero(&localWifiAddress, sizeof(localWifiAddress)); - localWifiAddress.sin_len = sizeof(localWifiAddress); - localWifiAddress.sin_family = AF_INET; - - // IN_LINKLOCALNETNUM is defined in as 169.254.0.0. - localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); - - QueueITReachability* returnValue = [self reachabilityWithAddress: &localWifiAddress]; - if (returnValue != NULL) - { - returnValue->_alwaysReturnLocalWiFiStatus = YES; - } - - return returnValue; -} - - -#pragma mark - Start and stop notifier - -- (BOOL)startNotifier -{ - BOOL returnValue = NO; - SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; - - if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context)) - { - if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) - { - returnValue = YES; - } - } - - return returnValue; -} - - -- (void)stopNotifier -{ - if (_reachabilityRef != NULL) - { - SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - } -} - - -- (void)dealloc -{ - [self stopNotifier]; - if (_reachabilityRef != NULL) - { - CFRelease(_reachabilityRef); - } -} - - -#pragma mark - Network Flag Handling - -- (NetworkStatus)localWiFiStatusForFlags:(SCNetworkReachabilityFlags)flags -{ - PrintReachabilityFlags(flags, "localWiFiStatusForFlags"); - NetworkStatus returnValue = NotReachable; - - if ((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) - { - returnValue = ReachableViaWiFi; - } - - return returnValue; -} - - -- (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags -{ - PrintReachabilityFlags(flags, "networkStatusForFlags"); - if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) - { - // The target host is not reachable. - return NotReachable; - } - - NetworkStatus returnValue = NotReachable; - - if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) - { - /* - If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi... - */ - returnValue = ReachableViaWiFi; - } - - if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || - (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) - { - /* - ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs... - */ - - if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) - { - /* - ... and no [user] intervention is needed... - */ - returnValue = ReachableViaWiFi; - } - } - - if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) - { - /* - ... but WWAN connections are OK if the calling application is using the CFNetwork APIs. - */ - returnValue = ReachableViaWWAN; - } - - return returnValue; -} - - -- (BOOL)connectionRequired -{ - NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); - SCNetworkReachabilityFlags flags; - - if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) - { - return (flags & kSCNetworkReachabilityFlagsConnectionRequired); - } - - return NO; -} - - -- (NetworkStatus)currentReachabilityStatus -{ - NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL SCNetworkReachabilityRef"); - NetworkStatus returnValue = NotReachable; - SCNetworkReachabilityFlags flags; - - if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) - { - if (_alwaysReturnLocalWiFiStatus) - { - returnValue = [self localWiFiStatusForFlags:flags]; - } - else - { - returnValue = [self networkStatusForFlags:flags]; - } - } - - return returnValue; -} - - -@end diff --git a/QueueITLib/QueueITWKViewController.h b/QueueITLib/QueueITWKViewController.h deleted file mode 100644 index b84d5ce..0000000 --- a/QueueITLib/QueueITWKViewController.h +++ /dev/null @@ -1,30 +0,0 @@ -#import - -@protocol QueueITViewControllerDelegate; - -@interface QueueITWKViewController : UIViewController - -@property (nonatomic, weak)id _Nullable delegate; - --(instancetype _Nullable )initWithHost:(nonnull UIViewController *)host - queueUrl:(nonnull NSString*)queueUrl - eventTargetUrl:(nonnull NSString*)eventTargetUrl - customerId:(nonnull NSString*)customerId - eventId:(nonnull NSString*)eventId; - -- (void) close:(void (^ __nullable)(void))completion; -- (BOOL) handleSpecialUrls:(nonnull NSURL*) url - decisionHandler:(nonnull void (^)(WKNavigationActionPolicy))decisionHandler; -- (BOOL) isTargetUrl:(nonnull NSURL*) targetUrl - destinationUrl:(nonnull NSURL*) destinationUrl; -- (BOOL) isBlockedUrl:(nonnull NSURL*) destinationUrl; - -@end - -@protocol QueueITViewControllerDelegate --(void)notifyViewControllerClosed; --(void)notifyViewControllerUserExited; --(void)notifyViewControllerSessionRestart; --(void)notifyViewControllerQueuePassed:(NSString* _Nullable) queueToken; --(void)notifyViewControllerPageUrlChanged:(NSString* _Nullable) urlString; -@end diff --git a/QueueITLib/QueueITWKViewController.m b/QueueITLib/QueueITWKViewController.m deleted file mode 100644 index 0c0220b..0000000 --- a/QueueITLib/QueueITWKViewController.m +++ /dev/null @@ -1,235 +0,0 @@ -#import "QueueITWKViewController.h" -#import "QueueConsts.h" - -@interface QueueITWKViewController () -@property (nonatomic) WKWebView* webView; -@property (nonatomic, strong) UIViewController* host; - -@property (nonatomic, strong)NSString* queueUrl; -@property (nonatomic, strong)NSString* eventTargetUrl; -@property (nonatomic, strong)UIActivityIndicatorView* spinner; -@property (nonatomic, strong)NSString* customerId; -@property (nonatomic, strong)NSString* eventId; -@property BOOL isQueuePassed; -@end - -static NSString * const JAVASCRIPT_GET_BODY_CLASSES = @"document.getElementsByTagName('body')[0].className"; - -@implementation QueueITWKViewController - --(instancetype)initWithHost:(UIViewController *)host - queueUrl:(NSString*)queueUrl - eventTargetUrl:(NSString*)eventTargetUrl - customerId:(NSString*)customerId - eventId:(NSString*)eventId -{ - self = [super init]; - if(self) { - self.host = host; - self.queueUrl = queueUrl; - self.eventTargetUrl = eventTargetUrl; - self.customerId = customerId; - self.eventId = eventId; - self.isQueuePassed = NO; - } - return self; -} - -- (void)close:(void (^ __nullable)(void))onComplete { - [self.host dismissViewControllerAnimated:YES completion:^{ - if(onComplete!=nil){ - onComplete(); - } - }]; -} - -- (BOOL) isTargetUrl:(nonnull NSURL*) targetUrl - destinationUrl:(nonnull NSURL*) destinationUrl { - NSString* destinationHost = destinationUrl.host; - NSString* destinationPath = destinationUrl.path; - NSString* targetHost = targetUrl.host; - NSString* targetPath = targetUrl.path; - - return [destinationHost isEqualToString: targetHost] - && [destinationPath isEqualToString: targetPath]; -} - -- (BOOL) isBlockedUrl:(nonnull NSURL*) destinationUrl { - NSString* path = destinationUrl.path; - if([path hasPrefix: @"/what-is-this.html"]){ - return true; - } - return false; -} - -- (BOOL)handleSpecialUrls:(NSURL*) url - decisionHandler:(nonnull void (^)(WKNavigationActionPolicy))decisionHandler { - if([[url absoluteString] isEqualToString: QueueCloseUrl]){ - [self close: ^{ - [self.delegate notifyViewControllerClosed]; - }]; - decisionHandler(WKNavigationActionPolicyCancel); - return true; - } else if ([[url absoluteString] isEqualToString: QueueRestartSessionUrl]){ - [self close:^{ - [self.delegate notifyViewControllerSessionRestart]; - }]; - decisionHandler(WKNavigationActionPolicyCancel); - return true; - } - return NO; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - self.spinner = [[UIActivityIndicatorView alloc]initWithFrame:self.view.bounds]; - [self.spinner setColor:[UIColor grayColor]]; - - WKPreferences* preferences = [[WKPreferences alloc]init]; - preferences.javaScriptEnabled = YES; - WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc]init]; - config.preferences = preferences; - WKWebView* webview = [[WKWebView alloc]initWithFrame:self.view.bounds configuration:config]; - webview.navigationDelegate = self; - [webview setAutoresizingMask: UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth]; - // Make webview transparent - webview.opaque = NO; - webview.backgroundColor = [UIColor clearColor]; - self.webView = webview; -} - -- (void)viewWillAppear:(BOOL)animated{ - [super viewWillAppear:animated]; -} - --(void)viewWillLayoutSubviews{ - [super viewWillLayoutSubviews]; - [self.spinner startAnimating]; - self.webView.frame = self.view.bounds; - self.spinner.frame = self.view.bounds; - - [self.view addSubview:self.webView]; - [self.webView addSubview:self.spinner]; - - [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.queueUrl]]]; -} - --(void)viewDidAppear:(BOOL)animated{ - [super viewDidAppear:animated]; -} - -- (void)viewDidDisappear:(BOOL)animated -{ - [super viewDidDisappear:animated]; - [self.webView removeFromSuperview]; - self.webView = nil; -} - -#pragma mark - WKNavigationDelegate - -- (void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(nonnull WKNavigationAction *)navigationAction decisionHandler:(nonnull void (^)(WKNavigationActionPolicy))decisionHandler{ - if (!self.isQueuePassed) { - NSURLRequest* request = navigationAction.request; - NSString* urlString = [[request URL] absoluteString]; - NSString* targetUrlString = self.eventTargetUrl; - if (urlString != nil) { - NSURL* url = [NSURL URLWithString:urlString]; - NSURL* targetUrl = [NSURL URLWithString:targetUrlString]; - if(urlString != nil && ![urlString isEqualToString:@"about:blank"]) { - BOOL isQueueUrl = [self.queueUrl containsString:url.host]; - BOOL isNotFrame = [[[request URL] absoluteString] isEqualToString:[[request mainDocumentURL] absoluteString]]; - - if([self handleSpecialUrls:url decisionHandler:decisionHandler]){ - return; - } - - if([self isBlockedUrl: url]){ - decisionHandler(WKNavigationActionPolicyCancel); - return; - } - - if (isNotFrame) { - if (isQueueUrl) { - [self raiseQueuePageUrl:urlString]; - } - if ([self isTargetUrl: targetUrl - destinationUrl: url]) { - self.isQueuePassed = YES; - NSString* queueitToken = [self extractQueueToken:url.absoluteString]; - [self.delegate notifyViewControllerQueuePassed:queueitToken]; - [self.host dismissViewControllerAnimated:YES completion:^{ - }]; - decisionHandler(WKNavigationActionPolicyCancel); - return; - } - } - if (navigationAction.navigationType == WKNavigationTypeLinkActivated && !isQueueUrl) { - if (@available(iOS 10, *)){ - [[UIApplication sharedApplication] openURL:[request URL] options:@{} completionHandler:^(BOOL success){ - - }]; - } - else { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - [[UIApplication sharedApplication] openURL:[request URL]]; -#pragma GCC diagnostic pop - } - - decisionHandler(WKNavigationActionPolicyCancel); - return; - } - } - } - } - - decisionHandler(WKNavigationActionPolicyAllow); -} - -- (NSString*)extractQueueToken:(NSString*) url { - NSString* tokenKey = @"queueittoken="; - if ([url containsString:tokenKey]) { - NSString* token = [url substringFromIndex:NSMaxRange([url rangeOfString:tokenKey])]; - if([token containsString:@"&"]) { - token = [token substringToIndex:NSMaxRange([token rangeOfString:@"&"]) - 1]; - } - return token; - } - return nil; -} - -- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{ -} - -- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{ - [self.spinner stopAnimating]; - if (![self.webView isLoading]) - { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; - } - - // Check if user exitted through the default exit link and notify the engine - [self.webView evaluateJavaScript:JAVASCRIPT_GET_BODY_CLASSES completionHandler:^(id result, NSError* error){ - if (error != nil) { - NSLog(@"evaluateJavaScript error : %@", error.localizedDescription); - } - else { - NSString* resultString = [NSString stringWithFormat:@"%@", result]; - NSArray *htmlBodyClasses = [resultString componentsSeparatedByString:@" "]; - BOOL isExitClassPresent = [htmlBodyClasses containsObject:@"exit"]; - if (isExitClassPresent) { - [self.delegate notifyViewControllerUserExited]; - } - } - }]; -} - -- (void)raiseQueuePageUrl:(NSString *)urlString { - [self.delegate notifyViewControllerPageUrlChanged:urlString]; -} - --(void)appWillResignActive:(NSNotification*)note -{ -} - -@end diff --git a/QueueITLib/QueueITWaitingRoomProvider.h b/QueueITLib/QueueITWaitingRoomProvider.h deleted file mode 100644 index 2bd25b3..0000000 --- a/QueueITLib/QueueITWaitingRoomProvider.h +++ /dev/null @@ -1,34 +0,0 @@ -#import "QueueITWaitingRoomView.h" -#import "QueueTryPassResult.h" - -@protocol QueueITWaitingRoomProviderDelegate; - -@interface QueueITWaitingRoomProvider : NSObject - -typedef enum { - NetworkUnavailable = -100, - RequestAlreadyInProgress = 10 -} QueueITRuntimeError; -#define QueueITRuntimeErrorArray @"Network connection is unavailable", @"Enqueue request is already in progress", nil - -@property (nonatomic, weak)id _Nullable delegate; - --(instancetype _Nonnull)initWithCustomerId:(NSString* _Nonnull)customerId - eventOrAliasId:(NSString* _Nonnull)eventOrAliasId - layoutName:(NSString* _Nullable)layoutName - language:(NSString* _Nullable)language; - --(BOOL)TryPass: (NSError* _Nullable*_Nullable)error; --(BOOL)TryPassWithEnqueueToken:(NSString* _Nullable)enqueueToken - error:(NSError* _Nullable*_Nullable)error; --(BOOL)TryPassWithEnqueueKey:(NSString* _Nullable)enqueueKey - error:(NSError* _Nullable*_Nullable)error; --(BOOL)IsRequestInProgress; -@end - -@protocol QueueITWaitingRoomProviderDelegate - --(void)waitingRoomProvider:(nonnull QueueITWaitingRoomProvider*)provider notifyProviderSuccess:(QueueTryPassResult* _Nonnull) queuePassResult; --(void)waitingRoomProvider:(nonnull QueueITWaitingRoomProvider*)provider notifyProviderFailure:(NSString* _Nullable)errorMessage - errorCode:(long)errorCode; -@end diff --git a/QueueITLib/QueueITWaitingRoomProvider.m b/QueueITLib/QueueITWaitingRoomProvider.m deleted file mode 100644 index 189639b..0000000 --- a/QueueITLib/QueueITWaitingRoomProvider.m +++ /dev/null @@ -1,207 +0,0 @@ -#import "QueueITWaitingRoomProvider.h" -#import "IOSUtils.h" -#import "QueueITApiClient.h" -#import "QueueTryPassResult.h" -#import "QueueITReachability.h" - -// TODO: Include all the method calls here -@interface QueueITWaitingRoomProvider() -@property (nonatomic) QueueITReachability *internetReachability; -@property NSString* customerId; -@property NSString* eventOrAliasId; -@property NSString* layoutName; -@property NSString* language; -@property BOOL requestInProgress; -@property int deltaSec; - - -@end - -@implementation QueueITWaitingRoomProvider - -static int MAX_RETRY_SEC = 10; -static int INITIAL_WAIT_RETRY_SEC = 1; - --(instancetype _Nonnull)initWithCustomerId:(NSString* _Nonnull)customerId - eventOrAliasId:(NSString* _Nonnull)eventOrAliasId - layoutName:(NSString* _Nullable)layoutName - language:(NSString* _Nullable)language { - - if(self = [super init]) { - self.customerId = customerId; - self.eventOrAliasId = eventOrAliasId; - self.layoutName = layoutName; - self.language = language; - self.deltaSec = INITIAL_WAIT_RETRY_SEC; - self.internetReachability = [QueueITReachability reachabilityForInternetConnection]; - } - - return self; -} - --(BOOL) TryPass: (NSError**)error { - return [self tryEnqueue:nil enqueueKey:nil error:error]; -} - --(BOOL) TryPassWithEnqueueToken: (NSString*)enqueueToken error:(NSError *__autoreleasing *)error { - return [self tryEnqueue:enqueueToken enqueueKey:nil error:error]; -} - --(BOOL) TryPassWithEnqueueKey: (NSString*)enqueueKey error:(NSError *__autoreleasing *)error { - return [self tryEnqueue:nil enqueueKey:enqueueKey error:error]; -} - - --(BOOL)tryEnqueue:(NSString*)enqueueToken - enqueueKey:(NSString*)enqueueKey - error:(NSError**)error -{ - if(![self checkConnection:error]) { - return NO; - } - - if(self.requestInProgress) { - *error = [NSError errorWithDomain:@"QueueITRuntimeException" code:RequestAlreadyInProgress userInfo:nil]; - return NO; - } - - [IOSUtils getUserAgent:^(NSString * userAgent) { - [self tryEnqueueWithUserAgent:userAgent enqueueToken:enqueueToken enqueueKey:enqueueKey error:error]; - }]; - - return YES; -} - --(void)tryEnqueueWithUserAgent:(NSString*)secretAgent - enqueueToken:(NSString*)enqueueToken - enqueueKey:(NSString*)enqueueKey - error:(NSError**)error -{ - NSString* userId = [IOSUtils getUserId]; - NSString* userAgent = [NSString stringWithFormat:@"%@;%@", secretAgent, [IOSUtils getLibraryVersion]]; - NSString* sdkVersion = [IOSUtils getSdkVersion]; - - QueueITApiClient* apiClient = [QueueITApiClient getInstance]; - [apiClient enqueue:self.customerId - eventOrAliasId:self.eventOrAliasId - userId:userId - userAgent:userAgent - sdkVersion:sdkVersion - layoutName:self.layoutName - language:self.language - enqueueToken:enqueueToken - enqueueKey:enqueueKey - success:^(QueueStatus *queueStatus) -{ - if (queueStatus == NULL) { - [self enqueueRetryMonitor:enqueueToken enqueueKey:enqueueKey error:error]; - return; - } - - [self handleAppEnqueueResponse: queueStatus.queueId - queueURL:queueStatus.queueUrlString - eventTargetURL:queueStatus.eventTargetUrl - queueItToken:queueStatus.queueitToken]; - - self.requestInProgress = NO; - } - failure:^(NSError *error, NSString* errorMessage) - { - if (error.code >= 400 && error.code < 500) - { - [self.delegate waitingRoomProvider:self notifyProviderFailure:errorMessage errorCode:error.code]; - } - else - { - [self enqueueRetryMonitor:enqueueToken enqueueKey:enqueueKey error:&error]; - } - }]; -} - --(void)handleAppEnqueueResponse:(NSString*) queueId - queueURL:(NSString*) queueURL - eventTargetURL:(NSString*) targetURL - queueItToken:(NSString*) token { - - bool isPassedThrough = ![self isNullOrEmpty:token]; - - NSString* redirectType = [self getRedirectTypeFromToken:token]; - - QueueTryPassResult* queueTryPassResult = [[QueueTryPassResult alloc] - initWithQueueUrl:queueURL - targetUrl:targetURL - redirectType:redirectType - isPassedThrough:isPassedThrough - queueToken:token]; - - [self.delegate waitingRoomProvider:self notifyProviderSuccess:queueTryPassResult]; -} - --(void)enqueueRetryMonitor:(NSString*)enqueueToken - enqueueKey:(NSString*)enqueueKey - error:(NSError**)error -{ - if (self.deltaSec < MAX_RETRY_SEC) - { - [self tryEnqueue:enqueueToken enqueueKey:enqueueKey error:error]; - - [NSThread sleepForTimeInterval:self.deltaSec]; - self.deltaSec = self.deltaSec * 2; - } - else - { - self.deltaSec = INITIAL_WAIT_RETRY_SEC; - self.requestInProgress = NO; - [self.delegate waitingRoomProvider:self notifyProviderFailure:@"Error! Queue is unavailable." errorCode:3]; - } -} - --(BOOL)checkConnection:(NSError **)error -{ - int count = 0; - while (count < 5) - { - NetworkStatus netStatus = [self.internetReachability currentReachabilityStatus]; - if (netStatus == NotReachable) - { - [NSThread sleepForTimeInterval:1.0f]; - count++; - } - else - { - return YES; - } - } - *error = [NSError errorWithDomain:@"QueueITRuntimeException" code:NetworkUnavailable userInfo:nil]; - return NO; -} - --(BOOL)IsRequestInProgress { - return self.requestInProgress; -} - --(BOOL)isNullOrEmpty:(NSString*)queueToken { - bool isNull = queueToken == nil || queueToken == (id)[NSNull null]; - bool isEmpty = isNull || [queueToken length] == 0; - - return isNull && isEmpty; -} - --(NSString*) getRedirectTypeFromToken: (NSString*) queueToken { - - if([self isNullOrEmpty:queueToken]) - { - return @"queue"; - } - - NSString *searchedString = queueToken; - NSRange searchedRange = NSMakeRange(0, [searchedString length]); - NSString *pattern = @"\\~rt_(.*?)\\~"; - NSError *error = nil; - - NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; - NSTextCheckingResult *match = [regex firstMatchInString:searchedString options:0 range: searchedRange]; - return [searchedString substringWithRange:[match rangeAtIndex:1]]; -} - -@end diff --git a/QueueITLib/QueueITWaitingRoomView.h b/QueueITLib/QueueITWaitingRoomView.h deleted file mode 100644 index ca4af3c..0000000 --- a/QueueITLib/QueueITWaitingRoomView.h +++ /dev/null @@ -1,29 +0,0 @@ -#import "QueueITWKViewController.h" -#import "QueueDisabledInfo.h" -#import "QueuePassedInfo.h" - -@protocol QueueITWaitingRoomViewDelegate; - -@interface QueueITWaitingRoomView : NSObject - -@property (nonatomic, weak)id _Nullable delegate; - --(instancetype _Nonnull)initWithHost:(UIViewController* _Nonnull)host - customerId: (NSString* _Nonnull)customerId - eventId: (NSString* _Nonnull)eventId; - --(void)show:(NSString* _Nonnull)queueUrl targetUrl:(NSString* _Nonnull)targetUrl; --(void)setViewDelay:(int)delayInterval; --(void)close:(void (^ __nullable)(void))onComplete; - -@end - -@protocol QueueITWaitingRoomViewDelegate --(void) notifyViewUserExited:(nonnull QueueITWaitingRoomView*)view; --(void) notifyViewUserClosed:(nonnull QueueITWaitingRoomView*)view; --(void) notifyViewSessionRestart:(nonnull QueueITWaitingRoomView*)view; --(void) waitingRoomView:(nonnull QueueITWaitingRoomView*)view notifyViewPassedQueue:(QueuePassedInfo* _Nullable)queuePassedInfo; --(void) notifyViewQueueDidAppear:(nonnull QueueITWaitingRoomView*)view; --(void) notifyViewQueueWillOpen:(nonnull QueueITWaitingRoomView*)view; --(void) waitingRoomView:(nonnull QueueITWaitingRoomView*)view notifyViewUpdatePageUrl:(NSString* _Nullable) urlString; -@end diff --git a/QueueITLib/QueueITWaitingRoomView.m b/QueueITLib/QueueITWaitingRoomView.m deleted file mode 100644 index 329095b..0000000 --- a/QueueITLib/QueueITWaitingRoomView.m +++ /dev/null @@ -1,100 +0,0 @@ -#import -#import "QueueITWaitingRoomView.h" -#import "QueueITWKViewController.h" - -@interface QueueITWaitingRoomView () -@property (nonatomic, weak) UIViewController* host; -@property (nonatomic, weak) QueueITWKViewController* currentWebView; -@property NSString* customerId; -@property NSString* eventId; -@property int delayInterval; -@end - -@implementation QueueITWaitingRoomView - --(instancetype _Nonnull)initWithHost:(UIViewController *)host - customerId: (NSString* _Nonnull) customerId - eventId: (NSString * _Nonnull)eventId - -{ - if(self = [super init]) { - self.host = host; - self.customerId = customerId; - self.eventId = eventId; - } - - return self; -} - --(void) show:(NSString* _Nonnull)queueUrl targetUrl:(NSString* _Nonnull)targetUrl -{ - [self raiseQueueViewWillOpen]; - - QueueITWKViewController *queueWKVC = [[QueueITWKViewController alloc] initWithHost:self.host - queueUrl:queueUrl - eventTargetUrl:targetUrl - customerId:self.customerId - eventId:self.eventId]; - - queueWKVC.delegate = self; - - if (@available(iOS 13.0, *)) { - [queueWKVC setModalPresentationStyle: UIModalPresentationFullScreen]; - } - if (self.delayInterval > 0) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.delayInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.host presentViewController:queueWKVC animated:YES completion:^{ - self.currentWebView = queueWKVC; - [self.delegate notifyViewQueueDidAppear:self ]; - }]; - }); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.host presentViewController:queueWKVC animated:YES completion:^{ - self.currentWebView = queueWKVC; - [self.delegate notifyViewQueueDidAppear:self ]; - }]; - }); - } -} - --(void)close:(void (^ __nullable)(void))onComplete -{ - if(self.currentWebView!=nil){ - dispatch_async(dispatch_get_main_queue(), ^{ - [self.currentWebView close: onComplete]; - }); - } -} - -- (void)raiseQueueViewWillOpen { - [self.delegate notifyViewQueueWillOpen:self]; -} - --(void)setViewDelay:(int)delayInterval { - self.delayInterval = delayInterval; -} - --(void) notifyViewControllerUserExited { - [self.delegate notifyViewUserExited:self]; -} - --(void) notifyViewControllerClosed { - [self.delegate notifyViewUserClosed:self]; -} - --(void) notifyViewControllerSessionRestart { - [self.delegate notifyViewSessionRestart:self]; -} - --(void) notifyViewControllerQueuePassed:(NSString *)queueToken { - QueuePassedInfo* queuePassedInfo = [[QueuePassedInfo alloc] initWithQueueitToken:queueToken]; - [self.delegate waitingRoomView:self notifyViewPassedQueue:queuePassedInfo]; -} - --(void)notifyViewControllerPageUrlChanged:(NSString* _Nullable) urlString { - [self.delegate waitingRoomView:self notifyViewUpdatePageUrl:urlString]; -} - -@end - diff --git a/QueueITLib/QueuePassedInfo.h b/QueueITLib/QueuePassedInfo.h deleted file mode 100644 index 36fcc6f..0000000 --- a/QueueITLib/QueuePassedInfo.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@interface QueuePassedInfo : NSObject - -@property (nonatomic, strong) NSString* _Nullable queueitToken; - --(instancetype _Nonnull )initWithQueueitToken:(NSString* _Nullable) queueitToken; - -@end diff --git a/QueueITLib/QueuePassedInfo.m b/QueueITLib/QueuePassedInfo.m deleted file mode 100644 index 66def51..0000000 --- a/QueueITLib/QueuePassedInfo.m +++ /dev/null @@ -1,14 +0,0 @@ -#import "QueuePassedInfo.h" - -@implementation QueuePassedInfo - --(instancetype)initWithQueueitToken:(NSString *)queueitToken -{ - if(self = [super init]) { - self.queueitToken = queueitToken; - } - - return self; -} - -@end diff --git a/QueueITLib/QueueStatus.h b/QueueITLib/QueueStatus.h deleted file mode 100644 index e3713d6..0000000 --- a/QueueITLib/QueueStatus.h +++ /dev/null @@ -1,12 +0,0 @@ -#import - -@interface QueueStatus : NSObject - -@property (nonatomic, strong) NSString* queueId; -@property (nonatomic, strong)NSString* queueUrlString; -@property (nonatomic, strong) NSString* eventTargetUrl; -@property (nonatomic, strong) NSString* queueitToken; - --(instancetype)initWithDictionary:(NSDictionary *)dictionary; - -@end diff --git a/QueueITLib/QueueStatus.m b/QueueITLib/QueueStatus.m deleted file mode 100644 index b53b58f..0000000 --- a/QueueITLib/QueueStatus.m +++ /dev/null @@ -1,59 +0,0 @@ -#import "QueueStatus.h" - -NSString * const KEY_QUEUE_ID = @"QueueId"; -NSString * const KEY_QUEUE_URL = @"QueueUrl"; -NSString * const KEY_EVENT_TARGET_URL = @"EventTargetUrl"; -NSString * const KEY_QUEUEIT_TOKEN = @"QueueitToken"; - -@implementation QueueStatus - --(instancetype)init:(NSString *)queueId - queueUrl:(NSString *)queueUrlString - eventTargetUrl:(NSString *)eventTargetUrl - queueitToken:(NSString *)queueitToken -{ - if(self = [super init]) { - self.queueId = queueId; - self.queueUrlString = queueUrlString; - self.eventTargetUrl = eventTargetUrl; - self.queueitToken = queueitToken; - } - - return self; -} - -- (instancetype)initWithDictionary:(NSDictionary *)dictionary -{ - NSString *queueId; - NSString *queueUrlString; - NSString *eventTargetUrl; - NSString *queueitToken; - id value; - - - value = dictionary[KEY_QUEUE_ID]; - if ([value isKindOfClass:[NSString class]]) { - queueId = (NSString*)value; - } - - value = dictionary[KEY_QUEUE_URL]; - if ([value isKindOfClass:[NSString class]]) { - queueUrlString = (NSString*)value; - } - - value = dictionary[KEY_EVENT_TARGET_URL]; - if ([value isKindOfClass:[NSString class]]) { - eventTargetUrl = (NSString*)value; - } - - value = dictionary[KEY_QUEUEIT_TOKEN]; - if ([value isKindOfClass:[NSString class]]) { - queueitToken = (NSString*)value; - } - - return [self init:queueId - queueUrl:queueUrlString - eventTargetUrl:eventTargetUrl - queueitToken:queueitToken]; -} -@end diff --git a/QueueITLib/QueueTryPassResult.h b/QueueITLib/QueueTryPassResult.h deleted file mode 100644 index 8abef8f..0000000 --- a/QueueITLib/QueueTryPassResult.h +++ /dev/null @@ -1,19 +0,0 @@ -#import - -@interface QueueTryPassResult : NSObject - -@property (nonatomic, strong) NSString* _Nullable queueUrl; -@property (nonatomic, strong) NSString* _Nullable targetUrl; -@property (nonatomic, strong) NSString* _Nonnull redirectType; -@property (nonatomic) BOOL isPassedThrough; -@property (nonatomic) NSString* _Nullable queueToken; - - --(instancetype _Nonnull ) - initWithQueueUrl: (NSString* _Nullable) queueUrl - targetUrl:(NSString* _Nullable)targetUrl - redirectType: (NSString* _Nonnull) redirectType - isPassedThrough: (BOOL) isPassedThrough - queueToken: (NSString* _Nullable) queueToken; - -@end diff --git a/QueueITLib/QueueTryPassResult.m b/QueueITLib/QueueTryPassResult.m deleted file mode 100644 index 76784c3..0000000 --- a/QueueITLib/QueueTryPassResult.m +++ /dev/null @@ -1,24 +0,0 @@ -#import "QueueTryPassResult.h" - - -@implementation QueueTryPassResult - --(instancetype _Nonnull ) - initWithQueueUrl: (NSString* _Nullable) queueUrl - targetUrl:(NSString* _Nullable)targetUrl - redirectType: (NSString* _Nonnull) redirectType - isPassedThrough: (BOOL) isPassedThrough - queueToken: (NSString* _Nullable) queueToken -{ - if(self = [super init]) { - self.queueUrl = queueUrl; - self.targetUrl = targetUrl; - self.redirectType = redirectType; - self.isPassedThrough = isPassedThrough; - self.queueToken = queueToken; - } - - return self; -} - -@end diff --git a/Sources/QueueITLib/NewConnection.swift b/Sources/QueueITLib/NewConnection.swift new file mode 100644 index 0000000..ce96456 --- /dev/null +++ b/Sources/QueueITLib/NewConnection.swift @@ -0,0 +1,44 @@ +import Foundation + +final class Connection: QueueITApiClient { + private var connectionRequest: ConnectionRequest? + + override func submitRequest( + with url: URL, + method httpMethod: String, + body bodyDict: [String: Any], + expectedStatus: Int, + success: @escaping QueueServiceSuccess, + failure: @escaping QueueServiceFailure + ) -> String { + var request = URLRequest(url: url) + request.httpMethod = httpMethod + + do { + let jsonData = try JSONSerialization.data(withJSONObject: bodyDict, options: []) + request.httpBody = jsonData + } catch { + failure(error, "Failed to serialize request body.") + return "" + } + + request.addValue("application/json", forHTTPHeaderField: "Accept") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + connectionRequest = ConnectionRequest( + request: request, + expectedStatusCode: expectedStatus, + success: success, + failure: failure, + delegate: self + ) + + return connectionRequest?.uniqueIdentifier ?? "" + } +} + +extension Connection: ConnectionRequestDelegate { + func requestDidComplete(_: ConnectionRequest) { + // Handle the completion of the request if needed + } +} diff --git a/Sources/QueueITLib/NewConnectionRequest.swift b/Sources/QueueITLib/NewConnectionRequest.swift new file mode 100644 index 0000000..08c2128 --- /dev/null +++ b/Sources/QueueITLib/NewConnectionRequest.swift @@ -0,0 +1,97 @@ +import Foundation + +protocol ConnectionRequestDelegate: AnyObject { + func requestDidComplete(_ request: ConnectionRequest) +} + +final class ConnectionRequest { + let uniqueIdentifier: String + + private var request: URLRequest + private var response: URLResponse? + private var data: Data + private var successCallback: QueueServiceSuccess + private var failureCallback: QueueServiceFailure + private weak var delegate: ConnectionRequestDelegate? + private var expectedStatusCode: Int + private var actualStatusCode: Int = NSNotFound + + init( + request: URLRequest, + expectedStatusCode: Int, + success: @escaping QueueServiceSuccess, + failure: @escaping QueueServiceFailure, + delegate: ConnectionRequestDelegate? + ) { + self.request = request + self.expectedStatusCode = expectedStatusCode + successCallback = success + failureCallback = failure + self.delegate = delegate + uniqueIdentifier = UUID().uuidString + data = Data() + initiateRequest() + } +} + +private extension ConnectionRequest { + func initiateRequest() { + response = nil + data = Data() + + let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in + guard let self else { return } + + if let error = error { + DispatchQueue.main.async { + self.failureCallback(error, "Unexpected failure occurred.") + self.delegate?.requestDidComplete(self) + } + return + } + + if let response = response as? HTTPURLResponse { + self.actualStatusCode = response.statusCode + self.response = response + } + + if let receivedData = data { + self.data.append(receivedData) + } + + DispatchQueue.main.async { + self.handleResponse() + } + } + task.resume() + } + + func handleResponse() { + if hasExpectedStatusCode() { + successCallback(data) + } else { + var message = "Unexpected response code: \(actualStatusCode)" + if actualStatusCode >= 400, actualStatusCode < 500 { + if let decodedMessage = String(data: data, encoding: .ascii) { + message = decodedMessage + } + } else if let json = try? JSONSerialization.jsonObject(with: data, options: []), + let jsonDict = json as? [String: Any], + let errorMessage = jsonDict["error"] as? String + { + message = errorMessage + } + + let error = NSError(domain: "QueueService", + code: actualStatusCode, + userInfo: [NSLocalizedDescriptionKey: message]) + failureCallback(error, message) + } + + delegate?.requestDidComplete(self) + } + + func hasExpectedStatusCode() -> Bool { + return actualStatusCode == expectedStatusCode + } +} diff --git a/Sources/QueueITLib/NewIOSUtils.swift b/Sources/QueueITLib/NewIOSUtils.swift new file mode 100644 index 0000000..1b15bf6 --- /dev/null +++ b/Sources/QueueITLib/NewIOSUtils.swift @@ -0,0 +1,40 @@ +import Foundation +import WebKit + +enum IOSUtils { + static func getUserId() -> String { + let device = UIDevice() + if let deviceId = device.identifierForVendor { + return deviceId.uuidString + } + return "" + } + + static func getUserAgent(completionHandler: @escaping (String) -> Void) { + DispatchQueue.main.async { + let view = WKWebView(frame: .zero) + view.evaluateJavaScript("navigator.userAgent") { result, error in + if let userAgent = result as? String, error == nil { + completionHandler(userAgent) + } else { + completionHandler("") + } + } + } + } + + static func getLibraryVersion() -> String { + if let infoDictionary = Bundle.main.infoDictionary, + let libName = infoDictionary[kCFBundleNameKey as String] as? String, + let major = infoDictionary["CFBundleShortVersionString"] as? String, + let minor = infoDictionary[kCFBundleVersionKey as String] as? String + { + return "\(libName)-\(major).\(minor)" + } + return "" + } + + static func getSdkVersion() -> String { + return QueueConsts.sdkVersion + } +} diff --git a/Sources/QueueITLib/NewQueueConsts.swift b/Sources/QueueITLib/NewQueueConsts.swift new file mode 100644 index 0000000..f2f6e7e --- /dev/null +++ b/Sources/QueueITLib/NewQueueConsts.swift @@ -0,0 +1,5 @@ +enum QueueConsts { + static let queueCloseUrl = "queueit://close" + static let queueRestartSessionUrl = "queueit://restartSession" + static let sdkVersion = "iOS-3.4.4" +} diff --git a/Sources/QueueITLib/NewQueueITApiClient.swift b/Sources/QueueITLib/NewQueueITApiClient.swift new file mode 100644 index 0000000..33e6cd9 --- /dev/null +++ b/Sources/QueueITLib/NewQueueITApiClient.swift @@ -0,0 +1,122 @@ +import Foundation + +typealias QueueServiceSuccess = (Data) -> Void +typealias QueueServiceFailure = (Error, String) -> Void + +class QueueITApiClient { + static let API_ROOT = "https://%@.queue-it.net/api/mobileapp/queue" + static let TESTING_API_ROOT = "https://%@.test.queue-it.net/api/mobileapp/queue" + private static var testingIsEnabled = false + private static var sharedInstance: QueueITApiClient? + + static func getInstance() -> QueueITApiClient { + if sharedInstance == nil { + sharedInstance = Connection() + } + return sharedInstance! + } + + static func setTesting(_ enabled: Bool) { + testingIsEnabled = enabled + } + + func enqueue( + customerId: String, + eventOrAliasId: String, + userId: String, + userAgent: String, + sdkVersion: String, + layoutName: String?, + language: String?, + enqueueToken: String?, + enqueueKey: String?, + success: @escaping (QueueStatus?) -> Void, + failure: @escaping QueueServiceFailure + ) -> String { + var bodyDict: [String: Any] = [ + "userId": userId, + "userAgent": userAgent, + "sdkVersion": sdkVersion, + ] + + if let layoutName = layoutName { + bodyDict["layoutName"] = layoutName + } + + if let language = language { + bodyDict["language"] = language + } + + if let enqueueToken = enqueueToken { + bodyDict["enqueueToken"] = enqueueToken + } + + if let enqueueKey = enqueueKey { + bodyDict["enqueueKey"] = enqueueKey + } + + let apiRoot = QueueITApiClient.testingIsEnabled ? QueueITApiClient.TESTING_API_ROOT : QueueITApiClient.API_ROOT + var urlAsString = String(format: apiRoot, customerId) + urlAsString += "/\(customerId)" + urlAsString += "/\(eventOrAliasId)" + urlAsString += "/enqueue" + + return submitPOSTPath( + path: urlAsString, + body: bodyDict, + success: { data in + do { + if let userDict = try JSONSerialization.jsonObject( + with: data, + options: [] + ) as? [String: Any] { + let queueStatus = QueueStatus(dictionary: userDict) + success(queueStatus) + } else { + success(nil) + } + } catch { + success(nil) + } + }, + failure: failure + ) + } + + func submitPOSTPath( + path: String, + body bodyDict: [String: Any], + success: @escaping QueueServiceSuccess, + failure: @escaping QueueServiceFailure + ) -> String { + guard let url = URL(string: path) else { + let error = NSError( + domain: "QueueITApiClient", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Invalid URL"] + ) + failure(error, "Invalid URL") + return "" + } + + return submitRequest( + with: url, + method: "POST", + body: bodyDict, + expectedStatus: 200, + success: success, + failure: failure + ) + } + + func submitRequest( + with _: URL, + method _: String, + body _: [String: Any], + expectedStatus _: Int, + success _: @escaping QueueServiceSuccess, + failure _: @escaping QueueServiceFailure + ) -> String { + return "" + } +} diff --git a/Sources/QueueITLib/NewQueueITEngine.swift b/Sources/QueueITLib/NewQueueITEngine.swift new file mode 100644 index 0000000..6f320be --- /dev/null +++ b/Sources/QueueITLib/NewQueueITEngine.swift @@ -0,0 +1,152 @@ +import UIKit + +public protocol QueuePassedDelegate: AnyObject { + func notifyYourTurn(queuePassedInfo: QueuePassedInfo?) +} + +public protocol QueueViewWillOpenDelegate: AnyObject { + func notifyQueueViewWillOpen() +} + +public protocol QueueDisabledDelegate: AnyObject { + func notifyQueueDisabled(queueDisabledInfo: QueueDisabledInfo?) +} + +public protocol QueueUnavailableDelegate: AnyObject { + func notifyQueueITUnavailable(errorMessage: String) +} + +public protocol QueueErrorDelegate: AnyObject { + func notifyQueueError(errorMessage: String, errorCode: Int) +} + +public protocol QueueViewClosedDelegate: AnyObject { + func notifyViewClosed() +} + +public protocol QueueUserExitedDelegate: AnyObject { + func notifyUserExited() +} + +public protocol QueueSessionRestartDelegate: AnyObject { + func notifySessionRestart() +} + +public protocol QueueUrlChangedDelegate: AnyObject { + func notifyQueueUrlChanged(url: String) +} + +public protocol QueueViewDidAppearDelegate: AnyObject { + func notifyQueueViewDidAppear() +} + +public final class QueueItEngine { + public weak var queuePassedDelegate: QueuePassedDelegate? + public weak var queueViewWillOpenDelegate: QueueViewWillOpenDelegate? + public weak var queueDisabledDelegate: QueueDisabledDelegate? + public weak var queueUnavailableDelegate: QueueUnavailableDelegate? + public weak var queueErrorDelegate: QueueErrorDelegate? + public weak var queueViewClosedDelegate: QueueViewClosedDelegate? + public weak var queueUserExitedDelegate: QueueUserExitedDelegate? + public weak var queueSessionRestartDelegate: QueueSessionRestartDelegate? + public weak var queueUrlChangedDelegate: QueueUrlChangedDelegate? + public weak var queueViewDidAppearDelegate: QueueViewDidAppearDelegate? + + public weak var host: UIViewController? + private var waitingRoomProvider: QueueITWaitingRoomProvider + private var waitingRoomView: QueueITWaitingRoomView + + public init(host: UIViewController, customerId: String, eventOrAliasId: String, layoutName: String?, language: String?) { + self.host = host + + waitingRoomProvider = QueueITWaitingRoomProvider( + customerId: customerId, + eventOrAliasId: eventOrAliasId, + layoutName: layoutName, + language: language + ) + waitingRoomView = QueueITWaitingRoomView(host: host, eventId: eventOrAliasId) + waitingRoomView.delegate = self + waitingRoomProvider.delegate = self + } + + public func setViewDelay(_ delayInterval: Int) { + waitingRoomView.setViewDelay(delayInterval) + } + + public func isRequestInProgress() -> Bool { + return waitingRoomProvider.isRequestInProgress() + } + + public func run(withEnqueueKey enqueueKey: String) throws -> Bool { + return try waitingRoomProvider.tryPassWithEnqueueKey(enqueueKey) + } + + public func run(withEnqueueToken enqueueToken: String) throws -> Bool { + return try waitingRoomProvider.tryPassWithEnqueueToken(enqueueToken) + } + + public func run() throws -> Bool { + return try waitingRoomProvider.tryPass() + } + + public func showQueue(queueUrl: String, targetUrl: String) { + waitingRoomView.show(queueUrl: queueUrl, targetUrl: targetUrl) + } +} + +extension QueueItEngine: QueueITWaitingRoomViewDelegate { + func notifyViewUserExited() { + queueUserExitedDelegate?.notifyUserExited() + } + + func notifyViewUserClosed() { + queueViewClosedDelegate?.notifyViewClosed() + } + + func notifyViewSessionRestart() { + queueSessionRestartDelegate?.notifySessionRestart() + } + + func notifyQueuePassed(info: QueuePassedInfo?) { + queuePassedDelegate?.notifyYourTurn(queuePassedInfo: info) + } + + func notifyViewQueueDidAppear() { + queueViewDidAppearDelegate?.notifyQueueViewDidAppear() + } + + func notifyViewQueueWillOpen() { + queueViewWillOpenDelegate?.notifyQueueViewWillOpen() + } + + func notifyViewUpdatePageUrl(urlString: String?) { + // TODO: fix optional parameter + queueUrlChangedDelegate?.notifyQueueUrlChanged(url: urlString ?? "") + } +} + +extension QueueItEngine: QueueITWaitingRoomProviderDelegate { + func notifyProviderSuccess(queuePassResult: QueueTryPassResult) { + switch queuePassResult.redirectType { + case "safetynet": + let queuePassedInfo = QueuePassedInfo(queueitToken: queuePassResult.queueToken) + queuePassedDelegate?.notifyYourTurn(queuePassedInfo: queuePassedInfo) + case "disabled", "idle", "afterevent": + let queueDisabledInfo = QueueDisabledInfo(queueitToken: queuePassResult.queueToken) + queueDisabledDelegate?.notifyQueueDisabled(queueDisabledInfo: queueDisabledInfo) + default: + // TODO: fix optional parameter + showQueue(queueUrl: queuePassResult.queueUrl ?? "", targetUrl: queuePassResult.targetUrl ?? "") + } + } + + func notifyProviderFailure(errorMessage: String?, errorCode: Int) { + // TODO: fix optional parameter + let errorMessage = errorMessage ?? "" + if errorCode == 3 { + queueUnavailableDelegate?.notifyQueueITUnavailable(errorMessage: errorMessage) + } + queueErrorDelegate?.notifyQueueError(errorMessage: errorMessage, errorCode: errorCode) + } +} diff --git a/Sources/QueueITLib/NewQueueITReachability.swift b/Sources/QueueITLib/NewQueueITReachability.swift new file mode 100644 index 0000000..3771353 --- /dev/null +++ b/Sources/QueueITLib/NewQueueITReachability.swift @@ -0,0 +1,118 @@ +import Foundation +import Network +import SystemConfiguration + + +extension Notification.Name { + static let reachabilityChanged = Notification.Name("kNetworkReachabilityChangedNotification") +} + +final class QueueITReachability { + private var monitor: NWPathMonitor? + private var isMonitoringLocalWiFi: Bool = false + private var queue = DispatchQueue.global(qos: .background) + + private init() {} + + static func reachabilityWithHostName(_ hostName: String) -> QueueITReachability { + let reachability = QueueITReachability() + reachability.startMonitoringHost(hostName: hostName) + return reachability + } + + static func reachabilityWithAddress(_ hostAddress: sockaddr_in) -> QueueITReachability { + let reachability = QueueITReachability() + reachability.startMonitoringIP(address: hostAddress) + return reachability + } + + static func reachabilityForInternetConnection() -> QueueITReachability { + let reachability = QueueITReachability() + reachability.startMonitoringInternet() + return reachability + } + + static func reachabilityForLocalWiFi() -> QueueITReachability { + let reachability = QueueITReachability() + reachability.startMonitoringWiFi() + return reachability + } + + func startNotifier() -> Bool { + guard monitor == nil else { return true } + monitor = NWPathMonitor() + monitor?.pathUpdateHandler = { [weak self] _ in + NotificationCenter.default.post(name: .reachabilityChanged, object: nil) + } + monitor?.start(queue: queue) + return true + } + + func stopNotifier() { + monitor?.cancel() + monitor = nil + } + + func currentReachabilityStatus() -> NetworkStatus { + guard let monitor = monitor else { return .notReachable } + let path = monitor.currentPath + if path.status == .unsatisfied { + return .notReachable + } + if path.usesInterfaceType(.wifi) { + return .reachableViaWiFi + } + if path.usesInterfaceType(.cellular) { + return .reachableViaWWAN + } + return .notReachable + } + + func connectionRequired() -> Bool { + guard let monitor = monitor else { return false } + return monitor.currentPath.status != .satisfied + } +} + +extension QueueITReachability { + enum NetworkStatus: Int { + case notReachable = 0 + case reachableViaWiFi + case reachableViaWWAN + } +} + +private extension QueueITReachability { + func startMonitoringHost(hostName _: String) { + monitor = NWPathMonitor(requiredInterfaceType: .other) + monitor?.pathUpdateHandler = { _ in + NotificationCenter.default.post(name: .reachabilityChanged, object: nil) + } + monitor?.start(queue: queue) + } + + func startMonitoringIP(address _: sockaddr_in) { + monitor = NWPathMonitor() + monitor?.pathUpdateHandler = { _ in + NotificationCenter.default.post(name: .reachabilityChanged, object: nil) + } + monitor?.start(queue: queue) + } + + func startMonitoringInternet() { + monitor = NWPathMonitor() + monitor?.pathUpdateHandler = { _ in + NotificationCenter.default.post(name: .reachabilityChanged, object: nil) + } + monitor?.start(queue: queue) + } + + func startMonitoringWiFi() { + isMonitoringLocalWiFi = true + monitor = NWPathMonitor(requiredInterfaceType: .wifi) + monitor?.pathUpdateHandler = { _ in + NotificationCenter.default.post(name: .reachabilityChanged, object: nil) + } + monitor?.start(queue: queue) + } +} diff --git a/Sources/QueueITLib/NewQueueITWKViewController.swift b/Sources/QueueITLib/NewQueueITWKViewController.swift new file mode 100644 index 0000000..bb68ca7 --- /dev/null +++ b/Sources/QueueITLib/NewQueueITWKViewController.swift @@ -0,0 +1,193 @@ +import UIKit +import WebKit + +protocol QueueITViewControllerDelegate: AnyObject { + func notifyViewControllerClosed() + func notifyViewControllerUserExited() + func notifyViewControllerSessionRestart() + func notifyViewControllerQueuePassed(queueToken: String?) + func notifyViewControllerPageUrlChanged(urlString: String?) +} + +final class QueueITWKViewController: UIViewController { + weak var delegate: QueueITViewControllerDelegate? + weak var webView: WKWebView? + + private var spinner: UIActivityIndicatorView? + private var isQueuePassed: Bool + + private var queueUrl: String + private var eventTargetUrl: String + private var eventId: String + + private let JAVASCRIPT_GET_BODY_CLASSES = "document.getElementsByTagName('body')[0].className" + + init(queueUrl: String, eventTargetUrl: String, eventId: String) { + self.queueUrl = queueUrl + self.eventTargetUrl = eventTargetUrl + self.eventId = eventId + isQueuePassed = false + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + let preferences = WKPreferences() + preferences.javaScriptEnabled = true + + let config = WKWebViewConfiguration() + config.preferences = preferences + + spinner = UIActivityIndicatorView(frame: view.bounds) + webView = WKWebView(frame: view.bounds, configuration: config) + + guard let spinner, let webView else { + return + } + + spinner.color = .gray + webView.navigationDelegate = self + webView.autoresizingMask = [.flexibleHeight, .flexibleWidth] + webView.isOpaque = false + webView.backgroundColor = .clear + + view.addSubview(webView) + view.addSubview(spinner) + + webView.frame = view.bounds + spinner.frame = view.bounds + } + + func loadWebView() { + guard let spinner, + let webView, + let url = URL(string: queueUrl) + else { + return + } + spinner.startAnimating() + webView.load(URLRequest(url: url)) + } +} + +extension QueueITWKViewController: WKNavigationDelegate { + func webView( + _: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void + ) { + if !isQueuePassed { + let request = navigationAction.request + let urlString = request.url?.absoluteString + let targetUrlString = eventTargetUrl + if let urlString, urlString != "about:blank" { + let url = URL(string: urlString)! + let targetUrl = URL(string: targetUrlString)! + let isQueueUrl = queueUrl.contains(url.host!) + let isNotFrame = request.url?.absoluteString == request.mainDocumentURL?.absoluteString + + + if url.absoluteString == QueueConsts.queueCloseUrl { + delegate?.notifyViewControllerClosed() + decisionHandler(.cancel) + return + } else if url.absoluteString == QueueConsts.queueRestartSessionUrl { + delegate?.notifyViewControllerSessionRestart() + decisionHandler(.cancel) + return + } + + if isBlockedUrl(destinationUrl: url) { + decisionHandler(.cancel) + return + } + + if isNotFrame { + if isQueueUrl { + raiseQueuePageUrl(urlString) + } + if isTargetUrl(targetUrl: targetUrl, destinationUrl: url) { + isQueuePassed = true + let queueitToken = extractQueueToken(urlString) + delegate?.notifyViewControllerQueuePassed(queueToken: queueitToken) + decisionHandler(.cancel) + return + } + } + + if navigationAction.navigationType == .linkActivated && !isQueueUrl { + UIApplication.shared.open(request.url!) + decisionHandler(.cancel) + return + } + } + } + + decisionHandler(.allow) + } + + func webView(_: WKWebView, didStartProvisionalNavigation _: WKNavigation!) {} + + func webView(_: WKWebView, didFinish _: WKNavigation!) { + NotificationCenter.default.addObserver( + self, + selector: #selector(appWillResignActive(_:)), + name: UIApplication.willResignActiveNotification, + object: nil + ) + + guard let spinner, let webView else { + return + } + + spinner.stopAnimating() + webView.evaluateJavaScript(JAVASCRIPT_GET_BODY_CLASSES) { [weak self] result, error in + guard let self else { + return + } + if let error { + print("evaluateJavaScript error: \(error.localizedDescription)") + } else if let resultString = result as? String { + let htmlBodyClasses = resultString.split(separator: " ") + let isExitClassPresent = htmlBodyClasses.contains("exit") + if isExitClassPresent { + self.delegate?.notifyViewControllerUserExited() + } + } + } + } +} + +private extension QueueITWKViewController { + func isTargetUrl(targetUrl: URL, destinationUrl: URL) -> Bool { + return destinationUrl.host == targetUrl.host && destinationUrl.path == targetUrl.path + } + + func isBlockedUrl(destinationUrl: URL) -> Bool { + return destinationUrl.path.hasPrefix("/what-is-this.html") + } + + func extractQueueToken(_ url: String) -> String? { + let tokenKey = "queueittoken=" + if let range = url.range(of: tokenKey) { + var token = String(url[range.upperBound...]) + if let ampersandRange = token.range(of: "&") { + token = String(token[.. Bool { + return try tryEnqueue(enqueueToken: nil, enqueueKey: nil) + } + + func tryPassWithEnqueueToken(_ enqueueToken: String?) throws -> Bool { + return try tryEnqueue(enqueueToken: enqueueToken, enqueueKey: nil) + } + + func tryPassWithEnqueueKey(_ enqueueKey: String?) throws -> Bool { + return try tryEnqueue(enqueueToken: nil, enqueueKey: enqueueKey) + } + + func isRequestInProgress() -> Bool { + return requestInProgress + } +} + +private extension QueueITWaitingRoomProvider { + func tryEnqueue(enqueueToken: String?, enqueueKey: String?) throws -> Bool { + guard checkConnection() else { + throw NSError( + domain: "QueueITRuntimeException", + code: QueueITRuntimeError.networkUnavailable.rawValue, + userInfo: nil + ) + } + + if requestInProgress { + throw NSError( + domain: "QueueITRuntimeException", + code: QueueITRuntimeError.requestAlreadyInProgress.rawValue, + userInfo: nil + ) + } + + requestInProgress = true + + IOSUtils.getUserAgent { [weak self] userAgent in + guard let self else { + return + } + do { + try self.tryEnqueueWithUserAgent( + secretAgent: userAgent, + enqueueToken: enqueueToken, + enqueueKey: enqueueKey + ) + } catch { + self.requestInProgress = false + self.delegate?.notifyProviderFailure( + errorMessage: error.localizedDescription, + errorCode: (error as NSError).code + ) + } + } + return true + } + + func tryEnqueueWithUserAgent(secretAgent: String, enqueueToken: String?, enqueueKey: String?) throws { + let userId = IOSUtils.getUserId() + let userAgent = "\(secretAgent);\(IOSUtils.getLibraryVersion())" + let sdkVersion = IOSUtils.getSdkVersion() + let apiClient = QueueITApiClient.getInstance() + + apiClient.enqueue( + customerId: customerId, + eventOrAliasId: eventOrAliasId, + userId: userId, + userAgent: userAgent, + sdkVersion: sdkVersion, + layoutName: layoutName, + language: language, + enqueueToken: enqueueToken, + enqueueKey: enqueueKey, + success: { [weak self] queueStatus in + guard let self else { + return + } + guard let queueStatus else { + self.enqueueRetryMonitor(enqueueToken: enqueueToken, enqueueKey: enqueueKey) + return + } + + self.handleAppEnqueueResponse( + queueURL: queueStatus.queueUrlString, + eventTargetURL: queueStatus.eventTargetUrl, + queueItToken: queueStatus.queueitToken + ) + self.requestInProgress = false + }, + failure: { [weak self] error, errorMessage in + guard let self else { + return + } + if let nsError = error as? NSError { + if nsError.code >= 400, nsError.code < 500 { + self.delegate?.notifyProviderFailure(errorMessage: errorMessage, errorCode: nsError.code) + } else { + self.enqueueRetryMonitor(enqueueToken: enqueueToken, enqueueKey: enqueueKey) + } + } + } + ) + } + + func handleAppEnqueueResponse( + queueURL: String, + eventTargetURL: String?, + queueItToken: String? + ) { + let isPassedThrough = !(queueItToken?.isEmpty ?? true) + let redirectType = getRedirectType(fromToken: queueItToken) + + let queueTryPassResult = QueueTryPassResult( + queueUrl: queueURL, + targetUrl: eventTargetURL, + redirectType: redirectType, + isPassedThrough: isPassedThrough, + queueToken: queueItToken + ) + delegate?.notifyProviderSuccess(queuePassResult: queueTryPassResult) + } + + func enqueueRetryMonitor(enqueueToken: String?, enqueueKey: String?) { + if deltaSec < QueueITWaitingRoomProvider.maxRetrySec { + try? tryEnqueue(enqueueToken: enqueueToken, enqueueKey: enqueueKey) + Thread.sleep(forTimeInterval: TimeInterval(deltaSec)) + deltaSec *= 2 + } else { + deltaSec = QueueITWaitingRoomProvider.initialWaitRetrySec + requestInProgress = false + delegate?.notifyProviderFailure(errorMessage: "Error! Queue is unavailable.", errorCode: 3) + } + } + + func checkConnection() -> Bool { + for _ in 0 ..< 5 { + if internetReachability.currentReachabilityStatus() != .notReachable { + return true + } + Thread.sleep(forTimeInterval: 1.0) + } + return false + } + + func getRedirectType(fromToken queueToken: String?) -> String { + guard let token = queueToken, !token.isEmpty else { + return "queue" + } + + let pattern = "\\~rt_(.*?)\\~" + if let regex = try? NSRegularExpression(pattern: pattern), + let match = regex.firstMatch(in: token, range: NSRange(token.startIndex..., in: token)) + { + return String(token[Range(match.range(at: 1), in: token)!]) + } + return "queue" + } +} diff --git a/Sources/QueueITLib/NewQueueITWaitingRoomView.swift b/Sources/QueueITLib/NewQueueITWaitingRoomView.swift new file mode 100644 index 0000000..d951c29 --- /dev/null +++ b/Sources/QueueITLib/NewQueueITWaitingRoomView.swift @@ -0,0 +1,100 @@ +import UIKit + +protocol QueueITWaitingRoomViewDelegate: AnyObject { + func notifyViewUserExited() + func notifyViewUserClosed() + func notifyViewSessionRestart() + func notifyQueuePassed(info: QueuePassedInfo?) + func notifyViewQueueDidAppear() + func notifyViewQueueWillOpen() + func notifyViewUpdatePageUrl(urlString: String?) +} + +final class QueueITWaitingRoomView { + weak var host: UIViewController? + weak var delegate: QueueITWaitingRoomViewDelegate? + weak var currentWebView: QueueITWKViewController? + + private var eventId: String + private var delayInterval: Int = 0 + + init(host: UIViewController, eventId: String) { + self.host = host + self.eventId = eventId + } + + func show(queueUrl: String, targetUrl: String) { + raiseQueueViewWillOpen() + + let queueWKVC = QueueITWKViewController( + queueUrl: queueUrl, + eventTargetUrl: targetUrl, + eventId: eventId + ) + + queueWKVC.delegate = self + + if #available(iOS 13.0, *) { + queueWKVC.modalPresentationStyle = UIModalPresentationStyle.fullScreen + } + + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(delayInterval)) { [weak self] in + guard let self else { + return + } + self.host?.present(queueWKVC, animated: true, completion: { [weak self] in + guard let self else { + return + } + self.currentWebView = queueWKVC + self.currentWebView?.loadWebView() + self.delegate?.notifyViewQueueDidAppear() + }) + } + } + + func setViewDelay(_ delayInterval: Int) { + self.delayInterval = delayInterval + } +} + +extension QueueITWaitingRoomView: QueueITViewControllerDelegate { + func notifyViewControllerClosed() { + delegate?.notifyViewUserClosed() + close() + } + + func notifyViewControllerUserExited() { + delegate?.notifyViewUserExited() + } + + func notifyViewControllerSessionRestart() { + delegate?.notifyViewSessionRestart() + close() + } + + func notifyViewControllerQueuePassed(queueToken: String?) { + let queuePassedInfo = QueuePassedInfo(queueitToken: queueToken) + delegate?.notifyQueuePassed(info: queuePassedInfo) + close() + } + + func notifyViewControllerPageUrlChanged(urlString: String?) { + delegate?.notifyViewUpdatePageUrl(urlString: urlString) + } +} + +private extension QueueITWaitingRoomView { + func close(onComplete: (() -> Void)? = nil) { + DispatchQueue.main.async { [weak self] in + guard let self, let host else { + return + } + host.dismiss(animated: true) + } + } + + func raiseQueueViewWillOpen() { + delegate?.notifyViewQueueWillOpen() + } +} diff --git a/Sources/QueueITLib/NewQueueInfo.swift b/Sources/QueueITLib/NewQueueInfo.swift new file mode 100644 index 0000000..5411c03 --- /dev/null +++ b/Sources/QueueITLib/NewQueueInfo.swift @@ -0,0 +1,17 @@ +import Foundation + +public struct QueuePassedInfo { + public var queueitToken: String? + + public init(queueitToken: String?) { + self.queueitToken = queueitToken + } +} + +public struct QueueDisabledInfo { + public var queueitToken: String? + + public init(queueitToken: String?) { + self.queueitToken = queueitToken + } +} diff --git a/Sources/QueueITLib/NewQueueStatus.swift b/Sources/QueueITLib/NewQueueStatus.swift new file mode 100644 index 0000000..f66106b --- /dev/null +++ b/Sources/QueueITLib/NewQueueStatus.swift @@ -0,0 +1,31 @@ +struct QueueStatus { + let queueId: String + let queueUrlString: String + let eventTargetUrl: String + let queueitToken: String + + init(queueId: String, queueUrl: String, eventTargetUrl: String, queueitToken: String) { + self.queueId = queueId + self.queueUrlString = queueUrl + self.eventTargetUrl = eventTargetUrl + self.queueitToken = queueitToken + } + + init(dictionary: [String: Any]) { + self.init( + queueId: dictionary[Constants.KEY_QUEUE_ID] as? String ?? "", + queueUrl: dictionary[Constants.KEY_QUEUE_URL] as? String ?? "", + eventTargetUrl: dictionary[Constants.KEY_EVENT_TARGET_URL] as? String ?? "", + queueitToken: dictionary[Constants.KEY_QUEUEIT_TOKEN] as? String ?? "" + ) + } +} + +private extension QueueStatus { + enum Constants { + static let KEY_QUEUE_ID = "QueueId" + static let KEY_QUEUE_URL = "QueueUrl" + static let KEY_EVENT_TARGET_URL = "EventTargetUrl" + static let KEY_QUEUEIT_TOKEN = "QueueitToken" + } +} diff --git a/Sources/QueueITLib/NewQueueTryPassResult.swift b/Sources/QueueITLib/NewQueueTryPassResult.swift new file mode 100644 index 0000000..94405f4 --- /dev/null +++ b/Sources/QueueITLib/NewQueueTryPassResult.swift @@ -0,0 +1,9 @@ +import Foundation + +struct QueueTryPassResult { + let queueUrl: String? + let targetUrl: String? + let redirectType: String + let isPassedThrough: Bool + let queueToken: String? +} From 2d152b2bd7ce8b24285a1944a200f940b3905420 Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Wed, 11 Dec 2024 19:35:31 +0100 Subject: [PATCH 02/13] refactor: remove redundant return types Removed redundant return types from methods in QueueItEngine and QueueITWaitingRoomProvider classes. This change simplifies the code by eliminating unnecessary return statements. --- Sources/QueueITLib/NewConnection.swift | 6 ++---- Sources/QueueITLib/NewQueueITApiClient.swift | 14 +++++++------- Sources/QueueITLib/NewQueueITEngine.swift | 12 ++++++------ .../NewQueueITWaitingRoomProvider.swift | 15 +++++++-------- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/Sources/QueueITLib/NewConnection.swift b/Sources/QueueITLib/NewConnection.swift index ce96456..3d3b11e 100644 --- a/Sources/QueueITLib/NewConnection.swift +++ b/Sources/QueueITLib/NewConnection.swift @@ -10,7 +10,7 @@ final class Connection: QueueITApiClient { expectedStatus: Int, success: @escaping QueueServiceSuccess, failure: @escaping QueueServiceFailure - ) -> String { + ) { var request = URLRequest(url: url) request.httpMethod = httpMethod @@ -19,7 +19,7 @@ final class Connection: QueueITApiClient { request.httpBody = jsonData } catch { failure(error, "Failed to serialize request body.") - return "" + return } request.addValue("application/json", forHTTPHeaderField: "Accept") @@ -32,8 +32,6 @@ final class Connection: QueueITApiClient { failure: failure, delegate: self ) - - return connectionRequest?.uniqueIdentifier ?? "" } } diff --git a/Sources/QueueITLib/NewQueueITApiClient.swift b/Sources/QueueITLib/NewQueueITApiClient.swift index 33e6cd9..231142f 100644 --- a/Sources/QueueITLib/NewQueueITApiClient.swift +++ b/Sources/QueueITLib/NewQueueITApiClient.swift @@ -32,7 +32,7 @@ class QueueITApiClient { enqueueKey: String?, success: @escaping (QueueStatus?) -> Void, failure: @escaping QueueServiceFailure - ) -> String { + ) { var bodyDict: [String: Any] = [ "userId": userId, "userAgent": userAgent, @@ -61,7 +61,7 @@ class QueueITApiClient { urlAsString += "/\(eventOrAliasId)" urlAsString += "/enqueue" - return submitPOSTPath( + submitPOSTPath( path: urlAsString, body: bodyDict, success: { data in @@ -88,7 +88,7 @@ class QueueITApiClient { body bodyDict: [String: Any], success: @escaping QueueServiceSuccess, failure: @escaping QueueServiceFailure - ) -> String { + ) { guard let url = URL(string: path) else { let error = NSError( domain: "QueueITApiClient", @@ -96,10 +96,10 @@ class QueueITApiClient { userInfo: [NSLocalizedDescriptionKey: "Invalid URL"] ) failure(error, "Invalid URL") - return "" + return } - return submitRequest( + submitRequest( with: url, method: "POST", body: bodyDict, @@ -116,7 +116,7 @@ class QueueITApiClient { expectedStatus _: Int, success _: @escaping QueueServiceSuccess, failure _: @escaping QueueServiceFailure - ) -> String { - return "" + ) { + return } } diff --git a/Sources/QueueITLib/NewQueueITEngine.swift b/Sources/QueueITLib/NewQueueITEngine.swift index 6f320be..48034a6 100644 --- a/Sources/QueueITLib/NewQueueITEngine.swift +++ b/Sources/QueueITLib/NewQueueITEngine.swift @@ -78,16 +78,16 @@ public final class QueueItEngine { return waitingRoomProvider.isRequestInProgress() } - public func run(withEnqueueKey enqueueKey: String) throws -> Bool { - return try waitingRoomProvider.tryPassWithEnqueueKey(enqueueKey) + public func run(withEnqueueKey enqueueKey: String) throws { + try waitingRoomProvider.tryPassWithEnqueueKey(enqueueKey) } - public func run(withEnqueueToken enqueueToken: String) throws -> Bool { - return try waitingRoomProvider.tryPassWithEnqueueToken(enqueueToken) + public func run(withEnqueueToken enqueueToken: String) throws { + try waitingRoomProvider.tryPassWithEnqueueToken(enqueueToken) } - public func run() throws -> Bool { - return try waitingRoomProvider.tryPass() + public func run() throws { + try waitingRoomProvider.tryPass() } public func showQueue(queueUrl: String, targetUrl: String) { diff --git a/Sources/QueueITLib/NewQueueITWaitingRoomProvider.swift b/Sources/QueueITLib/NewQueueITWaitingRoomProvider.swift index 6c07d32..7bbde19 100644 --- a/Sources/QueueITLib/NewQueueITWaitingRoomProvider.swift +++ b/Sources/QueueITLib/NewQueueITWaitingRoomProvider.swift @@ -38,16 +38,16 @@ final class QueueITWaitingRoomProvider { internetReachability = QueueITReachability.reachabilityForInternetConnection() } - func tryPass() throws -> Bool { - return try tryEnqueue(enqueueToken: nil, enqueueKey: nil) + func tryPass() throws { + try tryEnqueue(enqueueToken: nil, enqueueKey: nil) } - func tryPassWithEnqueueToken(_ enqueueToken: String?) throws -> Bool { - return try tryEnqueue(enqueueToken: enqueueToken, enqueueKey: nil) + func tryPassWithEnqueueToken(_ enqueueToken: String?) throws { + try tryEnqueue(enqueueToken: enqueueToken, enqueueKey: nil) } - func tryPassWithEnqueueKey(_ enqueueKey: String?) throws -> Bool { - return try tryEnqueue(enqueueToken: nil, enqueueKey: enqueueKey) + func tryPassWithEnqueueKey(_ enqueueKey: String?) throws { + try tryEnqueue(enqueueToken: nil, enqueueKey: enqueueKey) } func isRequestInProgress() -> Bool { @@ -56,7 +56,7 @@ final class QueueITWaitingRoomProvider { } private extension QueueITWaitingRoomProvider { - func tryEnqueue(enqueueToken: String?, enqueueKey: String?) throws -> Bool { + func tryEnqueue(enqueueToken: String?, enqueueKey: String?) throws { guard checkConnection() else { throw NSError( domain: "QueueITRuntimeException", @@ -93,7 +93,6 @@ private extension QueueITWaitingRoomProvider { ) } } - return true } func tryEnqueueWithUserAgent(secretAgent: String, enqueueToken: String?, enqueueKey: String?) throws { From b1e643c0f1e69c2d226d0a813679ca7f03e68d0f Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Wed, 11 Dec 2024 20:58:17 +0100 Subject: [PATCH 03/13] feat: rename files and classes for consistency Renamed various files and classes to improve naming consistency across the project. Updated references in the README.md and source files to reflect these changes. This includes renaming classes like QueueITApiClient to ApiClient, QueueITWaitingRoomProvider to WaitingRoomProvider, and others. --- .../contents.xcworkspacedata | 7 +++ README.md | 14 +++--- ...QueueITApiClient.swift => ApiClient.swift} | 16 +++---- .../{NewConnection.swift => Connection.swift} | 2 +- ...nRequest.swift => ConnectionRequest.swift} | 0 .../{NewQueueConsts.swift => Constants.swift} | 2 +- ...ueueITEngine.swift => QueueITEngine.swift} | 14 +++--- .../{NewQueueInfo.swift => QueueInfo.swift} | 0 ...TReachability.swift => Reachability.swift} | 22 +++++----- .../{NewQueueStatus.swift => Status.swift} | 4 +- ...ryPassResult.swift => TryPassResult.swift} | 2 +- .../{NewIOSUtils.swift => Utils.swift} | 4 +- ...ovider.swift => WaitingRoomProvider.swift} | 44 +++++++++---------- ...ngRoomView.swift => WaitingRoomView.swift} | 14 +++--- ...ntroller.swift => WebViewController.swift} | 14 +++--- 15 files changed, 83 insertions(+), 76 deletions(-) create mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata rename Sources/QueueITLib/{NewQueueITApiClient.swift => ApiClient.swift} (86%) rename Sources/QueueITLib/{NewConnection.swift => Connection.swift} (96%) rename Sources/QueueITLib/{NewConnectionRequest.swift => ConnectionRequest.swift} (100%) rename Sources/QueueITLib/{NewQueueConsts.swift => Constants.swift} (89%) rename Sources/QueueITLib/{NewQueueITEngine.swift => QueueITEngine.swift} (91%) rename Sources/QueueITLib/{NewQueueInfo.swift => QueueInfo.swift} (100%) rename Sources/QueueITLib/{NewQueueITReachability.swift => Reachability.swift} (86%) rename Sources/QueueITLib/{NewQueueStatus.swift => Status.swift} (94%) rename Sources/QueueITLib/{NewQueueTryPassResult.swift => TryPassResult.swift} (85%) rename Sources/QueueITLib/{NewIOSUtils.swift => Utils.swift} (95%) rename Sources/QueueITLib/{NewQueueITWaitingRoomProvider.swift => WaitingRoomProvider.swift} (80%) rename Sources/QueueITLib/{NewQueueITWaitingRoomView.swift => WaitingRoomView.swift} (87%) rename Sources/QueueITLib/{NewQueueITWKViewController.swift => WebViewController.swift} (93%) diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/README.md b/README.md index 4c870ec..ab38737 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ When the user clicks back, the same check needs to be done. ### Getting the status of a waiting room -If you're using version ```3.1.14``` or newer, it's possible to get the state of the waiting room using the new ```QueueITWaitingRoomProvider``` with one of the following methods: +If you're using version ```3.1.14``` or newer, it's possible to get the state of the waiting room using the new ```WaitingRoomProvider``` with one of the following methods: * ```TryPass``` * ```TryPassWithEnqueueToken``` @@ -176,21 +176,21 @@ If you're using version ```3.1.14``` or newer, it's possible to get the state of Calling one of the above methods will trigger either the ```notifyProviderSuccess``` callback on success, or ```notifyProviderFailure``` callback on failure. -When using the ```notifyProviderQueueITUnavailable``` from the ```ProviderSuccessDelegate``` it'll provide with a ```QueueTryPassResult``` depending on the ```isPassThrough``` result: +When using the ```notifyProviderQueueITUnavailable``` from the ```ProviderSuccessDelegate``` it'll provide with a ```TryPassResult``` depending on the ```isPassThrough``` result: -* ```true``` means that the ```QueueItToken``` is *not* empty, and more information is available in the ```QueueTryPassResult``` -* ```false``` means that the waiting room is *active*. You can show the visitor the waiting room by calling ```show``` from the ```QueueITWaitingRoomView```, by providing a ```queueUrl``` and ```targetUrl``` *([Read more about it here](#showing-the-queue-page-to-visitors))* +* ```true``` means that the ```QueueItToken``` is *not* empty, and more information is available in the ```TryPassResult``` +* ```false``` means that the waiting room is *active*. You can show the visitor the waiting room by calling ```show``` from the ```WaitingRoomView```, by providing a ```queueUrl``` and ```targetUrl``` *([Read more about it here](#showing-the-queue-page-to-visitors))* ### Showing the queue page to visitors -If you're using version ```3.1.14``` or newer, the ```QueueITWaitingRoomView``` class is available. +If you're using version ```3.1.14``` or newer, the ```WaitingRoomView``` class is available. -When the waiting room is queueing visitors, each visitor has to visit it once. Using the ```show``` method you can do this, you have to provide the ```queueUrl```, and the ```targetUrl``` which is returned by the ```notifyProviderSuccess``` from ```QueueITWaitingRoomProvider``` class, given the waiting room is *active* ([Read more about it here](#getting-the-status-of-a-waiting-room)) +When the waiting room is queueing visitors, each visitor has to visit it once. Using the ```show``` method you can do this, you have to provide the ```queueUrl```, and the ```targetUrl``` which is returned by the ```notifyProviderSuccess``` from ```WaitingRoomProvider``` class, given the waiting room is *active* ([Read more about it here](#getting-the-status-of-a-waiting-room)) #### Sample code showing the queue page: ``` objc --(void)notifyProviderSuccess:(QueueTryPassResult* _Nonnull) queuePassResult { +-(void)notifyProviderSuccess:(TryPassResult* _Nonnull) queuePassResult { [self.waitingRoomView show:queuePassResult.queueUrl targetUrl:queuePassResult.targetUrl]; } ``` diff --git a/Sources/QueueITLib/NewQueueITApiClient.swift b/Sources/QueueITLib/ApiClient.swift similarity index 86% rename from Sources/QueueITLib/NewQueueITApiClient.swift rename to Sources/QueueITLib/ApiClient.swift index 231142f..3676bb0 100644 --- a/Sources/QueueITLib/NewQueueITApiClient.swift +++ b/Sources/QueueITLib/ApiClient.swift @@ -3,13 +3,13 @@ import Foundation typealias QueueServiceSuccess = (Data) -> Void typealias QueueServiceFailure = (Error, String) -> Void -class QueueITApiClient { +class ApiClient { static let API_ROOT = "https://%@.queue-it.net/api/mobileapp/queue" static let TESTING_API_ROOT = "https://%@.test.queue-it.net/api/mobileapp/queue" private static var testingIsEnabled = false - private static var sharedInstance: QueueITApiClient? + private static var sharedInstance: ApiClient? - static func getInstance() -> QueueITApiClient { + static func getInstance() -> ApiClient { if sharedInstance == nil { sharedInstance = Connection() } @@ -30,7 +30,7 @@ class QueueITApiClient { language: String?, enqueueToken: String?, enqueueKey: String?, - success: @escaping (QueueStatus?) -> Void, + success: @escaping (Status?) -> Void, failure: @escaping QueueServiceFailure ) { var bodyDict: [String: Any] = [ @@ -55,7 +55,7 @@ class QueueITApiClient { bodyDict["enqueueKey"] = enqueueKey } - let apiRoot = QueueITApiClient.testingIsEnabled ? QueueITApiClient.TESTING_API_ROOT : QueueITApiClient.API_ROOT + let apiRoot = ApiClient.testingIsEnabled ? ApiClient.TESTING_API_ROOT : ApiClient.API_ROOT var urlAsString = String(format: apiRoot, customerId) urlAsString += "/\(customerId)" urlAsString += "/\(eventOrAliasId)" @@ -70,8 +70,8 @@ class QueueITApiClient { with: data, options: [] ) as? [String: Any] { - let queueStatus = QueueStatus(dictionary: userDict) - success(queueStatus) + let Status = Status(dictionary: userDict) + success(Status) } else { success(nil) } @@ -91,7 +91,7 @@ class QueueITApiClient { ) { guard let url = URL(string: path) else { let error = NSError( - domain: "QueueITApiClient", + domain: "ApiClient", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"] ) diff --git a/Sources/QueueITLib/NewConnection.swift b/Sources/QueueITLib/Connection.swift similarity index 96% rename from Sources/QueueITLib/NewConnection.swift rename to Sources/QueueITLib/Connection.swift index 3d3b11e..0c6f9b6 100644 --- a/Sources/QueueITLib/NewConnection.swift +++ b/Sources/QueueITLib/Connection.swift @@ -1,6 +1,6 @@ import Foundation -final class Connection: QueueITApiClient { +final class Connection: ApiClient { private var connectionRequest: ConnectionRequest? override func submitRequest( diff --git a/Sources/QueueITLib/NewConnectionRequest.swift b/Sources/QueueITLib/ConnectionRequest.swift similarity index 100% rename from Sources/QueueITLib/NewConnectionRequest.swift rename to Sources/QueueITLib/ConnectionRequest.swift diff --git a/Sources/QueueITLib/NewQueueConsts.swift b/Sources/QueueITLib/Constants.swift similarity index 89% rename from Sources/QueueITLib/NewQueueConsts.swift rename to Sources/QueueITLib/Constants.swift index f2f6e7e..7f9e5b8 100644 --- a/Sources/QueueITLib/NewQueueConsts.swift +++ b/Sources/QueueITLib/Constants.swift @@ -1,4 +1,4 @@ -enum QueueConsts { +enum Constants { static let queueCloseUrl = "queueit://close" static let queueRestartSessionUrl = "queueit://restartSession" static let sdkVersion = "iOS-3.4.4" diff --git a/Sources/QueueITLib/NewQueueITEngine.swift b/Sources/QueueITLib/QueueITEngine.swift similarity index 91% rename from Sources/QueueITLib/NewQueueITEngine.swift rename to Sources/QueueITLib/QueueITEngine.swift index 48034a6..884e59d 100644 --- a/Sources/QueueITLib/NewQueueITEngine.swift +++ b/Sources/QueueITLib/QueueITEngine.swift @@ -53,19 +53,19 @@ public final class QueueItEngine { public weak var queueViewDidAppearDelegate: QueueViewDidAppearDelegate? public weak var host: UIViewController? - private var waitingRoomProvider: QueueITWaitingRoomProvider - private var waitingRoomView: QueueITWaitingRoomView + private var waitingRoomProvider: WaitingRoomProvider + private var waitingRoomView: WaitingRoomView public init(host: UIViewController, customerId: String, eventOrAliasId: String, layoutName: String?, language: String?) { self.host = host - waitingRoomProvider = QueueITWaitingRoomProvider( + waitingRoomProvider = WaitingRoomProvider( customerId: customerId, eventOrAliasId: eventOrAliasId, layoutName: layoutName, language: language ) - waitingRoomView = QueueITWaitingRoomView(host: host, eventId: eventOrAliasId) + waitingRoomView = WaitingRoomView(host: host, eventId: eventOrAliasId) waitingRoomView.delegate = self waitingRoomProvider.delegate = self } @@ -95,7 +95,7 @@ public final class QueueItEngine { } } -extension QueueItEngine: QueueITWaitingRoomViewDelegate { +extension QueueItEngine: WaitingRoomViewDelegate { func notifyViewUserExited() { queueUserExitedDelegate?.notifyUserExited() } @@ -126,8 +126,8 @@ extension QueueItEngine: QueueITWaitingRoomViewDelegate { } } -extension QueueItEngine: QueueITWaitingRoomProviderDelegate { - func notifyProviderSuccess(queuePassResult: QueueTryPassResult) { +extension QueueItEngine: WaitingRoomProviderDelegate { + func notifyProviderSuccess(queuePassResult: TryPassResult) { switch queuePassResult.redirectType { case "safetynet": let queuePassedInfo = QueuePassedInfo(queueitToken: queuePassResult.queueToken) diff --git a/Sources/QueueITLib/NewQueueInfo.swift b/Sources/QueueITLib/QueueInfo.swift similarity index 100% rename from Sources/QueueITLib/NewQueueInfo.swift rename to Sources/QueueITLib/QueueInfo.swift diff --git a/Sources/QueueITLib/NewQueueITReachability.swift b/Sources/QueueITLib/Reachability.swift similarity index 86% rename from Sources/QueueITLib/NewQueueITReachability.swift rename to Sources/QueueITLib/Reachability.swift index 3771353..79236ed 100644 --- a/Sources/QueueITLib/NewQueueITReachability.swift +++ b/Sources/QueueITLib/Reachability.swift @@ -7,33 +7,33 @@ extension Notification.Name { static let reachabilityChanged = Notification.Name("kNetworkReachabilityChangedNotification") } -final class QueueITReachability { +final class Reachability { private var monitor: NWPathMonitor? private var isMonitoringLocalWiFi: Bool = false private var queue = DispatchQueue.global(qos: .background) private init() {} - static func reachabilityWithHostName(_ hostName: String) -> QueueITReachability { - let reachability = QueueITReachability() + static func reachabilityWithHostName(_ hostName: String) -> Reachability { + let reachability = Reachability() reachability.startMonitoringHost(hostName: hostName) return reachability } - static func reachabilityWithAddress(_ hostAddress: sockaddr_in) -> QueueITReachability { - let reachability = QueueITReachability() + static func reachabilityWithAddress(_ hostAddress: sockaddr_in) -> Reachability { + let reachability = Reachability() reachability.startMonitoringIP(address: hostAddress) return reachability } - static func reachabilityForInternetConnection() -> QueueITReachability { - let reachability = QueueITReachability() + static func reachabilityForInternetConnection() -> Reachability { + let reachability = Reachability() reachability.startMonitoringInternet() return reachability } - static func reachabilityForLocalWiFi() -> QueueITReachability { - let reachability = QueueITReachability() + static func reachabilityForLocalWiFi() -> Reachability { + let reachability = Reachability() reachability.startMonitoringWiFi() return reachability } @@ -74,7 +74,7 @@ final class QueueITReachability { } } -extension QueueITReachability { +extension Reachability { enum NetworkStatus: Int { case notReachable = 0 case reachableViaWiFi @@ -82,7 +82,7 @@ extension QueueITReachability { } } -private extension QueueITReachability { +private extension Reachability { func startMonitoringHost(hostName _: String) { monitor = NWPathMonitor(requiredInterfaceType: .other) monitor?.pathUpdateHandler = { _ in diff --git a/Sources/QueueITLib/NewQueueStatus.swift b/Sources/QueueITLib/Status.swift similarity index 94% rename from Sources/QueueITLib/NewQueueStatus.swift rename to Sources/QueueITLib/Status.swift index f66106b..a5da82f 100644 --- a/Sources/QueueITLib/NewQueueStatus.swift +++ b/Sources/QueueITLib/Status.swift @@ -1,4 +1,4 @@ -struct QueueStatus { +struct Status { let queueId: String let queueUrlString: String let eventTargetUrl: String @@ -21,7 +21,7 @@ struct QueueStatus { } } -private extension QueueStatus { +private extension Status { enum Constants { static let KEY_QUEUE_ID = "QueueId" static let KEY_QUEUE_URL = "QueueUrl" diff --git a/Sources/QueueITLib/NewQueueTryPassResult.swift b/Sources/QueueITLib/TryPassResult.swift similarity index 85% rename from Sources/QueueITLib/NewQueueTryPassResult.swift rename to Sources/QueueITLib/TryPassResult.swift index 94405f4..30f0d98 100644 --- a/Sources/QueueITLib/NewQueueTryPassResult.swift +++ b/Sources/QueueITLib/TryPassResult.swift @@ -1,6 +1,6 @@ import Foundation -struct QueueTryPassResult { +struct TryPassResult { let queueUrl: String? let targetUrl: String? let redirectType: String diff --git a/Sources/QueueITLib/NewIOSUtils.swift b/Sources/QueueITLib/Utils.swift similarity index 95% rename from Sources/QueueITLib/NewIOSUtils.swift rename to Sources/QueueITLib/Utils.swift index 1b15bf6..2e412f4 100644 --- a/Sources/QueueITLib/NewIOSUtils.swift +++ b/Sources/QueueITLib/Utils.swift @@ -1,7 +1,7 @@ import Foundation import WebKit -enum IOSUtils { +enum Utils { static func getUserId() -> String { let device = UIDevice() if let deviceId = device.identifierForVendor { @@ -35,6 +35,6 @@ enum IOSUtils { } static func getSdkVersion() -> String { - return QueueConsts.sdkVersion + return Constants.sdkVersion } } diff --git a/Sources/QueueITLib/NewQueueITWaitingRoomProvider.swift b/Sources/QueueITLib/WaitingRoomProvider.swift similarity index 80% rename from Sources/QueueITLib/NewQueueITWaitingRoomProvider.swift rename to Sources/QueueITLib/WaitingRoomProvider.swift index 7bbde19..33e8fb1 100644 --- a/Sources/QueueITLib/NewQueueITWaitingRoomProvider.swift +++ b/Sources/QueueITLib/WaitingRoomProvider.swift @@ -1,7 +1,7 @@ import Foundation -protocol QueueITWaitingRoomProviderDelegate: AnyObject { - func notifyProviderSuccess(queuePassResult: QueueTryPassResult) +protocol WaitingRoomProviderDelegate: AnyObject { + func notifyProviderSuccess(queuePassResult: TryPassResult) func notifyProviderFailure(errorMessage: String?, errorCode: Int) } @@ -15,27 +15,27 @@ enum QueueITRuntimeError: Int { ] } -final class QueueITWaitingRoomProvider { +final class WaitingRoomProvider { static let maxRetrySec = 10 static let initialWaitRetrySec = 1 - weak var delegate: QueueITWaitingRoomProviderDelegate? + weak var delegate: WaitingRoomProviderDelegate? private let customerId: String private let eventOrAliasId: String private let layoutName: String? private let language: String? - private var deltaSec: Int = QueueITWaitingRoomProvider.initialWaitRetrySec + private var deltaSec: Int = WaitingRoomProvider.initialWaitRetrySec private var requestInProgress: Bool = false - private let internetReachability: QueueITReachability + private let internetReachability: Reachability init(customerId: String, eventOrAliasId: String, layoutName: String? = nil, language: String? = nil) { self.customerId = customerId self.eventOrAliasId = eventOrAliasId self.layoutName = layoutName self.language = language - internetReachability = QueueITReachability.reachabilityForInternetConnection() + internetReachability = Reachability.reachabilityForInternetConnection() } func tryPass() throws { @@ -55,7 +55,7 @@ final class QueueITWaitingRoomProvider { } } -private extension QueueITWaitingRoomProvider { +private extension WaitingRoomProvider { func tryEnqueue(enqueueToken: String?, enqueueKey: String?) throws { guard checkConnection() else { throw NSError( @@ -75,7 +75,7 @@ private extension QueueITWaitingRoomProvider { requestInProgress = true - IOSUtils.getUserAgent { [weak self] userAgent in + Utils.getUserAgent { [weak self] userAgent in guard let self else { return } @@ -96,10 +96,10 @@ private extension QueueITWaitingRoomProvider { } func tryEnqueueWithUserAgent(secretAgent: String, enqueueToken: String?, enqueueKey: String?) throws { - let userId = IOSUtils.getUserId() - let userAgent = "\(secretAgent);\(IOSUtils.getLibraryVersion())" - let sdkVersion = IOSUtils.getSdkVersion() - let apiClient = QueueITApiClient.getInstance() + let userId = Utils.getUserId() + let userAgent = "\(secretAgent);\(Utils.getLibraryVersion())" + let sdkVersion = Utils.getSdkVersion() + let apiClient = ApiClient.getInstance() apiClient.enqueue( customerId: customerId, @@ -111,19 +111,19 @@ private extension QueueITWaitingRoomProvider { language: language, enqueueToken: enqueueToken, enqueueKey: enqueueKey, - success: { [weak self] queueStatus in + success: { [weak self] Status in guard let self else { return } - guard let queueStatus else { + guard let Status else { self.enqueueRetryMonitor(enqueueToken: enqueueToken, enqueueKey: enqueueKey) return } self.handleAppEnqueueResponse( - queueURL: queueStatus.queueUrlString, - eventTargetURL: queueStatus.eventTargetUrl, - queueItToken: queueStatus.queueitToken + queueURL: Status.queueUrlString, + eventTargetURL: Status.eventTargetUrl, + queueItToken: Status.queueitToken ) self.requestInProgress = false }, @@ -150,23 +150,23 @@ private extension QueueITWaitingRoomProvider { let isPassedThrough = !(queueItToken?.isEmpty ?? true) let redirectType = getRedirectType(fromToken: queueItToken) - let queueTryPassResult = QueueTryPassResult( + let TryPassResult = TryPassResult( queueUrl: queueURL, targetUrl: eventTargetURL, redirectType: redirectType, isPassedThrough: isPassedThrough, queueToken: queueItToken ) - delegate?.notifyProviderSuccess(queuePassResult: queueTryPassResult) + delegate?.notifyProviderSuccess(queuePassResult: TryPassResult) } func enqueueRetryMonitor(enqueueToken: String?, enqueueKey: String?) { - if deltaSec < QueueITWaitingRoomProvider.maxRetrySec { + if deltaSec < WaitingRoomProvider.maxRetrySec { try? tryEnqueue(enqueueToken: enqueueToken, enqueueKey: enqueueKey) Thread.sleep(forTimeInterval: TimeInterval(deltaSec)) deltaSec *= 2 } else { - deltaSec = QueueITWaitingRoomProvider.initialWaitRetrySec + deltaSec = WaitingRoomProvider.initialWaitRetrySec requestInProgress = false delegate?.notifyProviderFailure(errorMessage: "Error! Queue is unavailable.", errorCode: 3) } diff --git a/Sources/QueueITLib/NewQueueITWaitingRoomView.swift b/Sources/QueueITLib/WaitingRoomView.swift similarity index 87% rename from Sources/QueueITLib/NewQueueITWaitingRoomView.swift rename to Sources/QueueITLib/WaitingRoomView.swift index d951c29..453dd07 100644 --- a/Sources/QueueITLib/NewQueueITWaitingRoomView.swift +++ b/Sources/QueueITLib/WaitingRoomView.swift @@ -1,6 +1,6 @@ import UIKit -protocol QueueITWaitingRoomViewDelegate: AnyObject { +protocol WaitingRoomViewDelegate: AnyObject { func notifyViewUserExited() func notifyViewUserClosed() func notifyViewSessionRestart() @@ -10,10 +10,10 @@ protocol QueueITWaitingRoomViewDelegate: AnyObject { func notifyViewUpdatePageUrl(urlString: String?) } -final class QueueITWaitingRoomView { +final class WaitingRoomView { weak var host: UIViewController? - weak var delegate: QueueITWaitingRoomViewDelegate? - weak var currentWebView: QueueITWKViewController? + weak var delegate: WaitingRoomViewDelegate? + weak var currentWebView: WebViewController? private var eventId: String private var delayInterval: Int = 0 @@ -26,7 +26,7 @@ final class QueueITWaitingRoomView { func show(queueUrl: String, targetUrl: String) { raiseQueueViewWillOpen() - let queueWKVC = QueueITWKViewController( + let queueWKVC = WebViewController( queueUrl: queueUrl, eventTargetUrl: targetUrl, eventId: eventId @@ -58,7 +58,7 @@ final class QueueITWaitingRoomView { } } -extension QueueITWaitingRoomView: QueueITViewControllerDelegate { +extension WaitingRoomView: WebViewControllerDelegate { func notifyViewControllerClosed() { delegate?.notifyViewUserClosed() close() @@ -84,7 +84,7 @@ extension QueueITWaitingRoomView: QueueITViewControllerDelegate { } } -private extension QueueITWaitingRoomView { +private extension WaitingRoomView { func close(onComplete: (() -> Void)? = nil) { DispatchQueue.main.async { [weak self] in guard let self, let host else { diff --git a/Sources/QueueITLib/NewQueueITWKViewController.swift b/Sources/QueueITLib/WebViewController.swift similarity index 93% rename from Sources/QueueITLib/NewQueueITWKViewController.swift rename to Sources/QueueITLib/WebViewController.swift index bb68ca7..ee6c755 100644 --- a/Sources/QueueITLib/NewQueueITWKViewController.swift +++ b/Sources/QueueITLib/WebViewController.swift @@ -1,7 +1,7 @@ import UIKit import WebKit -protocol QueueITViewControllerDelegate: AnyObject { +protocol WebViewControllerDelegate: AnyObject { func notifyViewControllerClosed() func notifyViewControllerUserExited() func notifyViewControllerSessionRestart() @@ -9,8 +9,8 @@ protocol QueueITViewControllerDelegate: AnyObject { func notifyViewControllerPageUrlChanged(urlString: String?) } -final class QueueITWKViewController: UIViewController { - weak var delegate: QueueITViewControllerDelegate? +final class WebViewController: UIViewController { + weak var delegate: WebViewControllerDelegate? weak var webView: WKWebView? private var spinner: UIActivityIndicatorView? @@ -76,7 +76,7 @@ final class QueueITWKViewController: UIViewController { } } -extension QueueITWKViewController: WKNavigationDelegate { +extension WebViewController: WKNavigationDelegate { func webView( _: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, @@ -93,11 +93,11 @@ extension QueueITWKViewController: WKNavigationDelegate { let isNotFrame = request.url?.absoluteString == request.mainDocumentURL?.absoluteString - if url.absoluteString == QueueConsts.queueCloseUrl { + if url.absoluteString == Constants.queueCloseUrl { delegate?.notifyViewControllerClosed() decisionHandler(.cancel) return - } else if url.absoluteString == QueueConsts.queueRestartSessionUrl { + } else if url.absoluteString == Constants.queueRestartSessionUrl { delegate?.notifyViewControllerSessionRestart() decisionHandler(.cancel) return @@ -164,7 +164,7 @@ extension QueueITWKViewController: WKNavigationDelegate { } } -private extension QueueITWKViewController { +private extension WebViewController { func isTargetUrl(targetUrl: URL, destinationUrl: URL) -> Bool { return destinationUrl.host == targetUrl.host && destinationUrl.path == targetUrl.path } From c7a2025c6b62a6b75722c0659160a6917007e64c Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Tue, 25 Mar 2025 16:42:28 +0100 Subject: [PATCH 04/13] feat(api): expose public interface for WaitingRoom components Make WaitingRoomProvider and WaitingRoomView classes and their protocols publicly accessible to enable external usage of the SDK components. --- Sources/QueueITLib/QueueITEngine.swift | 18 +++++++++--------- Sources/QueueITLib/TryPassResult.swift | 12 ++++++------ Sources/QueueITLib/WaitingRoomProvider.swift | 16 ++++++++-------- Sources/QueueITLib/WaitingRoomView.swift | 12 ++++++------ 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Sources/QueueITLib/QueueITEngine.swift b/Sources/QueueITLib/QueueITEngine.swift index 884e59d..3577581 100644 --- a/Sources/QueueITLib/QueueITEngine.swift +++ b/Sources/QueueITLib/QueueITEngine.swift @@ -96,38 +96,38 @@ public final class QueueItEngine { } extension QueueItEngine: WaitingRoomViewDelegate { - func notifyViewUserExited() { + public func notifyViewUserExited() { queueUserExitedDelegate?.notifyUserExited() } - func notifyViewUserClosed() { + public func notifyViewUserClosed() { queueViewClosedDelegate?.notifyViewClosed() } - func notifyViewSessionRestart() { + public func notifyViewSessionRestart() { queueSessionRestartDelegate?.notifySessionRestart() } - func notifyQueuePassed(info: QueuePassedInfo?) { + public func notifyQueuePassed(info: QueuePassedInfo?) { queuePassedDelegate?.notifyYourTurn(queuePassedInfo: info) } - func notifyViewQueueDidAppear() { + public func notifyViewQueueDidAppear() { queueViewDidAppearDelegate?.notifyQueueViewDidAppear() } - func notifyViewQueueWillOpen() { + public func notifyViewQueueWillOpen() { queueViewWillOpenDelegate?.notifyQueueViewWillOpen() } - func notifyViewUpdatePageUrl(urlString: String?) { + public func notifyViewUpdatePageUrl(urlString: String?) { // TODO: fix optional parameter queueUrlChangedDelegate?.notifyQueueUrlChanged(url: urlString ?? "") } } extension QueueItEngine: WaitingRoomProviderDelegate { - func notifyProviderSuccess(queuePassResult: TryPassResult) { + public func notifyProviderSuccess(queuePassResult: TryPassResult) { switch queuePassResult.redirectType { case "safetynet": let queuePassedInfo = QueuePassedInfo(queueitToken: queuePassResult.queueToken) @@ -141,7 +141,7 @@ extension QueueItEngine: WaitingRoomProviderDelegate { } } - func notifyProviderFailure(errorMessage: String?, errorCode: Int) { + public func notifyProviderFailure(errorMessage: String?, errorCode: Int) { // TODO: fix optional parameter let errorMessage = errorMessage ?? "" if errorCode == 3 { diff --git a/Sources/QueueITLib/TryPassResult.swift b/Sources/QueueITLib/TryPassResult.swift index 30f0d98..ca9330b 100644 --- a/Sources/QueueITLib/TryPassResult.swift +++ b/Sources/QueueITLib/TryPassResult.swift @@ -1,9 +1,9 @@ import Foundation -struct TryPassResult { - let queueUrl: String? - let targetUrl: String? - let redirectType: String - let isPassedThrough: Bool - let queueToken: String? +public struct TryPassResult { + public let queueUrl: String? + public let targetUrl: String? + public let redirectType: String + public let isPassedThrough: Bool + public let queueToken: String? } diff --git a/Sources/QueueITLib/WaitingRoomProvider.swift b/Sources/QueueITLib/WaitingRoomProvider.swift index 33e8fb1..54ad191 100644 --- a/Sources/QueueITLib/WaitingRoomProvider.swift +++ b/Sources/QueueITLib/WaitingRoomProvider.swift @@ -1,6 +1,6 @@ import Foundation -protocol WaitingRoomProviderDelegate: AnyObject { +public protocol WaitingRoomProviderDelegate: AnyObject { func notifyProviderSuccess(queuePassResult: TryPassResult) func notifyProviderFailure(errorMessage: String?, errorCode: Int) } @@ -15,11 +15,11 @@ enum QueueITRuntimeError: Int { ] } -final class WaitingRoomProvider { +public final class WaitingRoomProvider { static let maxRetrySec = 10 static let initialWaitRetrySec = 1 - weak var delegate: WaitingRoomProviderDelegate? + public weak var delegate: WaitingRoomProviderDelegate? private let customerId: String private let eventOrAliasId: String @@ -30,7 +30,7 @@ final class WaitingRoomProvider { private var requestInProgress: Bool = false private let internetReachability: Reachability - init(customerId: String, eventOrAliasId: String, layoutName: String? = nil, language: String? = nil) { + public init(customerId: String, eventOrAliasId: String, layoutName: String? = nil, language: String? = nil) { self.customerId = customerId self.eventOrAliasId = eventOrAliasId self.layoutName = layoutName @@ -38,19 +38,19 @@ final class WaitingRoomProvider { internetReachability = Reachability.reachabilityForInternetConnection() } - func tryPass() throws { + public func tryPass() throws { try tryEnqueue(enqueueToken: nil, enqueueKey: nil) } - func tryPassWithEnqueueToken(_ enqueueToken: String?) throws { + public func tryPassWithEnqueueToken(_ enqueueToken: String?) throws { try tryEnqueue(enqueueToken: enqueueToken, enqueueKey: nil) } - func tryPassWithEnqueueKey(_ enqueueKey: String?) throws { + public func tryPassWithEnqueueKey(_ enqueueKey: String?) throws { try tryEnqueue(enqueueToken: nil, enqueueKey: enqueueKey) } - func isRequestInProgress() -> Bool { + public func isRequestInProgress() -> Bool { return requestInProgress } } diff --git a/Sources/QueueITLib/WaitingRoomView.swift b/Sources/QueueITLib/WaitingRoomView.swift index 453dd07..dd2b2d9 100644 --- a/Sources/QueueITLib/WaitingRoomView.swift +++ b/Sources/QueueITLib/WaitingRoomView.swift @@ -1,6 +1,6 @@ import UIKit -protocol WaitingRoomViewDelegate: AnyObject { +public protocol WaitingRoomViewDelegate: AnyObject { func notifyViewUserExited() func notifyViewUserClosed() func notifyViewSessionRestart() @@ -10,20 +10,20 @@ protocol WaitingRoomViewDelegate: AnyObject { func notifyViewUpdatePageUrl(urlString: String?) } -final class WaitingRoomView { +public final class WaitingRoomView { weak var host: UIViewController? - weak var delegate: WaitingRoomViewDelegate? + public weak var delegate: WaitingRoomViewDelegate? weak var currentWebView: WebViewController? private var eventId: String private var delayInterval: Int = 0 - init(host: UIViewController, eventId: String) { + public init(host: UIViewController, eventId: String) { self.host = host self.eventId = eventId } - func show(queueUrl: String, targetUrl: String) { + public func show(queueUrl: String, targetUrl: String) { raiseQueueViewWillOpen() let queueWKVC = WebViewController( @@ -53,7 +53,7 @@ final class WaitingRoomView { } } - func setViewDelay(_ delayInterval: Int) { + public func setViewDelay(_ delayInterval: Int) { self.delayInterval = delayInterval } } From ebb1221b717c2c465f269b3141ca868da0ce5f49 Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Thu, 12 Dec 2024 18:02:43 +0100 Subject: [PATCH 05/13] feat: update to Swift 6 and modernize codebase - Upgrade swift-tools-version to 6.0 in Package.swift - Update iOS deployment target to v15 - Refactor ApiClient to use async/await - Remove Connection and ConnectionRequest classes - Add @MainActor annotations to relevant methods - Ensure Status struct conforms to Sendable - Update various protocols and methods to use async/await --- Package.swift | 4 +- Sources/QueueITLib/ApiClient.swift | 135 ++++++++++-------- Sources/QueueITLib/Connection.swift | 42 ------ Sources/QueueITLib/ConnectionRequest.swift | 97 ------------- Sources/QueueITLib/QueueITEngine.swift | 53 +++---- Sources/QueueITLib/Reachability.swift | 2 +- Sources/QueueITLib/Status.swift | 2 +- Sources/QueueITLib/Utils.swift | 10 +- Sources/QueueITLib/WaitingRoomProvider.swift | 141 +++++++++---------- Sources/QueueITLib/WaitingRoomView.swift | 30 ++-- Sources/QueueITLib/WebViewController.swift | 14 +- 11 files changed, 204 insertions(+), 326 deletions(-) delete mode 100644 Sources/QueueITLib/Connection.swift delete mode 100644 Sources/QueueITLib/ConnectionRequest.swift diff --git a/Package.swift b/Package.swift index 0a4b44b..84d0275 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "QueueITLibrary", platforms: [ - .iOS(.v11), + .iOS(.v15), ], products: [ .library( diff --git a/Sources/QueueITLib/ApiClient.swift b/Sources/QueueITLib/ApiClient.swift index 3676bb0..fa91e09 100644 --- a/Sources/QueueITLib/ApiClient.swift +++ b/Sources/QueueITLib/ApiClient.swift @@ -3,20 +3,13 @@ import Foundation typealias QueueServiceSuccess = (Data) -> Void typealias QueueServiceFailure = (Error, String) -> Void -class ApiClient { +@MainActor +final class ApiClient { static let API_ROOT = "https://%@.queue-it.net/api/mobileapp/queue" static let TESTING_API_ROOT = "https://%@.test.queue-it.net/api/mobileapp/queue" - private static var testingIsEnabled = false - private static var sharedInstance: ApiClient? + private var testingIsEnabled = false - static func getInstance() -> ApiClient { - if sharedInstance == nil { - sharedInstance = Connection() - } - return sharedInstance! - } - - static func setTesting(_ enabled: Bool) { + func setTesting(_ enabled: Bool) { testingIsEnabled = enabled } @@ -29,10 +22,8 @@ class ApiClient { layoutName: String?, language: String?, enqueueToken: String?, - enqueueKey: String?, - success: @escaping (Status?) -> Void, - failure: @escaping QueueServiceFailure - ) { + enqueueKey: String? + ) async throws -> Status? { var bodyDict: [String: Any] = [ "userId": userId, "userAgent": userAgent, @@ -55,68 +46,102 @@ class ApiClient { bodyDict["enqueueKey"] = enqueueKey } - let apiRoot = ApiClient.testingIsEnabled ? ApiClient.TESTING_API_ROOT : ApiClient.API_ROOT + let apiRoot = testingIsEnabled ? ApiClient.TESTING_API_ROOT : ApiClient.API_ROOT var urlAsString = String(format: apiRoot, customerId) urlAsString += "/\(customerId)" urlAsString += "/\(eventOrAliasId)" urlAsString += "/enqueue" - submitPOSTPath( - path: urlAsString, - body: bodyDict, - success: { data in - do { - if let userDict = try JSONSerialization.jsonObject( - with: data, - options: [] - ) as? [String: Any] { - let Status = Status(dictionary: userDict) - success(Status) - } else { - success(nil) - } - } catch { - success(nil) - } - }, - failure: failure - ) + let data = try await submitPOSTPath(path: urlAsString, body: bodyDict) + do { + if let userDict = try JSONSerialization.jsonObject( + with: data, + options: [] + ) as? [String: Any] { + return Status(dictionary: userDict) + } else { + return nil + } + } catch { + return nil + } } +} +private extension ApiClient { func submitPOSTPath( path: String, - body bodyDict: [String: Any], - success: @escaping QueueServiceSuccess, - failure: @escaping QueueServiceFailure - ) { + body bodyDict: [String: Any] + ) async throws -> Data { guard let url = URL(string: path) else { let error = NSError( domain: "ApiClient", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"] ) - failure(error, "Invalid URL") - return + throw error } - submitRequest( + return try await submitRequest( with: url, method: "POST", - body: bodyDict, - expectedStatus: 200, - success: success, - failure: failure + body: bodyDict ) } func submitRequest( - with _: URL, - method _: String, - body _: [String: Any], - expectedStatus _: Int, - success _: @escaping QueueServiceSuccess, - failure _: @escaping QueueServiceFailure - ) { - return + with url: URL, + method httpMethod: String, + body bodyDict: [String: Any] + ) async throws -> Data { + var request = URLRequest(url: url) + request.httpMethod = httpMethod + + do { + let jsonData = try JSONSerialization.data(withJSONObject: bodyDict, options: []) + request.httpBody = jsonData + } catch { + throw error + } + + request.addValue("application/json", forHTTPHeaderField: "Accept") + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + return try await initiateRequest(request: request) } + + func initiateRequest(request: URLRequest) async throws -> Data { + let (data, response) = try await URLSession.shared.data(for: request) + + guard let response = response as? HTTPURLResponse else { + throw ApiClientError.nilResponse + } + + let actualStatusCode = response.statusCode + if actualStatusCode == 200 { + return data + } else { + var message = "Unexpected response code: \(actualStatusCode)" + if actualStatusCode >= 400, actualStatusCode < 500 { + if let decodedMessage = String(data: data, encoding: .ascii) { + message = decodedMessage + } + } else if let json = try? JSONSerialization.jsonObject(with: data, options: []), + let jsonDict = json as? [String: Any], + let errorMessage = jsonDict["error"] as? String + { + message = errorMessage + } + + throw NSError( + domain: "QueueService", + code: actualStatusCode, + userInfo: [NSLocalizedDescriptionKey: message] + ) + } + } +} + +enum ApiClientError: Error { + case nilResponse } diff --git a/Sources/QueueITLib/Connection.swift b/Sources/QueueITLib/Connection.swift deleted file mode 100644 index 0c6f9b6..0000000 --- a/Sources/QueueITLib/Connection.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation - -final class Connection: ApiClient { - private var connectionRequest: ConnectionRequest? - - override func submitRequest( - with url: URL, - method httpMethod: String, - body bodyDict: [String: Any], - expectedStatus: Int, - success: @escaping QueueServiceSuccess, - failure: @escaping QueueServiceFailure - ) { - var request = URLRequest(url: url) - request.httpMethod = httpMethod - - do { - let jsonData = try JSONSerialization.data(withJSONObject: bodyDict, options: []) - request.httpBody = jsonData - } catch { - failure(error, "Failed to serialize request body.") - return - } - - request.addValue("application/json", forHTTPHeaderField: "Accept") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - - connectionRequest = ConnectionRequest( - request: request, - expectedStatusCode: expectedStatus, - success: success, - failure: failure, - delegate: self - ) - } -} - -extension Connection: ConnectionRequestDelegate { - func requestDidComplete(_: ConnectionRequest) { - // Handle the completion of the request if needed - } -} diff --git a/Sources/QueueITLib/ConnectionRequest.swift b/Sources/QueueITLib/ConnectionRequest.swift deleted file mode 100644 index 08c2128..0000000 --- a/Sources/QueueITLib/ConnectionRequest.swift +++ /dev/null @@ -1,97 +0,0 @@ -import Foundation - -protocol ConnectionRequestDelegate: AnyObject { - func requestDidComplete(_ request: ConnectionRequest) -} - -final class ConnectionRequest { - let uniqueIdentifier: String - - private var request: URLRequest - private var response: URLResponse? - private var data: Data - private var successCallback: QueueServiceSuccess - private var failureCallback: QueueServiceFailure - private weak var delegate: ConnectionRequestDelegate? - private var expectedStatusCode: Int - private var actualStatusCode: Int = NSNotFound - - init( - request: URLRequest, - expectedStatusCode: Int, - success: @escaping QueueServiceSuccess, - failure: @escaping QueueServiceFailure, - delegate: ConnectionRequestDelegate? - ) { - self.request = request - self.expectedStatusCode = expectedStatusCode - successCallback = success - failureCallback = failure - self.delegate = delegate - uniqueIdentifier = UUID().uuidString - data = Data() - initiateRequest() - } -} - -private extension ConnectionRequest { - func initiateRequest() { - response = nil - data = Data() - - let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in - guard let self else { return } - - if let error = error { - DispatchQueue.main.async { - self.failureCallback(error, "Unexpected failure occurred.") - self.delegate?.requestDidComplete(self) - } - return - } - - if let response = response as? HTTPURLResponse { - self.actualStatusCode = response.statusCode - self.response = response - } - - if let receivedData = data { - self.data.append(receivedData) - } - - DispatchQueue.main.async { - self.handleResponse() - } - } - task.resume() - } - - func handleResponse() { - if hasExpectedStatusCode() { - successCallback(data) - } else { - var message = "Unexpected response code: \(actualStatusCode)" - if actualStatusCode >= 400, actualStatusCode < 500 { - if let decodedMessage = String(data: data, encoding: .ascii) { - message = decodedMessage - } - } else if let json = try? JSONSerialization.jsonObject(with: data, options: []), - let jsonDict = json as? [String: Any], - let errorMessage = jsonDict["error"] as? String - { - message = errorMessage - } - - let error = NSError(domain: "QueueService", - code: actualStatusCode, - userInfo: [NSLocalizedDescriptionKey: message]) - failureCallback(error, message) - } - - delegate?.requestDidComplete(self) - } - - func hasExpectedStatusCode() -> Bool { - return actualStatusCode == expectedStatusCode - } -} diff --git a/Sources/QueueITLib/QueueITEngine.swift b/Sources/QueueITLib/QueueITEngine.swift index 3577581..1e26d75 100644 --- a/Sources/QueueITLib/QueueITEngine.swift +++ b/Sources/QueueITLib/QueueITEngine.swift @@ -1,45 +1,46 @@ import UIKit public protocol QueuePassedDelegate: AnyObject { - func notifyYourTurn(queuePassedInfo: QueuePassedInfo?) + @MainActor func notifyYourTurn(queuePassedInfo: QueuePassedInfo?) } public protocol QueueViewWillOpenDelegate: AnyObject { - func notifyQueueViewWillOpen() + @MainActor func notifyQueueViewWillOpen() } public protocol QueueDisabledDelegate: AnyObject { - func notifyQueueDisabled(queueDisabledInfo: QueueDisabledInfo?) + @MainActor func notifyQueueDisabled(queueDisabledInfo: QueueDisabledInfo?) } public protocol QueueUnavailableDelegate: AnyObject { - func notifyQueueITUnavailable(errorMessage: String) + @MainActor func notifyQueueITUnavailable(errorMessage: String) } public protocol QueueErrorDelegate: AnyObject { - func notifyQueueError(errorMessage: String, errorCode: Int) + @MainActor func notifyQueueError(errorMessage: String, errorCode: Int) } public protocol QueueViewClosedDelegate: AnyObject { - func notifyViewClosed() + @MainActor func notifyViewClosed() } public protocol QueueUserExitedDelegate: AnyObject { - func notifyUserExited() + @MainActor func notifyUserExited() } public protocol QueueSessionRestartDelegate: AnyObject { - func notifySessionRestart() + @MainActor func notifySessionRestart() } public protocol QueueUrlChangedDelegate: AnyObject { - func notifyQueueUrlChanged(url: String) + @MainActor func notifyQueueUrlChanged(url: String) } public protocol QueueViewDidAppearDelegate: AnyObject { - func notifyQueueViewDidAppear() + @MainActor func notifyQueueViewDidAppear() } +@MainActor public final class QueueItEngine { public weak var queuePassedDelegate: QueuePassedDelegate? public weak var queueViewWillOpenDelegate: QueueViewWillOpenDelegate? @@ -78,56 +79,56 @@ public final class QueueItEngine { return waitingRoomProvider.isRequestInProgress() } - public func run(withEnqueueKey enqueueKey: String) throws { - try waitingRoomProvider.tryPassWithEnqueueKey(enqueueKey) + public func run(withEnqueueKey enqueueKey: String) async throws { + try await waitingRoomProvider.tryPassWithEnqueueKey(enqueueKey) } - public func run(withEnqueueToken enqueueToken: String) throws { - try waitingRoomProvider.tryPassWithEnqueueToken(enqueueToken) + public func run(withEnqueueToken enqueueToken: String) async throws { + try await waitingRoomProvider.tryPassWithEnqueueToken(enqueueToken) } - public func run() throws { - try waitingRoomProvider.tryPass() + public func run() async throws { + try await waitingRoomProvider.tryPass() } - public func showQueue(queueUrl: String, targetUrl: String) { + @MainActor public func showQueue(queueUrl: String, targetUrl: String) { waitingRoomView.show(queueUrl: queueUrl, targetUrl: targetUrl) } } extension QueueItEngine: WaitingRoomViewDelegate { - public func notifyViewUserExited() { + @MainActor public func notifyViewUserExited() { queueUserExitedDelegate?.notifyUserExited() } - public func notifyViewUserClosed() { + @MainActor public func notifyViewUserClosed() { queueViewClosedDelegate?.notifyViewClosed() } - public func notifyViewSessionRestart() { + @MainActor public func notifyViewSessionRestart() { queueSessionRestartDelegate?.notifySessionRestart() } - public func notifyQueuePassed(info: QueuePassedInfo?) { + @MainActor public func notifyQueuePassed(info: QueuePassedInfo?) { queuePassedDelegate?.notifyYourTurn(queuePassedInfo: info) } - public func notifyViewQueueDidAppear() { + @MainActor public func notifyViewQueueDidAppear() { queueViewDidAppearDelegate?.notifyQueueViewDidAppear() } - public func notifyViewQueueWillOpen() { + @MainActor public func notifyViewQueueWillOpen() { queueViewWillOpenDelegate?.notifyQueueViewWillOpen() } - public func notifyViewUpdatePageUrl(urlString: String?) { + @MainActor public func notifyViewUpdatePageUrl(urlString: String?) { // TODO: fix optional parameter queueUrlChangedDelegate?.notifyQueueUrlChanged(url: urlString ?? "") } } extension QueueItEngine: WaitingRoomProviderDelegate { - public func notifyProviderSuccess(queuePassResult: TryPassResult) { + @MainActor public func notifyProviderSuccess(queuePassResult: TryPassResult) async { switch queuePassResult.redirectType { case "safetynet": let queuePassedInfo = QueuePassedInfo(queueitToken: queuePassResult.queueToken) @@ -141,7 +142,7 @@ extension QueueItEngine: WaitingRoomProviderDelegate { } } - public func notifyProviderFailure(errorMessage: String?, errorCode: Int) { + @MainActor public func notifyProviderFailure(errorMessage: String?, errorCode: Int) async { // TODO: fix optional parameter let errorMessage = errorMessage ?? "" if errorCode == 3 { diff --git a/Sources/QueueITLib/Reachability.swift b/Sources/QueueITLib/Reachability.swift index 79236ed..c1f5a85 100644 --- a/Sources/QueueITLib/Reachability.swift +++ b/Sources/QueueITLib/Reachability.swift @@ -41,7 +41,7 @@ final class Reachability { func startNotifier() -> Bool { guard monitor == nil else { return true } monitor = NWPathMonitor() - monitor?.pathUpdateHandler = { [weak self] _ in + monitor?.pathUpdateHandler = { _ in NotificationCenter.default.post(name: .reachabilityChanged, object: nil) } monitor?.start(queue: queue) diff --git a/Sources/QueueITLib/Status.swift b/Sources/QueueITLib/Status.swift index a5da82f..b811b4c 100644 --- a/Sources/QueueITLib/Status.swift +++ b/Sources/QueueITLib/Status.swift @@ -1,4 +1,4 @@ -struct Status { +struct Status: Sendable { let queueId: String let queueUrlString: String let eventTargetUrl: String diff --git a/Sources/QueueITLib/Utils.swift b/Sources/QueueITLib/Utils.swift index 2e412f4..a7a29d0 100644 --- a/Sources/QueueITLib/Utils.swift +++ b/Sources/QueueITLib/Utils.swift @@ -2,7 +2,7 @@ import Foundation import WebKit enum Utils { - static func getUserId() -> String { + @MainActor static func getUserId() -> String { let device = UIDevice() if let deviceId = device.identifierForVendor { return deviceId.uuidString @@ -10,14 +10,14 @@ enum Utils { return "" } - static func getUserAgent(completionHandler: @escaping (String) -> Void) { - DispatchQueue.main.async { + @MainActor static func getUserAgent() async -> String { + await withCheckedContinuation { continuation in let view = WKWebView(frame: .zero) view.evaluateJavaScript("navigator.userAgent") { result, error in if let userAgent = result as? String, error == nil { - completionHandler(userAgent) + continuation.resume(returning: userAgent) } else { - completionHandler("") + continuation.resume(returning: "") } } } diff --git a/Sources/QueueITLib/WaitingRoomProvider.swift b/Sources/QueueITLib/WaitingRoomProvider.swift index 54ad191..80105d1 100644 --- a/Sources/QueueITLib/WaitingRoomProvider.swift +++ b/Sources/QueueITLib/WaitingRoomProvider.swift @@ -1,8 +1,9 @@ import Foundation +@MainActor public protocol WaitingRoomProviderDelegate: AnyObject { - func notifyProviderSuccess(queuePassResult: TryPassResult) - func notifyProviderFailure(errorMessage: String?, errorCode: Int) + func notifyProviderSuccess(queuePassResult: TryPassResult) async + func notifyProviderFailure(errorMessage: String?, errorCode: Int) async } enum QueueITRuntimeError: Int { @@ -15,6 +16,7 @@ enum QueueITRuntimeError: Int { ] } +@MainActor public final class WaitingRoomProvider { static let maxRetrySec = 10 static let initialWaitRetrySec = 1 @@ -26,6 +28,7 @@ public final class WaitingRoomProvider { private let layoutName: String? private let language: String? + private var apiClient: ApiClient? private var deltaSec: Int = WaitingRoomProvider.initialWaitRetrySec private var requestInProgress: Bool = false private let internetReachability: Reachability @@ -38,16 +41,16 @@ public final class WaitingRoomProvider { internetReachability = Reachability.reachabilityForInternetConnection() } - public func tryPass() throws { - try tryEnqueue(enqueueToken: nil, enqueueKey: nil) + public func tryPass() async throws { + try await tryEnqueue(enqueueToken: nil, enqueueKey: nil) } - public func tryPassWithEnqueueToken(_ enqueueToken: String?) throws { - try tryEnqueue(enqueueToken: enqueueToken, enqueueKey: nil) + public func tryPassWithEnqueueToken(_ enqueueToken: String?) async throws { + try await tryEnqueue(enqueueToken: enqueueToken, enqueueKey: nil) } - public func tryPassWithEnqueueKey(_ enqueueKey: String?) throws { - try tryEnqueue(enqueueToken: nil, enqueueKey: enqueueKey) + public func tryPassWithEnqueueKey(_ enqueueKey: String?) async throws { + try await tryEnqueue(enqueueToken: nil, enqueueKey: enqueueKey) } public func isRequestInProgress() -> Bool { @@ -56,7 +59,7 @@ public final class WaitingRoomProvider { } private extension WaitingRoomProvider { - func tryEnqueue(enqueueToken: String?, enqueueKey: String?) throws { + func tryEnqueue(enqueueToken: String?, enqueueKey: String?) async throws { guard checkConnection() else { throw NSError( domain: "QueueITRuntimeException", @@ -75,100 +78,88 @@ private extension WaitingRoomProvider { requestInProgress = true - Utils.getUserAgent { [weak self] userAgent in - guard let self else { - return - } - do { - try self.tryEnqueueWithUserAgent( - secretAgent: userAgent, - enqueueToken: enqueueToken, - enqueueKey: enqueueKey - ) - } catch { - self.requestInProgress = false - self.delegate?.notifyProviderFailure( - errorMessage: error.localizedDescription, - errorCode: (error as NSError).code - ) - } + let userAgent = await Utils.getUserAgent() + do { + try await self.tryEnqueueWithUserAgent( + secretAgent: userAgent, + enqueueToken: enqueueToken, + enqueueKey: enqueueKey + ) + } catch { + self.requestInProgress = false + await delegate?.notifyProviderFailure( + errorMessage: error.localizedDescription, + errorCode: (error as NSError).code + ) } } - func tryEnqueueWithUserAgent(secretAgent: String, enqueueToken: String?, enqueueKey: String?) throws { - let userId = Utils.getUserId() + func tryEnqueueWithUserAgent(secretAgent: String, enqueueToken: String?, enqueueKey: String?) async throws { + let userId = await Utils.getUserId() let userAgent = "\(secretAgent);\(Utils.getLibraryVersion())" let sdkVersion = Utils.getSdkVersion() - let apiClient = ApiClient.getInstance() - - apiClient.enqueue( - customerId: customerId, - eventOrAliasId: eventOrAliasId, - userId: userId, - userAgent: userAgent, - sdkVersion: sdkVersion, - layoutName: layoutName, - language: language, - enqueueToken: enqueueToken, - enqueueKey: enqueueKey, - success: { [weak self] Status in - guard let self else { - return - } - guard let Status else { - self.enqueueRetryMonitor(enqueueToken: enqueueToken, enqueueKey: enqueueKey) - return - } - - self.handleAppEnqueueResponse( - queueURL: Status.queueUrlString, - eventTargetURL: Status.eventTargetUrl, - queueItToken: Status.queueitToken - ) - self.requestInProgress = false - }, - failure: { [weak self] error, errorMessage in - guard let self else { - return - } - if let nsError = error as? NSError { - if nsError.code >= 400, nsError.code < 500 { - self.delegate?.notifyProviderFailure(errorMessage: errorMessage, errorCode: nsError.code) - } else { - self.enqueueRetryMonitor(enqueueToken: enqueueToken, enqueueKey: enqueueKey) - } - } + apiClient = ApiClient() + + do { + let status = try await apiClient?.enqueue( + customerId: customerId, + eventOrAliasId: eventOrAliasId, + userId: userId, + userAgent: userAgent, + sdkVersion: sdkVersion, + layoutName: layoutName, + language: language, + enqueueToken: enqueueToken, + enqueueKey: enqueueKey + ) + guard let status else { + await self.enqueueRetryMonitor(enqueueToken: enqueueToken, enqueueKey: enqueueKey) + return } - ) + + await self.handleAppEnqueueResponse( + queueURL: status.queueUrlString, + eventTargetURL: status.eventTargetUrl, + queueItToken: status.queueitToken + ) + self.requestInProgress = false + } catch { + let nsError = error as NSError + if nsError.code >= 400, nsError.code < 500 { + await self.delegate?.notifyProviderFailure(errorMessage: "", errorCode: nsError.code) + } else { + await self.enqueueRetryMonitor(enqueueToken: enqueueToken, enqueueKey: enqueueKey) + } + } } func handleAppEnqueueResponse( queueURL: String, eventTargetURL: String?, queueItToken: String? - ) { + ) async { let isPassedThrough = !(queueItToken?.isEmpty ?? true) let redirectType = getRedirectType(fromToken: queueItToken) - let TryPassResult = TryPassResult( + let tryPassResult = TryPassResult( queueUrl: queueURL, targetUrl: eventTargetURL, redirectType: redirectType, isPassedThrough: isPassedThrough, queueToken: queueItToken ) - delegate?.notifyProviderSuccess(queuePassResult: TryPassResult) + await delegate?.notifyProviderSuccess(queuePassResult: tryPassResult) } - func enqueueRetryMonitor(enqueueToken: String?, enqueueKey: String?) { + func enqueueRetryMonitor(enqueueToken: String?, enqueueKey: String?) async { if deltaSec < WaitingRoomProvider.maxRetrySec { - try? tryEnqueue(enqueueToken: enqueueToken, enqueueKey: enqueueKey) - Thread.sleep(forTimeInterval: TimeInterval(deltaSec)) + try? await tryEnqueue(enqueueToken: enqueueToken, enqueueKey: enqueueKey) + try? await Task.sleep(nanoseconds: UInt64(deltaSec * 1_000_000_000)) deltaSec *= 2 } else { deltaSec = WaitingRoomProvider.initialWaitRetrySec requestInProgress = false - delegate?.notifyProviderFailure(errorMessage: "Error! Queue is unavailable.", errorCode: 3) + await delegate?.notifyProviderFailure(errorMessage: "Error! Queue is unavailable.", errorCode: 3) } } diff --git a/Sources/QueueITLib/WaitingRoomView.swift b/Sources/QueueITLib/WaitingRoomView.swift index dd2b2d9..1e1bf2c 100644 --- a/Sources/QueueITLib/WaitingRoomView.swift +++ b/Sources/QueueITLib/WaitingRoomView.swift @@ -1,13 +1,13 @@ import UIKit public protocol WaitingRoomViewDelegate: AnyObject { - func notifyViewUserExited() - func notifyViewUserClosed() - func notifyViewSessionRestart() - func notifyQueuePassed(info: QueuePassedInfo?) - func notifyViewQueueDidAppear() - func notifyViewQueueWillOpen() - func notifyViewUpdatePageUrl(urlString: String?) + @MainActor func notifyViewUserExited() + @MainActor func notifyViewUserClosed() + @MainActor func notifyViewSessionRestart() + @MainActor func notifyQueuePassed(info: QueuePassedInfo?) + @MainActor func notifyViewQueueDidAppear() + @MainActor func notifyViewQueueWillOpen() + @MainActor func notifyViewUpdatePageUrl(urlString: String?) } public final class WaitingRoomView { @@ -23,7 +23,7 @@ public final class WaitingRoomView { self.eventId = eventId } - public func show(queueUrl: String, targetUrl: String) { + @MainActor public func show(queueUrl: String, targetUrl: String) { raiseQueueViewWillOpen() let queueWKVC = WebViewController( @@ -59,33 +59,33 @@ public final class WaitingRoomView { } extension WaitingRoomView: WebViewControllerDelegate { - func notifyViewControllerClosed() { + @MainActor func notifyViewControllerClosed() { delegate?.notifyViewUserClosed() close() } - func notifyViewControllerUserExited() { + @MainActor func notifyViewControllerUserExited() { delegate?.notifyViewUserExited() } - func notifyViewControllerSessionRestart() { + @MainActor func notifyViewControllerSessionRestart() { delegate?.notifyViewSessionRestart() close() } - func notifyViewControllerQueuePassed(queueToken: String?) { + @MainActor func notifyViewControllerQueuePassed(queueToken: String?) { let queuePassedInfo = QueuePassedInfo(queueitToken: queueToken) delegate?.notifyQueuePassed(info: queuePassedInfo) close() } - func notifyViewControllerPageUrlChanged(urlString: String?) { + @MainActor func notifyViewControllerPageUrlChanged(urlString: String?) { delegate?.notifyViewUpdatePageUrl(urlString: urlString) } } private extension WaitingRoomView { - func close(onComplete: (() -> Void)? = nil) { + @MainActor func close(onComplete: (() -> Void)? = nil) { DispatchQueue.main.async { [weak self] in guard let self, let host else { return @@ -94,7 +94,7 @@ private extension WaitingRoomView { } } - func raiseQueueViewWillOpen() { + @MainActor func raiseQueueViewWillOpen() { delegate?.notifyViewQueueWillOpen() } } diff --git a/Sources/QueueITLib/WebViewController.swift b/Sources/QueueITLib/WebViewController.swift index ee6c755..d84d995 100644 --- a/Sources/QueueITLib/WebViewController.swift +++ b/Sources/QueueITLib/WebViewController.swift @@ -1,18 +1,18 @@ import UIKit -import WebKit +@preconcurrency import WebKit protocol WebViewControllerDelegate: AnyObject { - func notifyViewControllerClosed() - func notifyViewControllerUserExited() - func notifyViewControllerSessionRestart() - func notifyViewControllerQueuePassed(queueToken: String?) - func notifyViewControllerPageUrlChanged(urlString: String?) + @MainActor func notifyViewControllerClosed() + @MainActor func notifyViewControllerUserExited() + @MainActor func notifyViewControllerSessionRestart() + @MainActor func notifyViewControllerQueuePassed(queueToken: String?) + @MainActor func notifyViewControllerPageUrlChanged(urlString: String?) } final class WebViewController: UIViewController { weak var delegate: WebViewControllerDelegate? - weak var webView: WKWebView? + private var webView: WKWebView? private var spinner: UIActivityIndicatorView? private var isQueuePassed: Bool From 035e4dd3bcaedab1bf64d7de17eed1c8c4c754bf Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Wed, 26 Mar 2025 12:38:46 +0100 Subject: [PATCH 06/13] refactor(WaitingRoomProvider): update tryPass method with MainActor and Task wrapper - Add @MainActor annotation for thread safety - Change tryPass to be non-throwing and non-async - Wrap async call in Task for better concurrency handling --- Sources/QueueITLib/WaitingRoomProvider.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/QueueITLib/WaitingRoomProvider.swift b/Sources/QueueITLib/WaitingRoomProvider.swift index 80105d1..bf5aa25 100644 --- a/Sources/QueueITLib/WaitingRoomProvider.swift +++ b/Sources/QueueITLib/WaitingRoomProvider.swift @@ -41,8 +41,11 @@ public final class WaitingRoomProvider { internetReachability = Reachability.reachabilityForInternetConnection() } - public func tryPass() async throws { - try await tryEnqueue(enqueueToken: nil, enqueueKey: nil) + @MainActor + public func tryPass() { + Task { + try await tryEnqueue(enqueueToken: nil, enqueueKey: nil) + } } public func tryPassWithEnqueueToken(_ enqueueToken: String?) async throws { From effff769dd8d86565afa9eb9c5962b74ac685d74 Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Wed, 26 Mar 2025 13:44:00 +0100 Subject: [PATCH 07/13] feat(webview): update WKNavigationDelegate to Swift 6 async API - Replace legacy decidePolicyFor callback pattern with modern async/await version - Update UIApplication.open call to be async - Return navigation policy and webpage preferences tuple instead of using completion handler Signed-off-by: alexanderthoren --- Sources/QueueITLib/WebViewController.swift | 27 ++++++++-------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Sources/QueueITLib/WebViewController.swift b/Sources/QueueITLib/WebViewController.swift index d84d995..f0931a4 100644 --- a/Sources/QueueITLib/WebViewController.swift +++ b/Sources/QueueITLib/WebViewController.swift @@ -77,11 +77,7 @@ final class WebViewController: UIViewController { } extension WebViewController: WKNavigationDelegate { - func webView( - _: WKWebView, - decidePolicyFor navigationAction: WKNavigationAction, - decisionHandler: @escaping (WKNavigationActionPolicy) -> Void - ) { + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences) async -> (WKNavigationActionPolicy, WKWebpagePreferences) { if !isQueuePassed { let request = navigationAction.request let urlString = request.url?.absoluteString @@ -95,17 +91,14 @@ extension WebViewController: WKNavigationDelegate { if url.absoluteString == Constants.queueCloseUrl { delegate?.notifyViewControllerClosed() - decisionHandler(.cancel) - return + return (.cancel, preferences) } else if url.absoluteString == Constants.queueRestartSessionUrl { delegate?.notifyViewControllerSessionRestart() - decisionHandler(.cancel) - return + return (.cancel, preferences) } if isBlockedUrl(destinationUrl: url) { - decisionHandler(.cancel) - return + return (.cancel, preferences) } if isNotFrame { @@ -116,20 +109,18 @@ extension WebViewController: WKNavigationDelegate { isQueuePassed = true let queueitToken = extractQueueToken(urlString) delegate?.notifyViewControllerQueuePassed(queueToken: queueitToken) - decisionHandler(.cancel) - return + return (.cancel, preferences) } } if navigationAction.navigationType == .linkActivated && !isQueueUrl { - UIApplication.shared.open(request.url!) - decisionHandler(.cancel) - return + await UIApplication.shared.open(request.url!) + return (.cancel, preferences) } } } - - decisionHandler(.allow) + + return (.allow, preferences) } func webView(_: WKWebView, didStartProvisionalNavigation _: WKNavigation!) {} From f6a455dc31a3c3680a7a8afb42effad0a2126f15 Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Thu, 27 Mar 2025 15:51:58 +0100 Subject: [PATCH 08/13] refactor(types): remove optional types from queue handling - Make queueUrl, targetUrl and queueToken non-optional in TryPassResult - Update QueueITEngine and WaitingRoomProvider to handle non-optional values - Remove unnecessary optional handling in showQueue method - Simplify getUserId call by removing async keyword --- Sources/QueueITLib/QueueITEngine.swift | 3 +-- Sources/QueueITLib/TryPassResult.swift | 6 +++--- Sources/QueueITLib/WaitingRoomProvider.swift | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Sources/QueueITLib/QueueITEngine.swift b/Sources/QueueITLib/QueueITEngine.swift index 1e26d75..0bc71f5 100644 --- a/Sources/QueueITLib/QueueITEngine.swift +++ b/Sources/QueueITLib/QueueITEngine.swift @@ -137,8 +137,7 @@ extension QueueItEngine: WaitingRoomProviderDelegate { let queueDisabledInfo = QueueDisabledInfo(queueitToken: queuePassResult.queueToken) queueDisabledDelegate?.notifyQueueDisabled(queueDisabledInfo: queueDisabledInfo) default: - // TODO: fix optional parameter - showQueue(queueUrl: queuePassResult.queueUrl ?? "", targetUrl: queuePassResult.targetUrl ?? "") + showQueue(queueUrl: queuePassResult.queueUrl, targetUrl: queuePassResult.targetUrl) } } diff --git a/Sources/QueueITLib/TryPassResult.swift b/Sources/QueueITLib/TryPassResult.swift index ca9330b..35c9fa6 100644 --- a/Sources/QueueITLib/TryPassResult.swift +++ b/Sources/QueueITLib/TryPassResult.swift @@ -1,9 +1,9 @@ import Foundation public struct TryPassResult { - public let queueUrl: String? - public let targetUrl: String? + public let queueUrl: String + public let targetUrl: String public let redirectType: String public let isPassedThrough: Bool - public let queueToken: String? + public let queueToken: String } diff --git a/Sources/QueueITLib/WaitingRoomProvider.swift b/Sources/QueueITLib/WaitingRoomProvider.swift index bf5aa25..6562430 100644 --- a/Sources/QueueITLib/WaitingRoomProvider.swift +++ b/Sources/QueueITLib/WaitingRoomProvider.swift @@ -98,7 +98,7 @@ private extension WaitingRoomProvider { } func tryEnqueueWithUserAgent(secretAgent: String, enqueueToken: String?, enqueueKey: String?) async throws { - let userId = await Utils.getUserId() + let userId = Utils.getUserId() let userAgent = "\(secretAgent);\(Utils.getLibraryVersion())" let sdkVersion = Utils.getSdkVersion() apiClient = ApiClient() @@ -138,10 +138,10 @@ private extension WaitingRoomProvider { func handleAppEnqueueResponse( queueURL: String, - eventTargetURL: String?, - queueItToken: String? + eventTargetURL: String, + queueItToken: String ) async { - let isPassedThrough = !(queueItToken?.isEmpty ?? true) + let isPassedThrough = !queueItToken.isEmpty let redirectType = getRedirectType(fromToken: queueItToken) let tryPassResult = TryPassResult( From cf895092f02a3bb782c0db690e1d335650aaf90d Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Thu, 27 Mar 2025 15:55:47 +0100 Subject: [PATCH 09/13] Improved .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index abca1e5..29f2a42 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build QueueITLibSwift.xcframework.zip .DS_Store .index-build +.build From fb7a16b84b83a861653d329762723e94e72338cb Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Fri, 28 Mar 2025 09:42:59 +0100 Subject: [PATCH 10/13] style(webview): change WebView background color to white Previously the WebView had a transparent background which could cause visual issues. Setting it to white provides a cleaner, more consistent appearance. --- Sources/QueueITLib/WebViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/QueueITLib/WebViewController.swift b/Sources/QueueITLib/WebViewController.swift index f0931a4..f169eb9 100644 --- a/Sources/QueueITLib/WebViewController.swift +++ b/Sources/QueueITLib/WebViewController.swift @@ -55,7 +55,7 @@ final class WebViewController: UIViewController { webView.navigationDelegate = self webView.autoresizingMask = [.flexibleHeight, .flexibleWidth] webView.isOpaque = false - webView.backgroundColor = .clear + webView.backgroundColor = .white view.addSubview(webView) view.addSubview(spinner) From cbeb45658f436c53c967152af8230bf69af3ea39 Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Fri, 4 Apr 2025 13:40:13 +0200 Subject: [PATCH 11/13] refactor(queue-engine): consolidate delegate protocols and improve type safety - Combine all queue delegate protocols into single QueueItEngineDelegate - Remove unnecessary optional string parameters from delegate methods - Improve error handling by passing actual error messages - Simplify delegate management in QueueItEngine class --- Sources/QueueITLib/QueueITEngine.swift | 82 ++++++-------------- Sources/QueueITLib/WaitingRoomProvider.swift | 4 +- Sources/QueueITLib/WaitingRoomView.swift | 4 +- Sources/QueueITLib/WebViewController.swift | 2 +- 4 files changed, 28 insertions(+), 64 deletions(-) diff --git a/Sources/QueueITLib/QueueITEngine.swift b/Sources/QueueITLib/QueueITEngine.swift index 0bc71f5..27bb65c 100644 --- a/Sources/QueueITLib/QueueITEngine.swift +++ b/Sources/QueueITLib/QueueITEngine.swift @@ -1,57 +1,21 @@ import UIKit -public protocol QueuePassedDelegate: AnyObject { - @MainActor func notifyYourTurn(queuePassedInfo: QueuePassedInfo?) -} - -public protocol QueueViewWillOpenDelegate: AnyObject { +public protocol QueueItEngineDelegate: AnyObject { + @MainActor func notifyQueuePassed(info: QueuePassedInfo?) @MainActor func notifyQueueViewWillOpen() -} - -public protocol QueueDisabledDelegate: AnyObject { @MainActor func notifyQueueDisabled(queueDisabledInfo: QueueDisabledInfo?) -} - -public protocol QueueUnavailableDelegate: AnyObject { - @MainActor func notifyQueueITUnavailable(errorMessage: String) -} - -public protocol QueueErrorDelegate: AnyObject { + @MainActor func notifyQueueUnavailable(errorMessage: String) @MainActor func notifyQueueError(errorMessage: String, errorCode: Int) -} - -public protocol QueueViewClosedDelegate: AnyObject { @MainActor func notifyViewClosed() -} - -public protocol QueueUserExitedDelegate: AnyObject { @MainActor func notifyUserExited() -} - -public protocol QueueSessionRestartDelegate: AnyObject { @MainActor func notifySessionRestart() -} - -public protocol QueueUrlChangedDelegate: AnyObject { @MainActor func notifyQueueUrlChanged(url: String) -} - -public protocol QueueViewDidAppearDelegate: AnyObject { @MainActor func notifyQueueViewDidAppear() } @MainActor public final class QueueItEngine { - public weak var queuePassedDelegate: QueuePassedDelegate? - public weak var queueViewWillOpenDelegate: QueueViewWillOpenDelegate? - public weak var queueDisabledDelegate: QueueDisabledDelegate? - public weak var queueUnavailableDelegate: QueueUnavailableDelegate? - public weak var queueErrorDelegate: QueueErrorDelegate? - public weak var queueViewClosedDelegate: QueueViewClosedDelegate? - public weak var queueUserExitedDelegate: QueueUserExitedDelegate? - public weak var queueSessionRestartDelegate: QueueSessionRestartDelegate? - public weak var queueUrlChangedDelegate: QueueUrlChangedDelegate? - public weak var queueViewDidAppearDelegate: QueueViewDidAppearDelegate? + public weak var delegate: QueueItEngineDelegate? public weak var host: UIViewController? private var waitingRoomProvider: WaitingRoomProvider @@ -86,9 +50,12 @@ public final class QueueItEngine { public func run(withEnqueueToken enqueueToken: String) async throws { try await waitingRoomProvider.tryPassWithEnqueueToken(enqueueToken) } - - public func run() async throws { - try await waitingRoomProvider.tryPass() + + @MainActor + public func run() { + Task { @MainActor in + try await waitingRoomProvider.tryPass() + } } @MainActor public func showQueue(queueUrl: String, targetUrl: String) { @@ -98,32 +65,31 @@ public final class QueueItEngine { extension QueueItEngine: WaitingRoomViewDelegate { @MainActor public func notifyViewUserExited() { - queueUserExitedDelegate?.notifyUserExited() + delegate?.notifyUserExited() } @MainActor public func notifyViewUserClosed() { - queueViewClosedDelegate?.notifyViewClosed() + delegate?.notifyViewClosed() } @MainActor public func notifyViewSessionRestart() { - queueSessionRestartDelegate?.notifySessionRestart() + delegate?.notifySessionRestart() } @MainActor public func notifyQueuePassed(info: QueuePassedInfo?) { - queuePassedDelegate?.notifyYourTurn(queuePassedInfo: info) + delegate?.notifyQueuePassed(info: info) } @MainActor public func notifyViewQueueDidAppear() { - queueViewDidAppearDelegate?.notifyQueueViewDidAppear() + delegate?.notifyQueueViewDidAppear() } @MainActor public func notifyViewQueueWillOpen() { - queueViewWillOpenDelegate?.notifyQueueViewWillOpen() + delegate?.notifyQueueViewWillOpen() } - @MainActor public func notifyViewUpdatePageUrl(urlString: String?) { - // TODO: fix optional parameter - queueUrlChangedDelegate?.notifyQueueUrlChanged(url: urlString ?? "") + @MainActor public func notifyViewUpdatePageUrl(urlString: String) { + delegate?.notifyQueueUrlChanged(url: urlString) } } @@ -132,21 +98,19 @@ extension QueueItEngine: WaitingRoomProviderDelegate { switch queuePassResult.redirectType { case "safetynet": let queuePassedInfo = QueuePassedInfo(queueitToken: queuePassResult.queueToken) - queuePassedDelegate?.notifyYourTurn(queuePassedInfo: queuePassedInfo) + delegate?.notifyQueuePassed(info: queuePassedInfo) case "disabled", "idle", "afterevent": let queueDisabledInfo = QueueDisabledInfo(queueitToken: queuePassResult.queueToken) - queueDisabledDelegate?.notifyQueueDisabled(queueDisabledInfo: queueDisabledInfo) + delegate?.notifyQueueDisabled(queueDisabledInfo: queueDisabledInfo) default: showQueue(queueUrl: queuePassResult.queueUrl, targetUrl: queuePassResult.targetUrl) } } - @MainActor public func notifyProviderFailure(errorMessage: String?, errorCode: Int) async { - // TODO: fix optional parameter - let errorMessage = errorMessage ?? "" + @MainActor public func notifyProviderFailure(errorMessage: String, errorCode: Int) async { if errorCode == 3 { - queueUnavailableDelegate?.notifyQueueITUnavailable(errorMessage: errorMessage) + delegate?.notifyQueueUnavailable(errorMessage: errorMessage) } - queueErrorDelegate?.notifyQueueError(errorMessage: errorMessage, errorCode: errorCode) + delegate?.notifyQueueError(errorMessage: errorMessage, errorCode: errorCode) } } diff --git a/Sources/QueueITLib/WaitingRoomProvider.swift b/Sources/QueueITLib/WaitingRoomProvider.swift index 6562430..c2bc289 100644 --- a/Sources/QueueITLib/WaitingRoomProvider.swift +++ b/Sources/QueueITLib/WaitingRoomProvider.swift @@ -3,7 +3,7 @@ import Foundation @MainActor public protocol WaitingRoomProviderDelegate: AnyObject { func notifyProviderSuccess(queuePassResult: TryPassResult) async - func notifyProviderFailure(errorMessage: String?, errorCode: Int) async + func notifyProviderFailure(errorMessage: String, errorCode: Int) async } enum QueueITRuntimeError: Int { @@ -129,7 +129,7 @@ private extension WaitingRoomProvider { } catch { let nsError = error as NSError if nsError.code >= 400, nsError.code < 500 { - await self.delegate?.notifyProviderFailure(errorMessage: "", errorCode: nsError.code) + await self.delegate?.notifyProviderFailure(errorMessage: error.localizedDescription, errorCode: nsError.code) } else { await self.enqueueRetryMonitor(enqueueToken: enqueueToken, enqueueKey: enqueueKey) } diff --git a/Sources/QueueITLib/WaitingRoomView.swift b/Sources/QueueITLib/WaitingRoomView.swift index 1e1bf2c..057c156 100644 --- a/Sources/QueueITLib/WaitingRoomView.swift +++ b/Sources/QueueITLib/WaitingRoomView.swift @@ -7,7 +7,7 @@ public protocol WaitingRoomViewDelegate: AnyObject { @MainActor func notifyQueuePassed(info: QueuePassedInfo?) @MainActor func notifyViewQueueDidAppear() @MainActor func notifyViewQueueWillOpen() - @MainActor func notifyViewUpdatePageUrl(urlString: String?) + @MainActor func notifyViewUpdatePageUrl(urlString: String) } public final class WaitingRoomView { @@ -79,7 +79,7 @@ extension WaitingRoomView: WebViewControllerDelegate { close() } - @MainActor func notifyViewControllerPageUrlChanged(urlString: String?) { + @MainActor func notifyViewControllerPageUrlChanged(urlString: String) { delegate?.notifyViewUpdatePageUrl(urlString: urlString) } } diff --git a/Sources/QueueITLib/WebViewController.swift b/Sources/QueueITLib/WebViewController.swift index f169eb9..ec9f21f 100644 --- a/Sources/QueueITLib/WebViewController.swift +++ b/Sources/QueueITLib/WebViewController.swift @@ -6,7 +6,7 @@ protocol WebViewControllerDelegate: AnyObject { @MainActor func notifyViewControllerUserExited() @MainActor func notifyViewControllerSessionRestart() @MainActor func notifyViewControllerQueuePassed(queueToken: String?) - @MainActor func notifyViewControllerPageUrlChanged(urlString: String?) + @MainActor func notifyViewControllerPageUrlChanged(urlString: String) } final class WebViewController: UIViewController { From b11111e0a2cd04a0f057cfe2c8ea5c6f125d2505 Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Wed, 23 Apr 2025 13:35:19 +0200 Subject: [PATCH 12/13] feat(ui): enhance waiting room presentation and controls - Change modal presentation style from fullScreen to pageSheet - Add close button with X icon to allow manual dismissal - Pass completion handler to dismiss animation for proper callback execution --- Sources/QueueITLib/WaitingRoomView.swift | 4 ++-- Sources/QueueITLib/WebViewController.swift | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Sources/QueueITLib/WaitingRoomView.swift b/Sources/QueueITLib/WaitingRoomView.swift index 057c156..e06c54c 100644 --- a/Sources/QueueITLib/WaitingRoomView.swift +++ b/Sources/QueueITLib/WaitingRoomView.swift @@ -35,7 +35,7 @@ public final class WaitingRoomView { queueWKVC.delegate = self if #available(iOS 13.0, *) { - queueWKVC.modalPresentationStyle = UIModalPresentationStyle.fullScreen + queueWKVC.modalPresentationStyle = UIModalPresentationStyle.pageSheet } DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(delayInterval)) { [weak self] in @@ -90,7 +90,7 @@ private extension WaitingRoomView { guard let self, let host else { return } - host.dismiss(animated: true) + host.dismiss(animated: true, completion: onComplete) } } diff --git a/Sources/QueueITLib/WebViewController.swift b/Sources/QueueITLib/WebViewController.swift index ec9f21f..3b4b342 100644 --- a/Sources/QueueITLib/WebViewController.swift +++ b/Sources/QueueITLib/WebViewController.swift @@ -14,6 +14,7 @@ final class WebViewController: UIViewController { private var webView: WKWebView? private var spinner: UIActivityIndicatorView? + private var closeButton: UIButton? private var isQueuePassed: Bool private var queueUrl: String @@ -46,8 +47,14 @@ final class WebViewController: UIViewController { spinner = UIActivityIndicatorView(frame: view.bounds) webView = WKWebView(frame: view.bounds, configuration: config) + + closeButton = UIButton(type: .system) + closeButton?.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + closeButton?.addTarget(self, action: #selector(closeTapped), for: .touchUpInside) + closeButton?.translatesAutoresizingMaskIntoConstraints = false + closeButton?.tintColor = .white - guard let spinner, let webView else { + guard let spinner, let webView, let closeButton else { return } @@ -59,9 +66,16 @@ final class WebViewController: UIViewController { view.addSubview(webView) view.addSubview(spinner) + view.addSubview(closeButton) webView.frame = view.bounds spinner.frame = view.bounds + NSLayoutConstraint.activate([ + closeButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8), + closeButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -8), + closeButton.widthAnchor.constraint(equalToConstant: 44), + closeButton.heightAnchor.constraint(equalToConstant: 44) + ]) } func loadWebView() { @@ -181,4 +195,8 @@ private extension WebViewController { } @objc func appWillResignActive(_: Notification) {} + + @objc func closeTapped() { + delegate?.notifyViewControllerClosed() + } } From 91a9f75e2512cb7652f1222520254d798310fc2d Mon Sep 17 00:00:00 2001 From: alexanderthoren Date: Wed, 23 Apr 2025 14:32:19 +0200 Subject: [PATCH 13/13] feat(WebViewController): add delegate notification on view close Add viewDidDisappear method to notify delegate when the view controller is closed, ensuring proper cleanup and state management when the queue view is dismissed. This improves the SDK's ability to handle view lifecycle events correctly. Signed-off-by: alexanderthoren --- Sources/QueueITLib/WebViewController.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/QueueITLib/WebViewController.swift b/Sources/QueueITLib/WebViewController.swift index 3b4b342..a648780 100644 --- a/Sources/QueueITLib/WebViewController.swift +++ b/Sources/QueueITLib/WebViewController.swift @@ -77,6 +77,12 @@ final class WebViewController: UIViewController { closeButton.heightAnchor.constraint(equalToConstant: 44) ]) } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + self.delegate?.notifyViewControllerClosed() + } func loadWebView() { guard let spinner,