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/.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/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/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/ApiClient.swift b/Sources/QueueITLib/ApiClient.swift new file mode 100644 index 0000000..3676bb0 --- /dev/null +++ b/Sources/QueueITLib/ApiClient.swift @@ -0,0 +1,122 @@ +import Foundation + +typealias QueueServiceSuccess = (Data) -> Void +typealias QueueServiceFailure = (Error, String) -> Void + +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? + + static func getInstance() -> ApiClient { + 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 (Status?) -> Void, + failure: @escaping QueueServiceFailure + ) { + 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 = ApiClient.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 + ) + } + + func submitPOSTPath( + path: String, + body bodyDict: [String: Any], + success: @escaping QueueServiceSuccess, + failure: @escaping QueueServiceFailure + ) { + guard let url = URL(string: path) else { + let error = NSError( + domain: "ApiClient", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Invalid URL"] + ) + failure(error, "Invalid URL") + 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 + ) { + return + } +} diff --git a/Sources/QueueITLib/Connection.swift b/Sources/QueueITLib/Connection.swift new file mode 100644 index 0000000..0c6f9b6 --- /dev/null +++ b/Sources/QueueITLib/Connection.swift @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000..08c2128 --- /dev/null +++ b/Sources/QueueITLib/ConnectionRequest.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/Constants.swift b/Sources/QueueITLib/Constants.swift new file mode 100644 index 0000000..7f9e5b8 --- /dev/null +++ b/Sources/QueueITLib/Constants.swift @@ -0,0 +1,5 @@ +enum Constants { + static let queueCloseUrl = "queueit://close" + static let queueRestartSessionUrl = "queueit://restartSession" + static let sdkVersion = "iOS-3.4.4" +} diff --git a/Sources/QueueITLib/QueueITEngine.swift b/Sources/QueueITLib/QueueITEngine.swift new file mode 100644 index 0000000..884e59d --- /dev/null +++ b/Sources/QueueITLib/QueueITEngine.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: WaitingRoomProvider + private var waitingRoomView: WaitingRoomView + + public init(host: UIViewController, customerId: String, eventOrAliasId: String, layoutName: String?, language: String?) { + self.host = host + + waitingRoomProvider = WaitingRoomProvider( + customerId: customerId, + eventOrAliasId: eventOrAliasId, + layoutName: layoutName, + language: language + ) + waitingRoomView = WaitingRoomView(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 { + try waitingRoomProvider.tryPassWithEnqueueKey(enqueueKey) + } + + public func run(withEnqueueToken enqueueToken: String) throws { + try waitingRoomProvider.tryPassWithEnqueueToken(enqueueToken) + } + + public func run() throws { + try waitingRoomProvider.tryPass() + } + + public func showQueue(queueUrl: String, targetUrl: String) { + waitingRoomView.show(queueUrl: queueUrl, targetUrl: targetUrl) + } +} + +extension QueueItEngine: WaitingRoomViewDelegate { + 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: WaitingRoomProviderDelegate { + func notifyProviderSuccess(queuePassResult: TryPassResult) { + 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/QueueInfo.swift b/Sources/QueueITLib/QueueInfo.swift new file mode 100644 index 0000000..5411c03 --- /dev/null +++ b/Sources/QueueITLib/QueueInfo.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/Reachability.swift b/Sources/QueueITLib/Reachability.swift new file mode 100644 index 0000000..79236ed --- /dev/null +++ b/Sources/QueueITLib/Reachability.swift @@ -0,0 +1,118 @@ +import Foundation +import Network +import SystemConfiguration + + +extension Notification.Name { + static let reachabilityChanged = Notification.Name("kNetworkReachabilityChangedNotification") +} + +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) -> Reachability { + let reachability = Reachability() + reachability.startMonitoringHost(hostName: hostName) + return reachability + } + + static func reachabilityWithAddress(_ hostAddress: sockaddr_in) -> Reachability { + let reachability = Reachability() + reachability.startMonitoringIP(address: hostAddress) + return reachability + } + + static func reachabilityForInternetConnection() -> Reachability { + let reachability = Reachability() + reachability.startMonitoringInternet() + return reachability + } + + static func reachabilityForLocalWiFi() -> Reachability { + let reachability = Reachability() + 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 Reachability { + enum NetworkStatus: Int { + case notReachable = 0 + case reachableViaWiFi + case reachableViaWWAN + } +} + +private extension Reachability { + 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/Status.swift b/Sources/QueueITLib/Status.swift new file mode 100644 index 0000000..a5da82f --- /dev/null +++ b/Sources/QueueITLib/Status.swift @@ -0,0 +1,31 @@ +struct Status { + 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 Status { + 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/TryPassResult.swift b/Sources/QueueITLib/TryPassResult.swift new file mode 100644 index 0000000..30f0d98 --- /dev/null +++ b/Sources/QueueITLib/TryPassResult.swift @@ -0,0 +1,9 @@ +import Foundation + +struct TryPassResult { + let queueUrl: String? + let targetUrl: String? + let redirectType: String + let isPassedThrough: Bool + let queueToken: String? +} diff --git a/Sources/QueueITLib/Utils.swift b/Sources/QueueITLib/Utils.swift new file mode 100644 index 0000000..2e412f4 --- /dev/null +++ b/Sources/QueueITLib/Utils.swift @@ -0,0 +1,40 @@ +import Foundation +import WebKit + +enum Utils { + 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 Constants.sdkVersion + } +} diff --git a/Sources/QueueITLib/WaitingRoomProvider.swift b/Sources/QueueITLib/WaitingRoomProvider.swift new file mode 100644 index 0000000..33e8fb1 --- /dev/null +++ b/Sources/QueueITLib/WaitingRoomProvider.swift @@ -0,0 +1,198 @@ +import Foundation + +protocol WaitingRoomProviderDelegate: AnyObject { + func notifyProviderSuccess(queuePassResult: TryPassResult) + func notifyProviderFailure(errorMessage: String?, errorCode: Int) +} + +enum QueueITRuntimeError: Int { + case networkUnavailable = -100 + case requestAlreadyInProgress = 10 + + static let errorMessages = [ + networkUnavailable: "Network connection is unavailable", + requestAlreadyInProgress: "Enqueue request is already in progress", + ] +} + +final class WaitingRoomProvider { + static let maxRetrySec = 10 + static let initialWaitRetrySec = 1 + + weak var delegate: WaitingRoomProviderDelegate? + + private let customerId: String + private let eventOrAliasId: String + private let layoutName: String? + private let language: String? + + private var deltaSec: Int = WaitingRoomProvider.initialWaitRetrySec + private var requestInProgress: Bool = false + 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 = Reachability.reachabilityForInternetConnection() + } + + func tryPass() throws { + try tryEnqueue(enqueueToken: nil, enqueueKey: nil) + } + + func tryPassWithEnqueueToken(_ enqueueToken: String?) throws { + try tryEnqueue(enqueueToken: enqueueToken, enqueueKey: nil) + } + + func tryPassWithEnqueueKey(_ enqueueKey: String?) throws { + try tryEnqueue(enqueueToken: nil, enqueueKey: enqueueKey) + } + + func isRequestInProgress() -> Bool { + return requestInProgress + } +} + +private extension WaitingRoomProvider { + func tryEnqueue(enqueueToken: String?, enqueueKey: String?) throws { + 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 + + 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 + ) + } + } + } + + func tryEnqueueWithUserAgent(secretAgent: String, enqueueToken: String?, enqueueKey: String?) throws { + let userId = 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) + } + } + } + ) + } + + func handleAppEnqueueResponse( + queueURL: String, + eventTargetURL: String?, + queueItToken: String? + ) { + let isPassedThrough = !(queueItToken?.isEmpty ?? true) + let redirectType = getRedirectType(fromToken: queueItToken) + + let TryPassResult = TryPassResult( + queueUrl: queueURL, + targetUrl: eventTargetURL, + redirectType: redirectType, + isPassedThrough: isPassedThrough, + queueToken: queueItToken + ) + delegate?.notifyProviderSuccess(queuePassResult: TryPassResult) + } + + func enqueueRetryMonitor(enqueueToken: String?, enqueueKey: String?) { + if deltaSec < WaitingRoomProvider.maxRetrySec { + try? tryEnqueue(enqueueToken: enqueueToken, enqueueKey: enqueueKey) + Thread.sleep(forTimeInterval: TimeInterval(deltaSec)) + deltaSec *= 2 + } else { + deltaSec = WaitingRoomProvider.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/WaitingRoomView.swift b/Sources/QueueITLib/WaitingRoomView.swift new file mode 100644 index 0000000..453dd07 --- /dev/null +++ b/Sources/QueueITLib/WaitingRoomView.swift @@ -0,0 +1,100 @@ +import UIKit + +protocol WaitingRoomViewDelegate: AnyObject { + func notifyViewUserExited() + func notifyViewUserClosed() + func notifyViewSessionRestart() + func notifyQueuePassed(info: QueuePassedInfo?) + func notifyViewQueueDidAppear() + func notifyViewQueueWillOpen() + func notifyViewUpdatePageUrl(urlString: String?) +} + +final class WaitingRoomView { + weak var host: UIViewController? + weak var delegate: WaitingRoomViewDelegate? + weak var currentWebView: WebViewController? + + 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 = WebViewController( + 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 WaitingRoomView: WebViewControllerDelegate { + 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 WaitingRoomView { + 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/WebViewController.swift b/Sources/QueueITLib/WebViewController.swift new file mode 100644 index 0000000..ee6c755 --- /dev/null +++ b/Sources/QueueITLib/WebViewController.swift @@ -0,0 +1,193 @@ +import UIKit +import WebKit + +protocol WebViewControllerDelegate: AnyObject { + func notifyViewControllerClosed() + func notifyViewControllerUserExited() + func notifyViewControllerSessionRestart() + func notifyViewControllerQueuePassed(queueToken: String?) + func notifyViewControllerPageUrlChanged(urlString: String?) +} + +final class WebViewController: UIViewController { + weak var delegate: WebViewControllerDelegate? + 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 WebViewController: 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 == Constants.queueCloseUrl { + delegate?.notifyViewControllerClosed() + decisionHandler(.cancel) + return + } else if url.absoluteString == Constants.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 WebViewController { + 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[..