From 80caa92f816a8c62dd38dfbf065cad19137ed311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Parreira?= Date: Mon, 21 Sep 2015 13:00:12 +0100 Subject: [PATCH] Repository rename (import from previous) --- LICENSE | 22 + .../project.pbxproj | 288 +++ .../contents.xcworkspacedata | 7 + .../RCTRealtimeMessaging.xccheckout | 41 + .../UserInterfaceState.xcuserstate | Bin 0 -> 16229 bytes .../xcschemes/RCTRealtimeMessaging.xcscheme | 110 + .../xcschemes/xcschememanagement.plist | 27 + ...AppDelegate+RealtimeRCTPushNotifications.h | 17 + ...AppDelegate+RealtimeRCTPushNotifications.m | 100 + RCTRealtimeMessaging/Balancer.h | 15 + RCTRealtimeMessaging/Balancer.m | 59 + RCTRealtimeMessaging/OrtcClient.h | 376 ++++ RCTRealtimeMessaging/OrtcClient.m | 1966 +++++++++++++++++ RCTRealtimeMessaging/RCTRealtimeMessaging.h | 20 + RCTRealtimeMessaging/RCTRealtimeMessaging.m | 450 ++++ RCTRealtimeMessagingIOS.js | 147 ++ README.md | 901 +++++++- SocketRocket/NSData+SRB64Additions.h | 23 + SocketRocket/RCTSRWebSocket.h | 132 ++ SocketRocket/base64.h | 34 + package.json | 27 + 21 files changed, 4760 insertions(+), 2 deletions(-) create mode 100644 LICENSE create mode 100644 RCTRealtimeMessaging.xcodeproj/project.pbxproj create mode 100644 RCTRealtimeMessaging.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 RCTRealtimeMessaging.xcodeproj/project.xcworkspace/xcshareddata/RCTRealtimeMessaging.xccheckout create mode 100644 RCTRealtimeMessaging.xcodeproj/project.xcworkspace/xcuserdata/jcaixinha.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 RCTRealtimeMessaging.xcodeproj/xcuserdata/jcaixinha.xcuserdatad/xcschemes/RCTRealtimeMessaging.xcscheme create mode 100644 RCTRealtimeMessaging.xcodeproj/xcuserdata/jcaixinha.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 RCTRealtimeMessaging/AppDelegate+RealtimeRCTPushNotifications.h create mode 100644 RCTRealtimeMessaging/AppDelegate+RealtimeRCTPushNotifications.m create mode 100644 RCTRealtimeMessaging/Balancer.h create mode 100644 RCTRealtimeMessaging/Balancer.m create mode 100644 RCTRealtimeMessaging/OrtcClient.h create mode 100644 RCTRealtimeMessaging/OrtcClient.m create mode 100644 RCTRealtimeMessaging/RCTRealtimeMessaging.h create mode 100644 RCTRealtimeMessaging/RCTRealtimeMessaging.m create mode 100644 RCTRealtimeMessagingIOS.js create mode 100644 SocketRocket/NSData+SRB64Additions.h create mode 100644 SocketRocket/RCTSRWebSocket.h create mode 100644 SocketRocket/base64.h create mode 100644 package.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b0bdc28 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Realtime Framework + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/RCTRealtimeMessaging.xcodeproj/project.pbxproj b/RCTRealtimeMessaging.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f0a4c0c --- /dev/null +++ b/RCTRealtimeMessaging.xcodeproj/project.pbxproj @@ -0,0 +1,288 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 57E5A0A71AD2D06E00B19F3B /* RCTRealtimeMessaging.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 57E5A0A61AD2D06E00B19F3B /* RCTRealtimeMessaging.h */; }; + 57E5A0A91AD2D06E00B19F3B /* RCTRealtimeMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 57E5A0A81AD2D06E00B19F3B /* RCTRealtimeMessaging.m */; }; + 57E5A0C91AD2D0E800B19F3B /* Balancer.m in Sources */ = {isa = PBXBuildFile; fileRef = 57E5A0C41AD2D0E800B19F3B /* Balancer.m */; }; + 57E5A0CA1AD2D0E800B19F3B /* OrtcClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 57E5A0C61AD2D0E800B19F3B /* OrtcClient.m */; }; + 57E5A0D11AD2D11B00B19F3B /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 57E5A0D01AD2D11B00B19F3B /* libicucore.dylib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 57E5A0A11AD2D06E00B19F3B /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + 57E5A0A71AD2D06E00B19F3B /* RCTRealtimeMessaging.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 5736D4F01B46CD8B009542D8 /* RCTSRWebSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTSRWebSocket.h; sourceTree = ""; }; + 57E5A0A31AD2D06E00B19F3B /* libRCTRealtimeMessaging.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTRealtimeMessaging.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 57E5A0A61AD2D06E00B19F3B /* RCTRealtimeMessaging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTRealtimeMessaging.h; sourceTree = ""; }; + 57E5A0A81AD2D06E00B19F3B /* RCTRealtimeMessaging.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTRealtimeMessaging.m; sourceTree = ""; }; + 57E5A0C31AD2D0E800B19F3B /* Balancer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Balancer.h; sourceTree = ""; }; + 57E5A0C41AD2D0E800B19F3B /* Balancer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Balancer.m; sourceTree = ""; }; + 57E5A0C51AD2D0E800B19F3B /* OrtcClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OrtcClient.h; sourceTree = ""; }; + 57E5A0C61AD2D0E800B19F3B /* OrtcClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OrtcClient.m; sourceTree = ""; }; + 57E5A0CD1AD2D11000B19F3B /* base64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = ""; }; + 57E5A0CE1AD2D11000B19F3B /* NSData+SRB64Additions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+SRB64Additions.h"; sourceTree = ""; }; + 57E5A0D01AD2D11B00B19F3B /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 57E5A0A01AD2D06E00B19F3B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 57E5A0D11AD2D11B00B19F3B /* libicucore.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 57E5A09A1AD2D06E00B19F3B = { + isa = PBXGroup; + children = ( + 57E5A0D01AD2D11B00B19F3B /* libicucore.dylib */, + 57E5A0CC1AD2D11000B19F3B /* SocketRocket */, + 57E5A0A51AD2D06E00B19F3B /* RCTRealtimeMessaging */, + 57E5A0A41AD2D06E00B19F3B /* Products */, + ); + sourceTree = ""; + }; + 57E5A0A41AD2D06E00B19F3B /* Products */ = { + isa = PBXGroup; + children = ( + 57E5A0A31AD2D06E00B19F3B /* libRCTRealtimeMessaging.a */, + ); + name = Products; + sourceTree = ""; + }; + 57E5A0A51AD2D06E00B19F3B /* RCTRealtimeMessaging */ = { + isa = PBXGroup; + children = ( + 57E5A0A61AD2D06E00B19F3B /* RCTRealtimeMessaging.h */, + 57E5A0A81AD2D06E00B19F3B /* RCTRealtimeMessaging.m */, + 57E5A0C31AD2D0E800B19F3B /* Balancer.h */, + 57E5A0C41AD2D0E800B19F3B /* Balancer.m */, + 57E5A0C51AD2D0E800B19F3B /* OrtcClient.h */, + 57E5A0C61AD2D0E800B19F3B /* OrtcClient.m */, + ); + path = RCTRealtimeMessaging; + sourceTree = ""; + }; + 57E5A0CC1AD2D11000B19F3B /* SocketRocket */ = { + isa = PBXGroup; + children = ( + 57E5A0CD1AD2D11000B19F3B /* base64.h */, + 57E5A0CE1AD2D11000B19F3B /* NSData+SRB64Additions.h */, + 5736D4F01B46CD8B009542D8 /* RCTSRWebSocket.h */, + ); + path = SocketRocket; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 57E5A0A21AD2D06E00B19F3B /* RCTRealtimeMessaging */ = { + isa = PBXNativeTarget; + buildConfigurationList = 57E5A0B71AD2D06E00B19F3B /* Build configuration list for PBXNativeTarget "RCTRealtimeMessaging" */; + buildPhases = ( + 57E5A09F1AD2D06E00B19F3B /* Sources */, + 57E5A0A01AD2D06E00B19F3B /* Frameworks */, + 57E5A0A11AD2D06E00B19F3B /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RCTRealtimeMessaging; + productName = RCTRealtimeMessaging; + productReference = 57E5A0A31AD2D06E00B19F3B /* libRCTRealtimeMessaging.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 57E5A09B1AD2D06E00B19F3B /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0620; + ORGANIZATIONNAME = Realtime; + TargetAttributes = { + 57E5A0A21AD2D06E00B19F3B = { + CreatedOnToolsVersion = 6.2; + }; + }; + }; + buildConfigurationList = 57E5A09E1AD2D06E00B19F3B /* Build configuration list for PBXProject "RCTRealtimeMessaging" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 57E5A09A1AD2D06E00B19F3B; + productRefGroup = 57E5A0A41AD2D06E00B19F3B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 57E5A0A21AD2D06E00B19F3B /* RCTRealtimeMessaging */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 57E5A09F1AD2D06E00B19F3B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 57E5A0A91AD2D06E00B19F3B /* RCTRealtimeMessaging.m in Sources */, + 57E5A0C91AD2D0E800B19F3B /* Balancer.m in Sources */, + 57E5A0CA1AD2D0E800B19F3B /* OrtcClient.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 57E5A0B51AD2D06E00B19F3B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 57E5A0B61AD2D06E00B19F3B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 57E5A0B81AD2D06E00B19F3B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 57E5A0B91AD2D06E00B19F3B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 57E5A09E1AD2D06E00B19F3B /* Build configuration list for PBXProject "RCTRealtimeMessaging" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57E5A0B51AD2D06E00B19F3B /* Debug */, + 57E5A0B61AD2D06E00B19F3B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 57E5A0B71AD2D06E00B19F3B /* Build configuration list for PBXNativeTarget "RCTRealtimeMessaging" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57E5A0B81AD2D06E00B19F3B /* Debug */, + 57E5A0B91AD2D06E00B19F3B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 57E5A09B1AD2D06E00B19F3B /* Project object */; +} diff --git a/RCTRealtimeMessaging.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/RCTRealtimeMessaging.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c94c227 --- /dev/null +++ b/RCTRealtimeMessaging.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/RCTRealtimeMessaging.xcodeproj/project.xcworkspace/xcshareddata/RCTRealtimeMessaging.xccheckout b/RCTRealtimeMessaging.xcodeproj/project.xcworkspace/xcshareddata/RCTRealtimeMessaging.xccheckout new file mode 100644 index 0000000..06c1162 --- /dev/null +++ b/RCTRealtimeMessaging.xcodeproj/project.xcworkspace/xcshareddata/RCTRealtimeMessaging.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + 682DD9C9-3D49-4912-8DFA-436162155BC6 + IDESourceControlProjectName + RCTRealtimeMessaging + IDESourceControlProjectOriginsDictionary + + 8D36A2E25D1F69289FF5D730990237D3FD9BBC1E + https://github.com/JoaoCaixinha/RCTRealtimeMessaging.git + + IDESourceControlProjectPath + RCTRealtimeMessaging.xcodeproj + IDESourceControlProjectRelativeInstallPathDictionary + + 8D36A2E25D1F69289FF5D730990237D3FD9BBC1E + ../.. + + IDESourceControlProjectURL + https://github.com/JoaoCaixinha/RCTRealtimeMessaging.git + IDESourceControlProjectVersion + 111 + IDESourceControlProjectWCCIdentifier + 8D36A2E25D1F69289FF5D730990237D3FD9BBC1E + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 8D36A2E25D1F69289FF5D730990237D3FD9BBC1E + IDESourceControlWCCName + RCTRealtimeMessaging + + + + diff --git a/RCTRealtimeMessaging.xcodeproj/project.xcworkspace/xcuserdata/jcaixinha.xcuserdatad/UserInterfaceState.xcuserstate b/RCTRealtimeMessaging.xcodeproj/project.xcworkspace/xcuserdata/jcaixinha.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..b9c1017aa0841aff721bed8af6bf8581c1eb724a GIT binary patch literal 16229 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RCTRealtimeMessaging.xcodeproj/xcuserdata/jcaixinha.xcuserdatad/xcschemes/xcschememanagement.plist b/RCTRealtimeMessaging.xcodeproj/xcuserdata/jcaixinha.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..449a78e --- /dev/null +++ b/RCTRealtimeMessaging.xcodeproj/xcuserdata/jcaixinha.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + RCTRealtimeMessaging.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + 57E5A0A21AD2D06E00B19F3B + + primary + + + 57E5A0AD1AD2D06E00B19F3B + + primary + + + + + diff --git a/RCTRealtimeMessaging/AppDelegate+RealtimeRCTPushNotifications.h b/RCTRealtimeMessaging/AppDelegate+RealtimeRCTPushNotifications.h new file mode 100644 index 0000000..8c2f483 --- /dev/null +++ b/RCTRealtimeMessaging/AppDelegate+RealtimeRCTPushNotifications.h @@ -0,0 +1,17 @@ +// +// AppDelegate+RealtimeRCTPushNotifications.h +// RCTRealtimeMessaging +// +// Created by Joao Caixinha on 07/09/15. +// Copyright (c) 2015 Realtime. All rights reserved. +// +#import "AppDelegate.h" + + +@interface AppDelegate (RealtimeRCTPushNotifications) + +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo; + +@end diff --git a/RCTRealtimeMessaging/AppDelegate+RealtimeRCTPushNotifications.m b/RCTRealtimeMessaging/AppDelegate+RealtimeRCTPushNotifications.m new file mode 100644 index 0000000..7c70339 --- /dev/null +++ b/RCTRealtimeMessaging/AppDelegate+RealtimeRCTPushNotifications.m @@ -0,0 +1,100 @@ +// +// AppDelegate+RealtimeRCTPushNotifications.m +// RCTRealtimeMessaging +// +// Created by Joao Caixinha on 07/09/15. +// Copyright (c) 2015 Realtime. All rights reserved. +// + +#import "AppDelegate+RealtimeRCTPushNotifications.h" +#import +#define NOTIFICATIONS_KEY @"Local_Storage_Notifications" + +#pragma GCC diagnostic ignored "-Wundeclared-selector" +@implementation AppDelegate (RealtimeRCTPushNotifications) + + ++ (void)load +{ + [[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(registForNotifications) name:UIApplicationDidFinishLaunchingNotification object:nil]; +} + + ++ (BOOL)registForNotifications +{ +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) { + UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil]; + [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; + } else { + [[UIApplication sharedApplication] registerForRemoteNotificationTypes: UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge]; + } +#else + [application registerForRemoteNotificationTypes: UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge]; +#endif + + return YES; +} + + + +- (void)application:(UIApplication *)application +didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + NSString* newToken = [deviceToken description]; + newToken = [newToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; + newToken = [newToken stringByReplacingOccurrencesOfString:@" " withString:@""]; + NSLog(@"\n\n - didRegisterForRemoteNotificationsWithDeviceToken:\n%@\n", deviceToken); + + id ortc = NSClassFromString (@"OrtcClient"); + if ([ortc respondsToSelector:@selector(setDEVICE_TOKEN:)]) { + [ortc performSelector:@selector(setDEVICE_TOKEN:) withObject:[[NSString alloc] initWithString:newToken]]; + } +} + + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler +{ + [self application:application didReceiveRemoteNotification:userInfo]; + completionHandler(UIBackgroundFetchResultNewData); +} + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo +{ + if ([userInfo objectForKey:@"C"] && [userInfo objectForKey:@"M"] && [userInfo objectForKey:@"A"]) { + + if ([[[userInfo objectForKey:@"aps" ] objectForKey:@"alert"] isKindOfClass:[NSString class]]) { + NSString *ortcMessage = [NSString stringWithFormat:@"a[\"{\\\"ch\\\":\\\"%@\\\",\\\"m\\\":\\\"%@\\\"}\"]", [userInfo objectForKey:@"C"], [userInfo objectForKey:@"M"]]; + + NSMutableDictionary *notificationsDict = [[NSMutableDictionary alloc] initWithDictionary:[[NSUserDefaults standardUserDefaults] objectForKey:NOTIFICATIONS_KEY]]; + NSMutableArray *notificationsArray = [[NSMutableArray alloc] initWithArray:[notificationsDict objectForKey:[userInfo objectForKey:@"A"]]]; + [notificationsArray addObject:ortcMessage]; + [notificationsDict setObject:notificationsArray forKey:[userInfo objectForKey:@"A"]]; + + [[NSUserDefaults standardUserDefaults] setObject:notificationsDict forKey:NOTIFICATIONS_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [[NSNotificationCenter defaultCenter] postNotificationName:@"ApnsNotification" object:nil userInfo:userInfo]; + + }else{ + + [[NSNotificationCenter defaultCenter] postNotificationName:@"Notification" object:nil userInfo:userInfo]; + + } + } +} + + +- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error +{ + NSLog(@"Failed to register with error : %@", error); + + [[NSNotificationCenter defaultCenter] postNotificationName:@"ApnsRegisterError" object:nil userInfo:[NSDictionary dictionaryWithObject:error forKey:@"ApnsRegisterError"]]; +} + + + + + +@end diff --git a/RCTRealtimeMessaging/Balancer.h b/RCTRealtimeMessaging/Balancer.h new file mode 100644 index 0000000..06cd139 --- /dev/null +++ b/RCTRealtimeMessaging/Balancer.h @@ -0,0 +1,15 @@ +// +// Balancer.h +// OrtcClient +// +// Created by Marcin Kulwikowski on 15/07/14. +// +// + +#import + +@interface Balancer : NSObject + +- initWithCluster:(NSString*) aCluster serverUrl:(NSString*)url isCluster:(BOOL)isCluster appKey:(NSString*) anAppKey callback:(void (^)(NSString *aBalancerResponse))aCallback; + +@end diff --git a/RCTRealtimeMessaging/Balancer.m b/RCTRealtimeMessaging/Balancer.m new file mode 100644 index 0000000..e19c28c --- /dev/null +++ b/RCTRealtimeMessaging/Balancer.m @@ -0,0 +1,59 @@ +// +// Balancer.m +// OrtcClient +// +// Created by Marcin Kulwikowski on 15/07/14. +// +// + +#import "Balancer.h" + +NSString* const BALANCER_RESPONSE_PATTERN = @"^var SOCKET_SERVER = \\\"(.*?)\\\";$"; + +@implementation Balancer { + NSMutableData *receivedData; + void (^theCallabck)(NSString*); +} + + +- (id) initWithCluster:(NSString*) aCluster serverUrl:(NSString*)url isCluster:(BOOL)isCluster appKey:(NSString*) anAppKey callback:(void (^)(NSString *aBalancerResponse))aCallback{ + if ((self = [super init])) { + theCallabck = aCallback; + NSString* parsedUrl = aCluster; + + if(!isCluster){ + aCallback(url); + } else { + if(anAppKey != NULL){ + parsedUrl = [parsedUrl stringByAppendingString:@"?appkey="]; + parsedUrl = [parsedUrl stringByAppendingString:anAppKey]; + } + + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:parsedUrl]]; + //(void)[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; + + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue new] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { + if(data !=nil){ + NSString* myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + NSRegularExpression* resRegex = [NSRegularExpression regularExpressionWithPattern:BALANCER_RESPONSE_PATTERN options:0 error:NULL]; + NSTextCheckingResult* resMatch = [resRegex firstMatchInString:myString options:0 range:NSMakeRange(0, [myString length])]; + if (resMatch) + { + NSRange strRange = [resMatch rangeAtIndex:1]; + + if (strRange.location != NSNotFound) { + theCallabck([myString substringWithRange:strRange]); + return; + } + } + } + theCallabck(nil); + }]; + } + } + + return nil; +} + +@end diff --git a/RCTRealtimeMessaging/OrtcClient.h b/RCTRealtimeMessaging/OrtcClient.h new file mode 100644 index 0000000..0bd82ab --- /dev/null +++ b/RCTRealtimeMessaging/OrtcClient.h @@ -0,0 +1,376 @@ + +// +// OrtcClient.h +// OrtcClient +// +// Created by Rafael Cabral on 2/2/12. +// Copyright (c) 2012 IBT. All rights reserved. +// +#define NOTIFICATIONS_KEY @"Local_Storage_Notifications" + +#ifndef OrtcClient_OrtcClient_h +#define OrtcClient_OrtcClient_h + +#import +#import "RCTSRWebSocket.h" + +#define heartbeatDefaultTime 15 // Heartbeat default interval time +#define heartbeatDefaultFails 3 // Heartbeat default max fails + +#define heartbeatMaxTime 60 +#define heartbeatMinTime 10 + +#define heartbeatMaxFails 6 +#define heartbeatMinFails 1 + +@class OrtcClient; + +@protocol OrtcClientDelegate + +///--------------------------------------------------------------------------------------- +/// @name Instance Methods +///-------------------------------------------------------------------------------------- +/** + * Occurs when the client connects. + * + * @param ortc The ORTC object. + */ +- (void)onConnected:(OrtcClient*) ortc; +/** + * Occurs when the client disconnects. + * + * @param ortc The ORTC object. + */ +- (void)onDisconnected:(OrtcClient*) ortc; +/** + * Occurs when the client subscribes to a channel. + * + * @param ortc The ORTC object. + * @param channel The channel name. + */ +- (void)onSubscribed:(OrtcClient*) ortc channel:(NSString*) channel; +/** + * Occurs when the client unsubscribes from a channel. + * + * @param ortc The ORTC object. + * @param channel The channel name. + */ +- (void)onUnsubscribed:(OrtcClient*) ortc channel:(NSString*) channel; +/** + * Occurs when there is an exception. + * + * @param ortc The ORTC object. + * @param error The occurred exception. + */ +- (void)onException:(OrtcClient*) ortc error:(NSError*) error; +/** + * Occurs when the client attempts to reconnect. + * + * @param ortc The ORTC object. + */ +- (void)onReconnecting:(OrtcClient*) ortc; +/** + * Occurs when the client reconnects. + * + * @param ortc The ORTC object. + */ +- (void)onReconnected:(OrtcClient*) ortc; +/** + * Occurs when the client enables presence. + * + * @param error Description of error if occurs. + * @param result Result of enablePresence + */ +//- (void)onEnablePresence:(NSError*) error result:(NSString*) result; + + +@end + +/** + + OrtcClient Usage - Code Example + + - ViewController.h +

+ #import "OrtcClient.h"
+ @interface ViewController : UIViewController 
+ {
+ @private
+ 
+    OrtcClient* ortcClient;
+    void (^onMessage)(OrtcClient* ortc, NSString* channel, NSString* message);
+    // ...
+ }
+ // ...
+ @end
+ 
+ 
+ + + +- ViewController.m + +

+ #import "ViewController.h"
+ @implementation ViewController
+ 
+ - (void)viewDidLoad
+ {
+    [super viewDidLoad];
+    
+    // Instantiate OrtcClient
+    ortcClient = [OrtcClient ortcClientWithConfig:self];
+    
+    // Post permissions
+    @try {
+        NSMutableDictionary* myPermissions = [[NSMutableDictionary alloc] init];
+        
+        [myPermissions setObject:@"w" forKey:@"channel1"];
+        [myPermissions setObject:@"w" forKey:@"channel2"];
+        [myPermissions setObject:@"r" forKey:@"channelread"];
+        
+        BOOL postResult = [ortcClient saveAuthentication:@"http://ortc_server"
+                            isCLuster:YES authenticationToken:@"myAuthenticationToken"
+                            authenticationTokenIsPrivate:NO applicationKey:@"myApplicationKey"
+                            timeToLive:1800 privateKey:@"myPrivateKey" permissions:myPermissions];
+        
+        if (postResult) {
+            // Permissions correctly posted
+        }
+        else {
+            // Unable to post permissions
+        }
+    }
+    @catch (NSException* exception) {
+        // Exception posting permissions
+    }
+    
+    // Set connection properties
+    [ortcClient setConnectionMetadata:@"clientConnMeta"];
+    [ortcClient setClusterUrl:@"http://ortc_server"];
+    
+    // Connect
+    [ortcClient connect:@"myApplicationKey" authenticationToken:@"myAuthenticationToken"];
+ }
+ 
+ - (void) onConnected:(OrtcClient*) ortc
+ {
+    // Connected
+    onMessage = ^(OrtcClient* ortc, NSString* channel, NSString* message) {
+    // Received message 'message' at channel 'channel'
+        [ortcClient unsubscribe:channel];
+    };
+    
+    [ortcClient subscribe:@"channel1" subscribeOnReconnected:YES onMessage:onMessage];
+    [ortcClient subscribe:@"channel2" subscribeOnReconnected:NO onMessage:onMessage];
+    [ortcClient subscribeWithNotifications:@"channel3" subscribeOnReconnected:YES onMessage:onMessage];
+}
+
+ - (void) onDisconnected:(OrtcClient*) ortc
+ {
+    // Disconnected
+ }
+
+ - (void) onReconnecting:(OrtcClient*) ortc
+ {
+    // Trying to reconnect
+ }
+ 
+ - (void) onReconnected:(OrtcClient*) ortc
+ {
+    // Reconnected
+ }
+ 
+ - (void) onSubscribed:(OrtcClient*) ortc channel:(NSString*) channel
+ {
+    // Subscribed to the channel 'channel'
+    [ortcClient send:channel message:@"Message to the channel"];
+ }
+
+ - (void) onUnsubscribed:(OrtcClient*) ortc channel:(NSString*) channel
+ {
+    // Unsubscribed from the channel 'channel'
+    [ortcClient disconnect];
+ }
+
+ - (void) onException:(OrtcClient*) ortc error:(NSError*) error
+ {
+    // Exception occurred
+ }
+
+ @end
+ 
+*/ + + + +@interface OrtcClient : NSObject + +///--------------------------------------------------------------------------------------- +/// @name Properties +///--------------------------------------------------------------------------------------- +@property (nonatomic, retain) NSString* id; +@property (nonatomic, retain) NSString* url; +@property (nonatomic, retain) NSString* clusterUrl; +@property (nonatomic, retain) NSString* connectionMetadata; +@property (nonatomic, retain) NSString* announcementSubChannel; +@property (nonatomic, retain) NSString* sessionId; +@property (assign) int connectionTimeout; +@property (assign) BOOL isConnected; + + +///--------------------------------------------------------------------------------------- +/// @name Class Methods +///--------------------------------------------------------------------------------------- +/** + * Initializes a new instance of the ORTC class. + * + * @param delegate The object holding the ORTC callbacks, usually 'self'. + * + * @return New instance of the ORTC class. + */ + ++ (id)ortcClientWithConfig:(id) delegate; + + +///--------------------------------------------------------------------------------------- +/// @name Instance Methods +///--------------------------------------------------------------------------------------- +/** + * Connects with the application key and authentication token. + * + * @param applicationKey The application key. + * @param authenticationToken The authentication token. + */ +- (void)connect:(NSString*) applicationKey authenticationToken:(NSString*) authenticationToken; +/** + * Sends a message to a channel. + * + * @param channel The channel name. + * @param message The message to send. + */ +- (void)send:(NSString*) channel message:(NSString*) message; +/** + * Subscribes to a channel to receive messages sent to it. + * + * @param channel The channel name. + * @param subscribeOnReconnected Indicates whether the client should subscribe to the channel when reconnected (if it was previously subscribed when connected). + * @param onMessage The callback called when a message arrives at the channel. + */ +- (void)subscribe:(NSString*) channel subscribeOnReconnected:(BOOL) aSubscribeOnReconnected onMessage:(void (^)(OrtcClient* ortc, NSString* channel, NSString* message)) onMessage; + +/** + * Subscribes to a channel, with Push Notifications Service, to receive messages sent to it. + * + * @param channel The channel name. Only channels with alphanumeric name and the following characters: "_" "-" ":" are allowed. + * @param subscribeOnReconnected Indicates whether the client should subscribe to the channel when reconnected (if it was previously subscribed when connected). + * @param onMessage The callback called when a message or a Push Notification arrives at the channel. + */ +- (void)subscribeWithNotifications:(NSString*) channel subscribeOnReconnected:(BOOL) aSubscribeOnReconnected onMessage:(void (^)(OrtcClient* ortc, NSString* channel, NSString* message)) onMessage; + +/** + * Unsubscribes from a channel to stop receiving messages sent to it. + * + * @param channel The channel name. + */ +- (void)unsubscribe:(NSString*) channel; +/** + * Disconnects. + */ +- (void)disconnect; +/** + * Indicates whether is subscribed to a channel or not. + * + * @param channel The channel name. + * + * @return TRUE if subscribed to the channel or FALSE if not. + */ +- (NSNumber*)isSubscribed:(NSString*) channel; + +/** Saves the channels and its permissions for the authentication token in the ORTC server. + @warning This function will send your private key over the internet. Make sure to use secure connection. + @param url ORTC server URL. + @param isCluster Indicates whether the ORTC server is in a cluster. + @param authenticationToken The authentication token generated by an application server (for instance: a unique session ID). + @param authenticationTokenIsPrivate Indicates whether the authentication token is private (1) or not (0). + @param applicationKey The application key provided together with the ORTC service purchasing. + @param timeToLive The authentication token time to live (TTL), in other words, the allowed activity time (in seconds). + @param privateKey The private key provided together with the ORTC service purchasing. + @param permissions The channels and their permissions (w: write, r: read, p: presence, case sensitive). + @return TRUE if the authentication was successful or FALSE if it was not. + */ +- (BOOL)saveAuthentication:(NSString*) url isCLuster:(BOOL) isCluster authenticationToken:(NSString*) authenticationToken authenticationTokenIsPrivate:(BOOL) authenticationTokenIsPrivate applicationKey:(NSString*) applicationKey timeToLive:(int) timeToLive privateKey:(NSString*) privateKey permissions:(NSMutableDictionary*) permissions; + +/** Enables presence for the specified channel with first 100 unique metadata if true. + + @warning This function will send your private key over the internet. Make sure to use secure connection. + @param url Server containing the presence service. + @param isCluster Specifies if url is cluster. + @param applicationKey Application key with access to presence service. + @param privateKey The private key provided when the ORTC service is purchased. + @param channel Channel with presence data active. + @param metadata Defines if to collect first 100 unique metadata. + @param callback Callback with error (NSError) and result (NSString) parameters + */ +- (void)enablePresence:(NSString*) aUrl isCLuster:(BOOL) aIsCluster applicationKey:(NSString*) aApplicationKey privateKey:(NSString*) aPrivateKey channel:(NSString*) channel metadata:(BOOL) aMetadata callback:(void (^)(NSError* error, NSString* result)) aCallback; + +/** Disables presence for the specified channel. + + @warning This function will send your private key over the internet. Make sure to use secure connection. + @param url Server containing the presence service. + @param isCluster Specifies if url is cluster. + @param applicationKey Application key with access to presence service. + @param privateKey The private key provided when the ORTC service is purchased. + @param channel Channel with presence data active. + @param callback Callback with error (NSError) and result (NSString) parameters + */ +- (void)disablePresence:(NSString*) aUrl isCLuster:(BOOL) aIsCluster applicationKey:(NSString*) aApplicationKey privateKey:(NSString*) aPrivateKey channel:(NSString*) channel callback:(void (^)(NSError* error, NSString* result)) aCallback; + +/** + * Gets a NSDictionary indicating the subscriptions in the specified channel and if active the first 100 unique metadata. + * + * @param url Server containing the presence service. + * @param isCluster Specifies if url is cluster. + * @param applicationKey Application key with access to presence service. + * @param authenticationToken Authentication token with access to presence service. + * @param channel Channel with presence data active. + * @param callback Callback with error (NSError) and result (NSDictionary) parameters + */ +- (void)presence:(NSString*) aUrl isCLuster:(BOOL) aIsCluster applicationKey:(NSString*) aApplicationKey authenticationToken:(NSString*) aAuthenticationToken channel:(NSString*) channel callback:(void (^)(NSError* error, NSDictionary* result)) aCallback; + +/** + * Get heartbeat interval. + */ +- (int) getHeartbeatTime; +/** + * Set heartbeat interval. + */ +- (void) setHeartbeatTime:(int) newHeartbeatTime; +/** + * Get how many times can the client fail the heartbeat. + */ +- (int) getHeartbeatFails; +/** + * Set heartbeat fails. Defines how many times can the client fail the heartbeat. + */ +- (void) setHeartbeatFails:(int) newHeartbeatFails; +/** + * Indicates whether heartbeat is active or not. + */ +- (BOOL) isHeartbeatActive; +/** + * Enables the client heartbeat + */ +- (void) enableHeartbeat; +/** + * Disables the client heartbeat + */ +- (void) disableHeartbeat; + + ++ (void) setDEVICE_TOKEN:(NSString *) deviceToken; +@end + + +#endif + diff --git a/RCTRealtimeMessaging/OrtcClient.m b/RCTRealtimeMessaging/OrtcClient.m new file mode 100644 index 0000000..24025c1 --- /dev/null +++ b/RCTRealtimeMessaging/OrtcClient.m @@ -0,0 +1,1966 @@ +// +// OrtcClient.m +// OrtcClient +// +// Created by Rafael Cabral on 2/2/12. +// Copyright (c) 2012 IBT. All rights reserved. +// + +#import "OrtcClient.h" +#import "Balancer.h" + +// Class Extension (private methods) +@interface OrtcClient() +{ +@private + RCTSRWebSocket* _webSocket; + + id _ortcDelegate; + + NSMutableDictionary* _subscribedChannels; + NSMutableDictionary* _permissions; + NSMutableDictionary* messagesBuffer; + NSMutableDictionary* opCases; + NSMutableDictionary* errCases; + + NSString* applicationKey; + NSString* authenticationToken; + + BOOL isCluster; + BOOL isConnecting; + BOOL isReconnecting; + BOOL hasConnectedFirstTime; + BOOL stopReconnecting; + BOOL doFallback; + + NSDate* sessionCreatedAt; + int sessionExpirationTime; + + // Time in seconds + int heartbeatTime;// = heartbeatDefaultTime; // Heartbeat interval time + int heartbeatFails;// = heartbeatDefaultFails; // Heartbeat max fails + NSTimer *heartbeatTimer; + BOOL heartbeatActive; +} +- (id)initWithConfig:(id) aDelegate; + +- (void)opValidated:(NSString*) message; +- (void)opSubscribed:(NSString*) message; +- (void)opUnsubscribed:(NSString*) message; +- (void)opException:(NSString*) message; +- (void)opReceive:(NSString*) message; + +- (void)parseReceivedMessage:(NSString*) aMessage; +- (void)doConnect:(id) sender; +- (void)processConnect:(id) sender; +- (BOOL)isEmpty:(id) thing; +- (NSError*)generateError:(NSString*) errText; +- (NSString*)randomString:(u_int32_t) size; +- (NSString*)getClusterServer:(BOOL) isPostingAuth aPostUrl:(NSString*) postUrl; +- (u_int32_t)randomInRangeLo:(u_int32_t) loBound toHi:(u_int32_t) hiBound; +- (NSString*)generateId:(int) size; +- (BOOL)ortcIsValidInput:(NSString*) input; +- (BOOL)ortcIsValidUrl:(NSString*) input; +- (NSString*)readLocalStorage:(NSString*) sessionStorageName; +- (void)createLocalStorage:(NSString*) sessionStorageName; ++ (void) setDEVICE_TOKEN:(NSString *) deviceToken; ++ (NSString *) getDEVICE_TOKEN; + +- (void)delegateConnectedCallback:(OrtcClient*) ortc; +- (void)delegateDisconnectedCallback:(OrtcClient*) ortc; +- (void)delegateSubscribedCallback:(OrtcClient*) ortc channel:(NSString*) channel; +- (void)delegateUnsubscribedCallback:(OrtcClient*) ortc channel:(NSString*) channel; +- (void)delegateExceptionCallback:(OrtcClient*) ortc error:(NSError*) aError; +- (void)delegateReconnectingCallback:(OrtcClient*) ortc; +- (void)delegateReconnectedCallback:(OrtcClient*) ortc; + +@end + +@interface ChannelSubscription : NSObject +{ +} + +@property (assign) BOOL isSubscribing; +@property (assign) BOOL isSubscribed; +@property (assign) BOOL subscribeOnReconnected; +@property (assign) BOOL withNotifications; +@property (nonatomic, strong) void (^onMessage)(OrtcClient* ortc, NSString* channel, NSString* message); + +@end + +@interface PresenceRequest : NSObject { + NSMutableData *receivedData; + bool isResponseJSON; +} +@property (nonatomic,retain) NSMutableData *receivedData; +@property bool isResponseJSON; +@property (nonatomic, strong) void (^callback)(NSError* error, NSString* result); +@property (nonatomic, strong) void (^callbackDictionary)(NSError* error, NSDictionary* result); + +- (void)get: (NSMutableURLRequest *)request; +- (void)post: (NSMutableURLRequest *)request; + +@end + + +@implementation OrtcClient + +#pragma mark Attributes + +@synthesize id; +@synthesize url; +@synthesize clusterUrl; +@synthesize connectionTimeout; +@synthesize isConnected; +@synthesize connectionMetadata; +@synthesize announcementSubChannel; +@synthesize sessionId; + +#pragma mark Enumerators + +typedef enum { + opValidate, + opSubscribe, + opUnsubscribe, + opException +} opCodes; + +typedef enum { + errValidate, + errSubscribe, + errSubscribeMaxSize, + errUnsubscribeMaxSize, + errSendMaxSize +} errCodes; + +#pragma mark Regex patterns + +//NSString* const TESTER= @"^a\\[\"\\{\\\\\"op\\\\\"(.*?[^\"]+)\\\\\",(.*?)\\}\"\\]$"; +//NSString* const OPERATION_PATTERN = @"^a\\[\"\\{\\\"op\\\":\\\"(.*?[^\"]+)\\\",(.*?)\\}\"\\]$"; +NSString* const OPERATION_PATTERN = @"^a\\[\"\\{\\\\\"op\\\\\":\\\\\"(.*?[^\"]+)\\\\\",(.*?)\\}\"\\]$"; + +NSString* const VALIDATED_PATTERN = @"^(\\\\\"up\\\\\":){1}(.*?)(,\\\\\"set\\\\\":(.*?))?$"; +NSString* const CHANNEL_PATTERN = @"^\\\\\"ch\\\\\":\\\\\"(.*?)\\\\\"$"; +NSString* const EXCEPTION_PATTERN = @"^\\\\\"ex\\\\\":\\{(\\\\\"op\\\\\":\\\\\"(.*?[^\"]+)\\\\\",)?(\\\\\"ch\\\\\":\\\\\"(.*?)\\\\\",)?\\\\\"ex\\\\\":\\\\\"(.*?)\\\\\"\\}$"; +NSString* const RECEIVED_PATTERN = @"^a\\[\"\\{\\\\\"ch\\\\\":\\\\\"(.*?)\\\\\",\\\\\"m\\\\\":\\\\\"([\\s\\S]*?)\\\\\"\\}\"\\]$"; +NSString* const MULTI_PART_MESSAGE_PATTERN = @"^(.[^_]*?)_(.[^-]*?)-(.[^_]*?)_([\\s\\S]*?)$"; +NSString* const CLUSTER_RESPONSE_PATTERN = @"^var SOCKET_SERVER = \\\"(.*?)\\\";$"; +NSString* const DEVICE_TOKEN_PATTERN = @"[0-9A-Fa-f]{64}"; + +#pragma mark Maximum sizes + +int const MAX_MESSAGE_SIZE = 600; +int const MAX_CHANNEL_SIZE = 100; +int const MAX_CONNECTION_METADATA_SIZE = 256; +NSString* SESSION_STORAGE_NAME = @"ortcsession-"; + +#pragma mark Notifications constants + +NSString* const PLATFORM = @"Apns"; +#define WITH_NOTIFICATIONS YES +#define WITHOUT_NOTIFICATIONS NO + +#pragma mark Redefined properties + +- (void) setUrl:(NSString*) aUrl +{ + isCluster = NO; + url = [aUrl stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + +- (void) setClusterUrl:(NSString*) aClusterUrl +{ + isCluster = YES; + clusterUrl = [aClusterUrl stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + +#pragma mark Public methods + +- (void)connect:(NSString*) aApplicationKey authenticationToken:(NSString*) aAuthenticationToken +{ + /* + * Sanity Checks. + */ + if (isConnected) { + [self delegateExceptionCallback:self error:[self generateError:@"Already connected"]]; + } + else if (!url && !clusterUrl) { + [self delegateExceptionCallback:self error:[self generateError:@"URL and Cluster URL are null or empty"]]; + } + else if (!aApplicationKey) { + [self delegateExceptionCallback:self error:[self generateError:@"Application Key is null or empty"]]; + } + else if (!aAuthenticationToken) { + [self delegateExceptionCallback:self error:[self generateError:@"Authentication Token is null or empty"]]; + } + else if (!isCluster && ![self ortcIsValidUrl:url]) { + [self delegateExceptionCallback:self error:[self generateError:@"Invalid URL"]]; + } + else if (isCluster && ![self ortcIsValidUrl:clusterUrl]) { + [self delegateExceptionCallback:self error:[self generateError:@"Invalid Cluster URL"]]; + } + else if (![self ortcIsValidInput:aApplicationKey]) { + [self delegateExceptionCallback:self error:[self generateError:@"Application Key has invalid characters"]]; + } + else if (![self ortcIsValidInput:aAuthenticationToken]) { + [self delegateExceptionCallback:self error:[self generateError:@"Authentication Token has invalid characters"]]; + } + else if (announcementSubChannel && ![self ortcIsValidInput:announcementSubChannel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Announcement Subchannel has invalid characters"]]; + } + else if (![self isEmpty:connectionMetadata] && [connectionMetadata length] > MAX_CONNECTION_METADATA_SIZE) { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Connection metadata size exceeds the limit of %d characters", MAX_CONNECTION_METADATA_SIZE]]]; + } + else if (isConnecting) { + [self delegateExceptionCallback:self error:[self generateError:@"Already trying to connect"]]; + } + else { + applicationKey = aApplicationKey; + authenticationToken = aAuthenticationToken; + + isConnecting = YES; + isReconnecting = NO; + stopReconnecting = NO; + + [self doConnect:self]; + } +} + +- (void)send:(NSString*) channel message:(NSString*) aMessage +{ + /* + * Sanity Checks. + */ + if (!isConnected) { + [self delegateExceptionCallback:self error:[self generateError:@"Not connected"]]; + } + else if ([self isEmpty:channel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Channel is null or empty"]]; + } + else if (![self ortcIsValidInput:channel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Channel has invalid characters"]]; + } + else if ([self isEmpty:aMessage]) { + [self delegateExceptionCallback:self error:[self generateError:@"Message is null or empty"]]; + } + else { + + aMessage = [[aMessage stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"] stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]; + aMessage = [aMessage stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; + + NSData* channelBytes = [channel dataUsingEncoding:NSUTF8StringEncoding]; + + if (channelBytes.length >= MAX_CHANNEL_SIZE) { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Channel size exceeds the limit of %d characters", MAX_CHANNEL_SIZE]]]; + } + else { + unsigned long domainChannelIndex = (int)[channel rangeOfString:@":"].location; + NSString* channelToValidate = channel; + NSString* hashPerm = nil; + + + if (domainChannelIndex != NSNotFound) { + channelToValidate = [[channel substringToIndex:domainChannelIndex + 1] stringByAppendingString:@"*"]; + } + + if (_permissions) { + hashPerm = [_permissions objectForKey:channelToValidate] ? [_permissions objectForKey:channelToValidate] : [_permissions objectForKey:channel]; + } + + if (_permissions && !hashPerm) { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"No permission found to send to the channel '%@'", channel]]]; + } + else { + + NSData* messageBytes = [NSData dataWithBytes:[aMessage UTF8String] length:[aMessage lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; + + NSMutableArray* messageParts = [[NSMutableArray alloc] init]; + unsigned long pos = 0; + unsigned long remaining; + NSString* messageId = [self generateId:8]; + + // Multi part + while ((remaining = messageBytes.length - pos) > 0) { + unsigned long arraySize = 0; + + if (remaining >= MAX_MESSAGE_SIZE - channelBytes.length) { + arraySize = MAX_MESSAGE_SIZE - ((int)channelBytes.length); + } + else { + arraySize = remaining; + } + + Byte messagePart[arraySize]; + + [messageBytes getBytes:messagePart range:NSMakeRange(pos, arraySize)]; + + [messageParts addObject:[[NSString alloc] initWithBytes:messagePart length:arraySize encoding:NSUTF8StringEncoding]]; + + pos += arraySize; + } + + int counter = 1; + + for (NSString* __strong messageToSend in messageParts) { + NSString* encodedData = [[NSString alloc] initWithData:[NSData dataWithBytes:[messageToSend UTF8String] length:[messageToSend lengthOfBytesUsingEncoding:NSUTF8StringEncoding]] encoding:NSUTF8StringEncoding]; + + NSString* aString = [NSString stringWithFormat:@"\"send;%@;%@;%@;%@;%@\"", applicationKey, authenticationToken, channel, hashPerm, [[[[[[messageId stringByAppendingString:@"_"] stringByAppendingString:[NSString stringWithFormat:@"%d", counter]] stringByAppendingString:@"-"] stringByAppendingString:[NSString stringWithFormat:@"%d", ((int)messageParts.count)]] stringByAppendingString:@"_"] stringByAppendingString:encodedData]]; + + [_webSocket send:aString]; + + counter++; + } + } + } + } +} + +- (void)subscribe:(NSString*) channel subscribeOnReconnected:(BOOL) aSubscribeOnReconnected onMessage:(void (^)(OrtcClient* ortc, NSString* channel, NSString* message)) onMessage +{ + + [self subscribeChannel:channel WithNotifications:WITHOUT_NOTIFICATIONS subscribeOnReconnected:aSubscribeOnReconnected onMessage:onMessage]; +} + + +- (void)subscribeWithNotifications:(NSString*) channel subscribeOnReconnected:(BOOL) aSubscribeOnReconnected onMessage:(void (^)(OrtcClient* ortc, NSString* channel, NSString* message)) onMessage +{ + [self subscribeChannel:channel WithNotifications:WITH_NOTIFICATIONS subscribeOnReconnected:aSubscribeOnReconnected onMessage:onMessage]; +} + + + +- (void)subscribeChannel:(NSString*) channel WithNotifications:(BOOL) withNotifications subscribeOnReconnected:(BOOL) aSubscribeOnReconnected onMessage:(void (^)(OrtcClient* ortc, NSString* channel, NSString* message)) onMessage +{ + if ([self checkChannelSubscription:channel WithNotifications:withNotifications]) { + + NSString* hashPerm = [self checkChannelPermissions:channel]; + if (!_permissions || (_permissions && hashPerm != nil)) { + if (![_subscribedChannels objectForKey:channel]) { + // Instantiate ChannelSubscription + ChannelSubscription* channelSubscription = [[ChannelSubscription alloc] init]; + + // Set channelSubscription properties + channelSubscription.isSubscribing = YES; + channelSubscription.isSubscribed = NO; + channelSubscription.subscribeOnReconnected = aSubscribeOnReconnected; + channelSubscription.onMessage = [onMessage copy]; + channelSubscription.withNotifications = withNotifications; + // Add to subscribed channels dictionary + [_subscribedChannels setObject:channelSubscription forKey:channel]; + } + + NSString* aString = nil; + if (withNotifications) { + if (![self isEmpty:[OrtcClient getDEVICE_TOKEN]]) { + aString = [NSString stringWithFormat:@"\"subscribe;%@;%@;%@;%@;%@;%@\"", applicationKey, authenticationToken, channel, hashPerm, [OrtcClient getDEVICE_TOKEN], PLATFORM]; + } + else { + [self delegateExceptionCallback:self error:[self generateError:@"Failed to register Device Token. Channel subscribed without Push Notifications"]]; + aString = [NSString stringWithFormat:@"\"subscribe;%@;%@;%@;%@\"", applicationKey, authenticationToken, channel, hashPerm]; + } + } + else { + aString = [NSString stringWithFormat:@"\"subscribe;%@;%@;%@;%@\"", applicationKey, authenticationToken, channel, hashPerm]; + } + //NSLog(@"SUB ON ORTC:\n%@",aString); + if (![self isEmpty:aString]) { + [_webSocket send:aString]; + } + } + } +} + +- (void)unsubscribe:(NSString*) channel +{ + ChannelSubscription* channelSubscription = [_subscribedChannels objectForKey:channel]; + /* + * Sanity Checks. + */ + if (!isConnected) { + [self delegateExceptionCallback:self error:[self generateError:@"Not connected"]]; + } + else if ([self isEmpty:channel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Channel is null or empty"]]; + } + else if (![self ortcIsValidInput:channel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Channel has invalid characters"]]; + } + else if (!channelSubscription.isSubscribed) { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Not subscribed to the channel %@", channel]]]; + } + else { + NSData* channelBytes = [NSData dataWithBytes:[channel UTF8String] length:[channel lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; + + if (channelBytes.length >= MAX_CHANNEL_SIZE) { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Channel size exceeds the limit of %d characters", MAX_CHANNEL_SIZE]]]; + } + else { + NSString* aString = [[NSString alloc] init]; + if (channelSubscription.withNotifications) { + if (![self isEmpty:[OrtcClient getDEVICE_TOKEN]]) { + aString = [NSString stringWithFormat:@"\"unsubscribe;%@;%@;%@;%@\"", applicationKey, channel, [OrtcClient getDEVICE_TOKEN], PLATFORM]; + } + else { + aString = [NSString stringWithFormat:@"\"unsubscribe;%@;%@\"", applicationKey, channel]; + } + } + else { + aString = [NSString stringWithFormat:@"\"unsubscribe;%@;%@\"", applicationKey, channel]; + } + //NSLog(@"UNSUB ON ORTC:\n%@",aString); + if (![self isEmpty:aString]) { + [_webSocket send:aString]; + } + } + } +} + +- (BOOL) checkChannelSubscription:(NSString *) channel WithNotifications:(BOOL) withNotifications +{ + ChannelSubscription* channelSubscription = [_subscribedChannels objectForKey:channel]; + /* + * Sanity Checks. + */ + if (!isConnected) { + [self delegateExceptionCallback:self error:[self generateError:@"Not connected"]]; + return NO; + } + else if ([self isEmpty:channel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Channel is null or empty"]]; + return NO; + } + else if (withNotifications) { + if (![self ortcIsValidChannelForMobile:channel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Channel has invalid characters"]]; + return NO; + } + } + else if (![self ortcIsValidInput:channel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Channel has invalid characters"]]; + return NO; + } + else if (channelSubscription.isSubscribing) { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Already subscribing to the channel %@", channel]]]; + return NO; + } + else if (channelSubscription.isSubscribed) { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Already subscribed to the channel %@", channel]]]; + return NO; + } + else { + NSData* channelBytes = [NSData dataWithBytes:[channel UTF8String] length:[channel lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; + + if (channelBytes.length >= MAX_CHANNEL_SIZE) { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Channel size exceeds the limit of %d characters", MAX_CHANNEL_SIZE]]]; + return NO; + } + } + return YES; +} + + +- (NSString *) checkChannelPermissions:(NSString *) channel +{ + unsigned long domainChannelIndex = (int)[channel rangeOfString:@":"].location; + NSString* channelToValidate = channel; + NSString* hashPerm = nil; + + if (domainChannelIndex != NSNotFound) { + channelToValidate = [[channel substringToIndex:domainChannelIndex + 1] stringByAppendingString:@"*"]; + } + + if (_permissions) { + hashPerm = [_permissions objectForKey:channelToValidate] ? [_permissions objectForKey:channelToValidate] : [_permissions objectForKey:channel]; + return hashPerm; + } + if (_permissions && !hashPerm) { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"No permission found to subscribe to the channel '%@'", channel]]]; + return nil; + } + return hashPerm; +} + + +- (void)disconnect +{ + // Stop the connecting/reconnecting process + stopReconnecting = YES; + isConnecting = NO; + isReconnecting = NO; + hasConnectedFirstTime = NO; + + // Clear subscribed channels + [_subscribedChannels removeAllObjects]; + + /* + * Sanity Checks. + */ + if (!isConnected) { + [self delegateExceptionCallback:self error:[self generateError:@"Not connected"]]; + } + else { + [self processDisconnect:YES]; + } +} + +- (NSNumber*)isSubscribed:(NSString*) channel +{ + NSNumber* result = nil; + + /* + * Sanity Checks. + */ + if (!isConnected) { + [self delegateExceptionCallback:self error:[self generateError:@"Not connected"]]; + } + else if ([self isEmpty:channel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Channel is null or empty"]]; + } + else if (![self ortcIsValidInput:channel]) { + [self delegateExceptionCallback:self error:[self generateError:@"Channel has invalid characters"]]; + } + else { + result = [NSNumber numberWithBool:NO]; + + ChannelSubscription* channelSubscription = [_subscribedChannels objectForKey:channel]; + + result = channelSubscription.isSubscribed ? [NSNumber numberWithBool:YES] : [NSNumber numberWithBool:NO]; + } + + return result; +} + +- (BOOL)saveAuthentication:(NSString*) aUrl isCLuster:(BOOL) aIsCluster authenticationToken:(NSString*) aAuthenticationToken authenticationTokenIsPrivate:(BOOL) aAuthenticationTokenIsPrivate applicationKey:(NSString*) aApplicationKey timeToLive:(int) aTimeToLive privateKey:(NSString*) aPrivateKey permissions:(NSMutableDictionary*) aPermissions +{ + /* + * Sanity Checks. + */ + if ([self isEmpty:aUrl]) { + @throw [NSException exceptionWithName:@"Url" reason:@"URL is null or empty" userInfo:nil]; + } + else if ([self isEmpty:aAuthenticationToken]) { + @throw [NSException exceptionWithName:@"Authentication Token" reason:@"Authentication Token is null or empty" userInfo:nil]; + } + else if ([self isEmpty:aApplicationKey]) { + @throw [NSException exceptionWithName:@"Application Key" reason:@"Application Key is null or empty" userInfo:nil]; + } + else if ([self isEmpty:aPrivateKey]) { + @throw [NSException exceptionWithName:@"Private Key" reason:@"Private Key is null or empty" userInfo:nil]; + } + else { + BOOL ret = NO; + + NSString* connectionUrl = aUrl; + + if (aIsCluster) { + connectionUrl = [[self getClusterServer:YES aPostUrl:aUrl] copy]; + } + + if (connectionUrl) { + connectionUrl = [connectionUrl hasSuffix:@"/"] ? connectionUrl : [connectionUrl stringByAppendingString:@"/"]; + + NSString* post = [NSString stringWithFormat:@"AT=%@&PVT=%@&AK=%@&TTL=%d&PK=%@", aAuthenticationToken, aAuthenticationTokenIsPrivate ? @"1" : @"0", aApplicationKey, aTimeToLive, aPrivateKey]; + + if (aPermissions && aPermissions.count > 0) + { + post = [post stringByAppendingString:[NSString stringWithFormat:@"&TP=%lu", (unsigned long)aPermissions.count]]; + + NSArray* keys = [aPermissions allKeys]; // the dictionary keys + + for (NSString* key in keys) { + post = [post stringByAppendingString:[NSString stringWithFormat:@"&%@=%@", key, [aPermissions objectForKey:key]]]; + } + } + + NSData* postData = [post dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; + + NSString* postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[postData length]]; + + NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init]; + + [request setURL:[NSURL URLWithString:[connectionUrl stringByAppendingString:@"authenticate"]]]; + [request setHTTPMethod:@"POST"]; + [request setValue:postLength forHTTPHeaderField:@"Content-Length"]; + [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:postData]; + + // Send request and get response + NSHTTPURLResponse* urlResponse = nil; + NSError* error = nil; + + [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error]; + + ret = [urlResponse statusCode] == 201; + } + else { + @throw [NSException exceptionWithName:@"Get Cluster URL" reason:@"Unable to get URL from cluster" userInfo:nil]; + } + + return ret; + } +} + + +- (void)enablePresence:(NSString*) aUrl isCLuster:(BOOL) aIsCluster applicationKey:(NSString*) aApplicationKey privateKey:(NSString*) aPrivateKey channel:(NSString*) channel metadata:(BOOL) aMetadata callback:(void (^)(NSError* error, NSString* result)) aCallback +{ + /* + * Sanity Checks. + */ + if ([self isEmpty:aUrl]) { + @throw [NSException exceptionWithName:@"Url" reason:@"URL is null or empty" userInfo:nil]; + } + else if ([self isEmpty:aApplicationKey]) { + @throw [NSException exceptionWithName:@"Application Key" reason:@"Application Key is null or empty" userInfo:nil]; + } + else if ([self isEmpty:aPrivateKey]) { + @throw [NSException exceptionWithName:@"Private Key" reason:@"Private Key is null or empty" userInfo:nil]; + } + else if ([self isEmpty:channel]) { + @throw [NSException exceptionWithName:@"Channel" reason:@"Channel is null or empty" userInfo:nil]; + } + else if (![self ortcIsValidInput:channel]) { + @throw [NSException exceptionWithName:@"Channel" reason:@"Channel has invalid characters" userInfo:nil]; + } + else { + NSString* connectionUrl = aUrl; + if (aIsCluster) { + connectionUrl = [[self getClusterServer:YES aPostUrl:aUrl] copy]; + } + if (connectionUrl) { + connectionUrl = [connectionUrl hasSuffix:@"/"] ? connectionUrl : [connectionUrl stringByAppendingString:@"/"]; + NSString* path = [NSString stringWithFormat:@"presence/enable/%@/%@", aApplicationKey, channel]; + connectionUrl = [connectionUrl stringByAppendingString:path]; + NSString* content = [NSString stringWithFormat:@"privatekey=%@&metadata=%@", aPrivateKey, (aMetadata ? @"1" : @"0")]; + NSData* postData = [content dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; + NSString* postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[postData length]]; + NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init]; + + [request setURL:[NSURL URLWithString:connectionUrl]]; + [request setHTTPMethod:@"POST"]; + [request setValue:postLength forHTTPHeaderField:@"Content-Length"]; + [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:postData]; + + PresenceRequest *pr = [[PresenceRequest alloc] init]; + pr.callback = aCallback; + [pr post:request]; + } else { + NSError* error = [self generateError:@"Unable to get URL from cluster"]; + aCallback(error, nil); + } + } +} + +- (void)disablePresence:(NSString*) aUrl isCLuster:(BOOL) aIsCluster applicationKey:(NSString*) aApplicationKey privateKey:(NSString*) aPrivateKey channel:(NSString*) channel callback:(void (^)(NSError* error, NSString* result)) aCallback +{ + /* + * Sanity Checks. + */ + if ([self isEmpty:aUrl]) { + @throw [NSException exceptionWithName:@"Url" reason:@"URL is null or empty" userInfo:nil]; + } + else if ([self isEmpty:aApplicationKey]) { + @throw [NSException exceptionWithName:@"Application Key" reason:@"Application Key is null or empty" userInfo:nil]; + } + else if ([self isEmpty:aPrivateKey]) { + @throw [NSException exceptionWithName:@"Private Key" reason:@"Private Key is null or empty" userInfo:nil]; + } + else if ([self isEmpty:channel]) { + @throw [NSException exceptionWithName:@"Channel" reason:@"Channel is null or empty" userInfo:nil]; + } + else if (![self ortcIsValidInput:channel]) { + @throw [NSException exceptionWithName:@"Channel" reason:@"Channel has invalid characters" userInfo:nil]; + } + else { + NSString* connectionUrl = aUrl; + if (aIsCluster) { + connectionUrl = [[self getClusterServer:YES aPostUrl:aUrl] copy]; + } + if (connectionUrl) { + connectionUrl = [connectionUrl hasSuffix:@"/"] ? connectionUrl : [connectionUrl stringByAppendingString:@"/"]; + NSString* path = [NSString stringWithFormat:@"presence/disable/%@/%@", aApplicationKey, channel]; + connectionUrl = [connectionUrl stringByAppendingString:path]; + NSString* content = [NSString stringWithFormat:@"privatekey=%@", aPrivateKey]; + NSData* postData = [content dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; + NSString* postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[postData length]]; + NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init]; + + [request setURL:[NSURL URLWithString:connectionUrl]]; + [request setHTTPMethod:@"POST"]; + [request setValue:postLength forHTTPHeaderField:@"Content-Length"]; + [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:postData]; + + PresenceRequest *pr = [[PresenceRequest alloc] init]; + pr.callback = aCallback; + [pr post:request]; + } else { + NSError* error = [self generateError:@"Unable to get URL from cluster"]; + aCallback(error, nil); + } + } +} + +- (void)presence:(NSString*) aUrl isCLuster:(BOOL) aIsCluster applicationKey:(NSString*) aApplicationKey authenticationToken:(NSString*) aAuthenticationToken channel:(NSString*) channel callback:(void (^)(NSError* error, NSDictionary* result)) aCallback +{ + /* + * Sanity Checks. + */ + if ([self isEmpty:aUrl]) { + @throw [NSException exceptionWithName:@"Url" reason:@"URL is null or empty" userInfo:nil]; + } + else if ([self isEmpty:aApplicationKey]) { + @throw [NSException exceptionWithName:@"Application Key" reason:@"Application Key is null or empty" userInfo:nil]; + } + else if ([self isEmpty:aAuthenticationToken]) { + @throw [NSException exceptionWithName:@"Authentication Token" reason:@"Authentication Token is null or empty" userInfo:nil]; + } + else if ([self isEmpty:channel]) { + @throw [NSException exceptionWithName:@"Channel" reason:@"Channel is null or empty" userInfo:nil]; + } + else if (![self ortcIsValidInput:channel]) { + @throw [NSException exceptionWithName:@"Channel" reason:@"Channel has invalid characters" userInfo:nil]; + } + else { + NSString* connectionUrl = aUrl; + if (aIsCluster) { + connectionUrl = [[self getClusterServer:YES aPostUrl:aUrl] copy]; + } + if (connectionUrl) { + connectionUrl = [connectionUrl hasSuffix:@"/"] ? connectionUrl : [connectionUrl stringByAppendingString:@"/"]; + NSString* path = [NSString stringWithFormat:@"presence/%@/%@/%@", aApplicationKey, aAuthenticationToken, channel]; + connectionUrl = [connectionUrl stringByAppendingString:path]; + + NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init]; + + [request setURL:[NSURL URLWithString:connectionUrl]]; + [request setHTTPMethod:@"GET"]; + [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + + PresenceRequest *pr = [[PresenceRequest alloc] init]; + pr.callbackDictionary = aCallback; + [pr get:request]; + } else { + NSError* error = [self generateError:@"Unable to get URL from cluster"]; + aCallback(error, nil); + } + + } +} + + +- (int) getHeartbeatTime{ + return heartbeatTime; +} +- (void) setHeartbeatTime:(int) newHeartbeatTime { + if(newHeartbeatTime > heartbeatMaxTime || newHeartbeatTime < heartbeatMinTime){ + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Heartbeat time is out of limits (min: %d, max: %d)", heartbeatMinTime, heartbeatMaxTime]]]; + } else { + heartbeatTime = newHeartbeatTime; + } +} +- (int) getHeartbeatFails{ + return heartbeatFails; +} +- (void) setHeartbeatFails:(int) newHeartbeatFails { + if(newHeartbeatFails > heartbeatMaxFails || newHeartbeatFails < heartbeatMinFails){ + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Heartbeat fails is out of limits (min: %d, max: %d)", heartbeatMinFails, heartbeatMaxFails]]]; + } else { + heartbeatFails = newHeartbeatFails; + } +} +- (BOOL) isHeartbeatActive{ + return heartbeatActive; +} +- (void) enableHeartbeat{ + heartbeatActive = true; +} +- (void) disableHeartbeat{ + heartbeatActive = false; +} + + +static NSString *ortcDEVICE_TOKEN; ++ (void) setDEVICE_TOKEN:(NSString *) deviceToken { + ortcDEVICE_TOKEN = deviceToken; +} + +#pragma mark Private methods + +- (void) startHeartbeatLoop{ + if(heartbeatTimer == nil && heartbeatActive) + heartbeatTimer = [NSTimer scheduledTimerWithTimeInterval:heartbeatTime target:self selector:@selector(heartbeatLoop) userInfo:nil repeats:YES]; +} +- (void) stopHeartbeatLoop{ + if(heartbeatTimer != nil) + [heartbeatTimer invalidate]; + heartbeatTimer = nil; +} +- (void) heartbeatLoop{ + if(heartbeatActive){ + [_webSocket send:@"\"b\""]; + } else { + [self stopHeartbeatLoop]; + } +} + ++ (NSString *) getDEVICE_TOKEN { + return ortcDEVICE_TOKEN; +} + +- (BOOL)ortcIsValidInput:(NSString*) input +{ + NSRegularExpression* opRegex = [NSRegularExpression regularExpressionWithPattern:@"^[\\w-:/.]*$" options:0 error:NULL]; + NSTextCheckingResult* opMatch = [opRegex firstMatchInString:input options:0 range:NSMakeRange(0, [input length])]; + + return opMatch ? true : false; +} + + +- (BOOL)ortcIsValidChannelForMobile:(NSString*) input +{ + NSRegularExpression* opRegex = [NSRegularExpression regularExpressionWithPattern:@"^[\\w-:]*$" options:0 error:NULL]; + NSTextCheckingResult* opMatch = [opRegex firstMatchInString:input options:0 range:NSMakeRange(0, [input length])]; + + return opMatch ? true : false; +} + +- (BOOL)ortcIsValidUrl:(NSString*) input +{ + NSRegularExpression* opRegex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*(http|https)://(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(/|/([\\w#!:.?+=&%@!\\-/]))?\\s*$" options:0 error:NULL]; + NSTextCheckingResult* opMatch = [opRegex firstMatchInString:input options:0 range:NSMakeRange(0, [input length])]; + + return opMatch ? true : false; +} + +- (BOOL)isEmpty:(id) thing +{ + return thing == nil + || ([thing respondsToSelector:@selector(length)] + && [(NSData*)thing length] == 0) + || ([thing respondsToSelector:@selector(count)] + && [(NSArray*)thing count] == 0); +} + +- (NSError*)generateError:(NSString*) errText +{ + NSMutableDictionary* errorDetail = [NSMutableDictionary dictionary]; + [errorDetail setValue:errText forKey:NSLocalizedDescriptionKey]; + return [NSError errorWithDomain:@"OrtcClient" code:1 userInfo:errorDetail]; +} + +- (void)doConnect:(id) sender { + if(heartbeatTimer != nil) + [self stopHeartbeatLoop]; + + if (isReconnecting) { + [self delegateReconnectingCallback:self]; + } + + if (!stopReconnecting) { + [self processConnect:self]; + } +} + +- (void)parseReceivedMessage:(NSString*) aMessage +{ + if (aMessage) + { + if (![aMessage isEqualToString:@"o"] && ![aMessage isEqualToString:@"h"]) // Open and Heartbeat + { + //aMessage = [[aMessage stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""] stringByReplacingOccurrencesOfString:@"\\\\\\\\n" withString:@"\\n"]; + //NSRegularExpression* opTest = [NSRegularExpression regularExpressionWithPattern:TESTER options:0 error:NULL]; + //NSTextCheckingResult* opRES = [opTest firstMatchInString:aMessage options:0 range:NSMakeRange(0, [aMessage length])]; + + NSRegularExpression* opRegex = [NSRegularExpression regularExpressionWithPattern:OPERATION_PATTERN options:0 error:NULL]; + NSTextCheckingResult* opMatch = [opRegex firstMatchInString:aMessage options:0 range:NSMakeRange(0, [aMessage length])]; + + if (opMatch) + { + NSString* operation = nil; + NSString* arguments = nil; + + NSRange strRangeOp = [opMatch rangeAtIndex:1]; + NSRange strRangeArgs = [opMatch rangeAtIndex:2]; + + if (strRangeOp.location != NSNotFound) { + operation = [aMessage substringWithRange:strRangeOp]; + } + + if (strRangeArgs.location != NSNotFound) { + arguments = [aMessage substringWithRange:strRangeArgs]; + } + + if (operation) { + if ([opCases objectForKey:operation]) { + switch ([[opCases objectForKey:operation] intValue]) { + case opValidate: + if (arguments) { + [self opValidated:arguments]; + } + break; + case opSubscribe: + if (arguments) { + [self opSubscribed:arguments]; + } + break; + case opUnsubscribe: + if (arguments) { + [self opUnsubscribed:arguments]; + } + break; + case opException: + if (arguments) { + [self opException:arguments]; + } + break; + default: + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Unknown message received: %@", aMessage]]]; + break; + } + } + } + else { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Unknown message received: %@", aMessage]]]; + } + } + else { + [self opReceive:aMessage]; + } + } + } +} + +- (void)opValidated:(NSString*) message { + BOOL isValid = NO; + + NSRegularExpression* valRegex = [NSRegularExpression regularExpressionWithPattern:VALIDATED_PATTERN options:0 error:NULL]; + NSTextCheckingResult* valMatch = [valRegex firstMatchInString:message options:0 range:NSMakeRange(0, [message length])]; + + if (valMatch) + { + isValid = YES; + NSString* userPermissions = nil; + + NSRange strRangePerm = [valMatch rangeAtIndex:2]; + NSRange strRangeExpi = [valMatch rangeAtIndex:4]; + + if (strRangePerm.location != NSNotFound) { + userPermissions = [message substringWithRange:strRangePerm]; + } + + if (strRangeExpi.location != NSNotFound) { + sessionExpirationTime = [[message substringWithRange:strRangeExpi] intValue]; + } + + if ([self isEmpty:[self readLocalStorage:[SESSION_STORAGE_NAME stringByAppendingString:applicationKey]]]) { + [self createLocalStorage:[SESSION_STORAGE_NAME stringByAppendingString:applicationKey]]; + } + + // NOTE: userPermissions = null -> No authentication required for the application key + if (userPermissions && ![userPermissions isEqualToString:@"null"]) { + userPermissions = [userPermissions stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""]; + + // Parse the string into JSON + NSError* err = nil; + NSDictionary* dictionary = [NSJSONSerialization JSONObjectWithData:[userPermissions dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&err]; + + if (err) { + [self delegateExceptionCallback:self error:[self generateError:@"Error parsing the permissions received from server"]]; + } + else { + _permissions = [[NSMutableDictionary alloc] init]; + + for (NSString* key in [dictionary allKeys]) { + // Add to permissions dictionary + [_permissions setValue:[dictionary objectForKey:key] forKey:key]; + } + } + } + } + + if (isValid) { + isConnecting = NO; + isReconnecting = NO; + isConnected = YES; + + if (hasConnectedFirstTime) { + NSMutableArray* channelsToRemove = [[NSMutableArray alloc] init]; + + // Subscribe to the previously subscribed channels + for (NSString* channel in _subscribedChannels) { + ChannelSubscription* channelSubscription = [_subscribedChannels objectForKey:channel]; + + // Subscribe again + if (channelSubscription.subscribeOnReconnected && (channelSubscription.isSubscribing || channelSubscription.isSubscribed)) { + channelSubscription.isSubscribing = YES; + channelSubscription.isSubscribed = NO; + + unsigned long domainChannelIndex = [channel rangeOfString:@":"].location; + NSString* channelToValidate = channel; + NSString* hashPerm = nil; + + if (domainChannelIndex != NSNotFound) { + channelToValidate = [[channel substringToIndex:domainChannelIndex + 1] stringByAppendingString:@"*"]; + } + + if (_permissions) { + hashPerm = [_permissions objectForKey:channelToValidate] ? [_permissions objectForKey:channelToValidate] : [_permissions objectForKey:channel]; + } + + NSString* aString = [[NSString alloc] init]; + aString = nil; + if (channelSubscription.withNotifications) { + if (![self isEmpty:[OrtcClient getDEVICE_TOKEN]]) { + aString = [NSString stringWithFormat:@"\"subscribe;%@;%@;%@;%@;%@;%@\"", applicationKey, authenticationToken, channel, hashPerm, [OrtcClient getDEVICE_TOKEN], PLATFORM]; + } + else { + [self delegateExceptionCallback:self error:[self generateError:@"Failed to register Device Token. Channel subscribed without Push Notifications"]]; + aString = [NSString stringWithFormat:@"\"subscribe;%@;%@;%@;%@\"", applicationKey, authenticationToken, channel, hashPerm]; + } + } + else { + aString = [NSString stringWithFormat:@"\"subscribe;%@;%@;%@;%@\"", applicationKey, authenticationToken, channel, hashPerm]; + } + //NSLog(@"SUB ON ORTC:\n%@",aString); + if (![self isEmpty:aString]) { + [_webSocket send:aString]; + } + } + else { + [channelsToRemove addObject:channel]; + } + } + + for (NSString* channel in channelsToRemove) { + [_subscribedChannels removeObjectForKey:channel]; + } + // Clean messages buffer (can have lost message parts in memory) + [messagesBuffer removeAllObjects]; + [OrtcClient removeReceivedNotifications]; + [self delegateReconnectedCallback:self]; + } + else { + hasConnectedFirstTime = YES; + + [self delegateConnectedCallback:self]; + } + [self startHeartbeatLoop]; + } + else { + [self disconnect]; + [self delegateExceptionCallback:self error:[self generateError:@"Invalid connection"]]; + } +} + +- (void)opSubscribed:(NSString*) message { + NSRegularExpression* subRegex = [NSRegularExpression regularExpressionWithPattern:CHANNEL_PATTERN options:0 error:NULL]; + NSTextCheckingResult* subMatch = [subRegex firstMatchInString:message options:0 range:NSMakeRange(0, [message length])]; + + if (subMatch) + { + NSString* channel = nil; + NSRange strRangeChn = [subMatch rangeAtIndex:1]; + + if (strRangeChn.location != NSNotFound) { + channel = [message substringWithRange:strRangeChn]; + } + + if (channel) { + ChannelSubscription* channelSubscription = [_subscribedChannels objectForKey:channel]; + + channelSubscription.isSubscribing = NO; + channelSubscription.isSubscribed = YES; + + [self delegateSubscribedCallback:self channel:channel]; + } + } +} + +- (void)opUnsubscribed:(NSString*) message { + NSRegularExpression* unsubRegex = [NSRegularExpression regularExpressionWithPattern:CHANNEL_PATTERN options:0 error:NULL]; + NSTextCheckingResult* unsubMatch = [unsubRegex firstMatchInString:message options:0 range:NSMakeRange(0, [message length])]; + + if (unsubMatch) + { + NSString* channel = nil; + NSRange strRangeChn = [unsubMatch rangeAtIndex:1]; + + if (strRangeChn.location != NSNotFound) { + channel = [message substringWithRange:strRangeChn]; + } + + if (channel) { + [_subscribedChannels removeObjectForKey:channel]; + + [self delegateUnsubscribedCallback:self channel:channel]; + } + } +} + +- (void)opException:(NSString*) message { + NSRegularExpression* exRegex = [NSRegularExpression regularExpressionWithPattern:EXCEPTION_PATTERN options:0 error:NULL]; + NSTextCheckingResult* exMatch = [exRegex firstMatchInString:message options:0 range:NSMakeRange(0, [message length])]; + + if (exMatch) + { + NSString* operation = nil; + NSString* channel = nil; + NSString* error = nil; + + NSRange strRangeOp = [exMatch rangeAtIndex:2]; + NSRange strRangeChn = [exMatch rangeAtIndex:4]; + NSRange strRangeErr = [exMatch rangeAtIndex:5]; + + if (strRangeOp.location != NSNotFound) { + operation = [message substringWithRange:strRangeOp]; + } + + if (strRangeChn.location != NSNotFound) { + channel = [message substringWithRange:strRangeChn]; + } + + if (strRangeErr.location != NSNotFound) { + error = [message substringWithRange:strRangeErr]; + } + + if (error) { + if ([error isEqualToString:@"Invalid connection."]) { + [self disconnect]; + } + [self delegateExceptionCallback:self error:[self generateError:error]]; + } + + if (operation) { + if ([errCases objectForKey:operation]) { + switch ([[errCases objectForKey:operation] intValue]) { + case errValidate: + isConnecting = NO; + isReconnecting = NO; + + // Stop the connecting/reconnecting process + stopReconnecting = YES; + hasConnectedFirstTime = NO; + + [self processDisconnect:NO]; + break; + case errSubscribe: + if (channel && [_subscribedChannels objectForKey:channel]) { + ChannelSubscription* channelSubscription = [_subscribedChannels objectForKey:channel]; + + channelSubscription.isSubscribing = NO; + } + break; + case errSubscribeMaxSize: + case errUnsubscribeMaxSize: + case errSendMaxSize: + if (channel && [_subscribedChannels objectForKey:channel]) { + ChannelSubscription* channelSubscription = [_subscribedChannels objectForKey:channel]; + + channelSubscription.isSubscribing = NO; + } + + // Stop the connecting/reconnecting process + stopReconnecting = YES; + hasConnectedFirstTime = NO; + + [self disconnect]; + break; + default: + break; + } + } + } + } +} + +- (void)opReceive:(NSString*) message { + NSRegularExpression* recRegex = [NSRegularExpression regularExpressionWithPattern:RECEIVED_PATTERN options:0 error:NULL]; + NSTextCheckingResult* recMatch = [recRegex firstMatchInString:message options:0 range:NSMakeRange(0, [message length])]; + + if (recMatch) + { + NSString* aChannel = nil; + NSString* aMessage = nil; + + NSRange strRangeChn = [recMatch rangeAtIndex:1]; + NSRange strRangeMsg = [recMatch rangeAtIndex:2]; + + if (strRangeChn.location != NSNotFound) { + aChannel = [message substringWithRange:strRangeChn]; + } + + if (strRangeMsg.location != NSNotFound) { + aMessage = [message substringWithRange:strRangeMsg]; + } + + if (aChannel && aMessage) { + //aMessage = [[[aMessage stringByReplacingOccurrencesOfString:@"\\\\n" withString:@"\n"] stringByReplacingOccurrencesOfString:@"\\\\\"" withString:@"\""] stringByReplacingOccurrencesOfString:@"\\\\\\\\" withString:@"\\"]; + + // Multi part + NSRegularExpression* msgRegex = [NSRegularExpression regularExpressionWithPattern:MULTI_PART_MESSAGE_PATTERN options:0 error:NULL]; + NSTextCheckingResult* multiMatch = [msgRegex firstMatchInString:aMessage options:0 range:NSMakeRange(0, [aMessage length])]; + + NSString* messageId = nil; + int messageCurrentPart = 1; + int messageTotalPart = 1; + BOOL lastPart = NO; + + if (multiMatch) + { + NSRange strRangeMsgId = [multiMatch rangeAtIndex:1]; + NSRange strRangeMsgCurPart = [multiMatch rangeAtIndex:2]; + NSRange strRangeMsgTotPart = [multiMatch rangeAtIndex:3]; + NSRange strRangeMsgRec = [multiMatch rangeAtIndex:4]; + + if (strRangeMsgId.location != NSNotFound) { + messageId = [aMessage substringWithRange:strRangeMsgId]; + } + + if (strRangeMsgCurPart.location != NSNotFound) { + messageCurrentPart = [[aMessage substringWithRange:strRangeMsgCurPart] intValue]; + } + + if (strRangeMsgTotPart.location != NSNotFound) { + messageTotalPart = [[aMessage substringWithRange:strRangeMsgTotPart] intValue]; + } + + if (strRangeMsgRec.location != NSNotFound) { + aMessage = [aMessage substringWithRange:strRangeMsgRec]; + //code below written by Rafa, gives a bug for a meesage containing % character + //aMessage = [[aMessage substringWithRange:strRangeMsgRec] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + } + } + // Is a message part + if (![self isEmpty:messageId]) { + if (![messagesBuffer objectForKey:messageId]) { + + NSMutableDictionary *msgSentDict = [[NSMutableDictionary alloc] init]; + [msgSentDict setObject:[NSNumber numberWithBool:NO] forKey:@"isMsgSent"]; + [messagesBuffer setObject:msgSentDict forKey:messageId]; + } + + NSMutableDictionary* messageBufferId = [messagesBuffer objectForKey:messageId]; + [messageBufferId setObject:aMessage forKey:[NSString stringWithFormat:@"%d", messageCurrentPart]]; + + // Last message part -1 isMsgSent Key + if (([[messageBufferId allKeys] count] -1) == messageTotalPart) { + lastPart = YES; + } + } + // Message does not have multipart, like the messages received at announcement channels + else { + lastPart = YES; + } + + if (lastPart) { + if (![self isEmpty:messageId]) { + aMessage = @""; + NSMutableDictionary* messageBufferId = [messagesBuffer objectForKey:messageId]; + + for (int i = 1; i <= messageTotalPart; i++) { + NSString* messagePart = [messageBufferId objectForKey:[NSString stringWithFormat:@"%d", i]]; + + aMessage = [aMessage stringByAppendingString:messagePart]; + // Delete from messages buffer + [messageBufferId removeObjectForKey:[NSString stringWithFormat:@"%d", i]]; + } + } + + if ([messagesBuffer objectForKey:messageId] && [[[messagesBuffer objectForKey:messageId] objectForKey:@"isMsgSent"] boolValue]) { + [messagesBuffer removeObjectForKey:messageId]; + } + else if ([_subscribedChannels objectForKey:aChannel]) { + ChannelSubscription* channelSubscription = [_subscribedChannels objectForKey:aChannel]; + + if (![self isEmpty:messageId]) { + NSMutableDictionary *msgSentDict = [messagesBuffer objectForKey:messageId]; + [msgSentDict setObject:[NSNumber numberWithBool:YES] forKey:@"isMsgSent"]; + [messagesBuffer setObject:msgSentDict forKey:messageId]; + } + aMessage = [self escapeRecvChars:aMessage]; + channelSubscription.onMessage(self, aChannel, aMessage); + } + } + } + } +} + +- (NSString*)escapeRecvChars:(NSString*) str{ + str = [self simulateJsonParse:str]; + str = [self simulateJsonParse:str]; + return str; +} +- (NSString*)simulateJsonParse:(NSString*) str{ + NSMutableString *ms = [NSMutableString string]; + for(int i =0; i < [str length]; i++){ + unichar ascii = [str characterAtIndex:i]; + if(ascii > 128){ //unicode + [ms appendFormat:@"%@", [NSString stringWithCharacters:&ascii length:1]]; + } else { //ascii + if(ascii == '\\'){ + i = i + 1; + int next = [str characterAtIndex:i]; + if(next == '\\'){ + [ms appendString:@"\\"]; + } else if(next == 'n'){ + [ms appendString:@"\n"]; + } else if(next == '"'){ + [ms appendString:@"\""]; + } else if(next == 'b'){ + [ms appendString:@"\b"]; + } else if(next == 'f'){ + [ms appendString:@"\f"]; + } else if(next == 'r'){ + [ms appendString:@"\r"]; + } else if(next == 't'){ + [ms appendString:@"\t"]; + } + } else { + [ms appendFormat:@"%c", ascii]; + } + } + } + return ms; +} + +- (NSString*)generateId:(int) size +{ + CFUUIDRef uuidRef = CFUUIDCreate(NULL); + CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef); + CFRelease(uuidRef); + + NSString *uuid = [NSString stringWithString:(__bridge NSString *) uuidStringRef]; + CFRelease(uuidStringRef); + + return [[[uuid stringByReplacingOccurrencesOfString:@"-" withString:@""] substringToIndex:size] lowercaseString]; +} + +- (NSString*)randomString:(u_int32_t) size +{ + NSString* ret = @""; + + for (int i = 0; i < size; i++) { + // A-Z + NSString* letter = [NSString stringWithFormat:@"%0.1u", [self randomInRangeLo:65 toHi:90]]; + + ret = [NSString stringWithFormat:@"%@%c", ret, (char)[letter intValue]]; + } + + return ret; +} + +- (u_int32_t)randomInRangeLo:(u_int32_t) loBound toHi:(u_int32_t) hiBound +{ + u_int32_t random; + int32_t range = hiBound - loBound + 1; + + u_int32_t limit = UINT32_MAX - (UINT32_MAX % range); + + do { + random = arc4random(); + } while (random > limit); + + return loBound + (random % range); +} + +- (void)processDisconnect:(BOOL) callDisconnectedCallback +{ + [self stopHeartbeatLoop]; + + _webSocket.delegate = nil; + [_webSocket close]; + + if (callDisconnectedCallback) { + [self delegateDisconnectedCallback:self]; + } + + isConnected = NO; + isConnecting = NO; + + // Clear user permissions + _permissions = nil; +} + +- (void)processConnect:(id) sender +{ + if (!stopReconnecting) { + /* + if (isCluster) { + url = [[self getClusterServer:NO aPostUrl:clusterUrl] copy]; + isCluster = YES; + }*/ + + (void)[[Balancer alloc] initWithCluster:clusterUrl serverUrl:url isCluster:isCluster appKey:applicationKey callback: ^(NSString* balancerResponse){ + url = balancerResponse; + if(isCluster){ + if ([self isEmpty:balancerResponse]){ + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Unable to get URL from cluster (%@)", clusterUrl]]]; + } + } + if (url) { + NSString* wsScheme = @"ws"; + NSDictionary* tlsSettings = nil; + NSURL* connectionUrl = [NSURL URLWithString:url]; + + if ([connectionUrl.scheme isEqualToString:@"https"]) { + wsScheme = @"wss"; + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + tlsSettings = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates, [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot, [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, kCFNull, kCFStreamSSLPeerName, kCFStreamSocketSecurityLevelSSLv3, kCFStreamSSLLevel, nil]; + } + + NSString* serverId = [NSString stringWithFormat:@"%0.3u", [self randomInRangeLo:1 toHi:1000]]; + NSString* connId = [self randomString:8]; + NSString* connUrl = connectionUrl.host; + + if (![self isEmpty:connectionUrl.port]) { + connUrl = [[connUrl stringByAppendingString:@":"] stringByAppendingString:[connectionUrl.port stringValue]]; + } + + NSString* wsUrl = [NSString stringWithFormat:@"%@://%@/broadcast/%@/%@/websocket", wsScheme, connUrl, serverId, connId]; + + NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:wsUrl] cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0]; + _webSocket = [[RCTSRWebSocket alloc] initWithURLRequest:req]; + + //_webSocket = [[RCTSRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:wsUrl]]]; + _webSocket.delegate = self; + + [_webSocket open]; + + } + else { + [NSTimer scheduledTimerWithTimeInterval:connectionTimeout target:self selector:@selector(processConnect:) userInfo:nil repeats:NO]; + + } + + }]; + } +} + +- (NSDictionary*)readLocalStorage:(NSString*) sessionStorageName +{ + NSString *errorDesc = nil; + NSPropertyListFormat format; + NSString *plistPath; + NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + + plistPath = [rootPath stringByAppendingPathComponent:@"OrtcClient.plist"]; + + if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) { + plistPath = [[NSBundle mainBundle] pathForResource:@"OrtcClient" ofType:@"plist"]; + } + + //NSLog(@"plistPath: %@", plistPath); + + NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath]; + NSDictionary *plistProps = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc]; + + if (plistProps) { + //[self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Error reading plist: %@, format: %d", errorDesc, format]]]; + + if ([plistProps objectForKey:@"sessionCreatedAt"]) { + sessionCreatedAt = [plistProps objectForKey:@"sessionCreatedAt"]; + } + + NSDate* currentDateTime = [NSDate date]; + NSTimeInterval time = [currentDateTime timeIntervalSinceDate:sessionCreatedAt]; + int minutes = time / 60; + + if (minutes >= sessionExpirationTime) { + plistProps = nil; + } + else if ([plistProps objectForKey:@"sessionId"]) { + sessionId = [plistProps objectForKey:@"sessionId"]; + } + } + + return plistProps; +} + +- (void)createLocalStorage:(NSString*) sessionStorageName +{ + sessionCreatedAt = [NSDate date]; + + NSArray *keys = [NSArray arrayWithObjects:@"sessionId", @"sessionCreatedAt", nil]; + NSArray *objects = [NSArray arrayWithObjects:sessionId, sessionCreatedAt, nil]; + NSDictionary* sessionInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys]; + + NSString *error; + NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString *plistPath = [rootPath stringByAppendingPathComponent:@"OrtcClient.plist"]; + NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:sessionInfo format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; + + if (plistData) { + [plistData writeToFile:plistPath atomically:YES]; + } + else { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Error : %@", error]]]; + } +} + +- (NSString*)getClusterServer:(BOOL) isPostingAuth aPostUrl:(NSString *) postUrl +{ + NSString* result = nil; + NSString* parsedUrl = postUrl; + + if(applicationKey != NULL) + { + parsedUrl = [parsedUrl stringByAppendingString:@"?appkey="]; + parsedUrl = [parsedUrl stringByAppendingString:applicationKey]; + } + + // Initiate connection + NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:parsedUrl]]; + + // Send request and get response + NSData* response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; + + NSString* myString = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding]; + + NSRegularExpression* resRegex = [NSRegularExpression regularExpressionWithPattern:CLUSTER_RESPONSE_PATTERN options:0 error:NULL]; + NSTextCheckingResult* resMatch = [resRegex firstMatchInString:myString options:0 range:NSMakeRange(0, [myString length])]; + + if (resMatch) + { + NSRange strRange = [resMatch rangeAtIndex:1]; + + if (strRange.location != NSNotFound) { + result = [myString substringWithRange:strRange]; + } + } + + if (!isPostingAuth) + { + if ([self isEmpty:result]) + { + [self delegateExceptionCallback:self error:[self generateError:[NSString stringWithFormat:@"Unable to get URL from cluster (%@)", parsedUrl]]]; + } + } + + return result; +} + +#pragma mark - RCTSRWebSocketDelegate + +- (void)webSocket:(RCTSRWebSocket*) webSocket didReceiveMessage:(id) aMessage +{ + [self parseReceivedMessage:aMessage]; +} + +- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket +{ + if ([self isEmpty:[self readLocalStorage:[SESSION_STORAGE_NAME stringByAppendingString:applicationKey]]]) { + sessionId = [self generateId:16]; + } + //Heartbeat details + NSString *hbDetails = @""; + if(heartbeatActive){ + hbDetails = [NSString stringWithFormat:@";%d;%d;", heartbeatTime, heartbeatFails]; + } + // Send validate + NSString* aString = [NSString stringWithFormat:@"\"validate;%@;%@;%@;%@;%@%@\"", applicationKey, authenticationToken, announcementSubChannel ? announcementSubChannel : @"", sessionId ? sessionId : @"", connectionMetadata ? connectionMetadata : @"", hbDetails]; + + [_webSocket send:aString]; +} + +- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error +{ + isConnecting = NO; + + // Reconnect + if (!stopReconnecting) { + isConnecting = YES; + stopReconnecting = NO; + + if (!isReconnecting) { + isReconnecting = YES; + if(isCluster){ + NSURL *tUrl = [NSURL URLWithString:clusterUrl]; + if ([tUrl.scheme isEqualToString:@"http"] && doFallback) { + NSString *t = [clusterUrl stringByReplacingOccurrencesOfString:@"http:" withString:@"https:"]; + NSRange r = [t rangeOfString:@"/server/ssl/"]; + if(r.location == NSNotFound){ + clusterUrl = [t stringByReplacingOccurrencesOfString:@"/server/" withString:@"/server/ssl/"]; + } else { + clusterUrl = t; + } + } + } + [self doConnect:self]; + } + else { + + [NSTimer scheduledTimerWithTimeInterval:connectionTimeout target:self selector:@selector(doConnect:) userInfo:nil repeats:NO]; + } + } +} + +- (void)webSocket:(RCTSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean +{ + [self processDisconnect:YES]; + + // Reconnect + if (!stopReconnecting) { + isConnecting = YES; + stopReconnecting = NO; + + if (!isReconnecting) { + isReconnecting = YES; + + [self doConnect:self]; + } + else { + [NSTimer scheduledTimerWithTimeInterval:connectionTimeout target:self selector:@selector(doConnect:) userInfo:nil repeats:NO]; + } + } +} + +#pragma mark Lifecycle + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self stopHeartbeatLoop]; +} + ++ (id)ortcClientWithConfig:(id) aDelegate +{ + return [[[self class] alloc] initWithConfig:aDelegate]; +} + +- (id)initWithConfig:(id) aDelegate +{ + self = [super init]; + + if (self) { + if (opCases == nil) { + opCases = [[NSMutableDictionary alloc] initWithCapacity:4]; + + [opCases setObject:[NSNumber numberWithInt:opValidate] forKey:@"ortc-validated"]; + [opCases setObject:[NSNumber numberWithInt:opSubscribe] forKey:@"ortc-subscribed"]; + [opCases setObject:[NSNumber numberWithInt:opUnsubscribe] forKey:@"ortc-unsubscribed"]; + [opCases setObject:[NSNumber numberWithInt:opException] forKey:@"ortc-error"]; + } + + if (errCases == nil) { + errCases = [[NSMutableDictionary alloc] initWithCapacity:5]; + + [errCases setObject:[NSNumber numberWithInt:errValidate] forKey:@"validate"]; + [errCases setObject:[NSNumber numberWithInt:errSubscribe] forKey:@"subscribe"]; + [errCases setObject:[NSNumber numberWithInt:errSubscribeMaxSize] forKey:@"subscribe_maxsize"]; + [errCases setObject:[NSNumber numberWithInt:errUnsubscribeMaxSize] forKey:@"unsubscribe_maxsize"]; + [errCases setObject:[NSNumber numberWithInt:errSendMaxSize] forKey:@"send_maxsize"]; + } + + //apply properties + _ortcDelegate = aDelegate; + + connectionTimeout = 5; // seconds + sessionExpirationTime = 30; // minutes + + isConnected = NO; + isConnecting = NO; + isReconnecting = NO; + hasConnectedFirstTime = NO; + doFallback = YES; + + _permissions = nil; + + _subscribedChannels = [[NSMutableDictionary alloc] init]; + messagesBuffer = [[NSMutableDictionary alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedNotification:) name:@"ApnsNotification" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedNotification:) name:@"ApnsRegisterError" object:nil]; + + heartbeatTime = heartbeatDefaultTime; // Heartbeat interval time + heartbeatFails = heartbeatDefaultFails; // Heartbeat max fails + heartbeatTimer = nil; + heartbeatActive = false; + } + return self; +} + + + +- (void) receivedNotification:(NSNotification *) notification +{ + // [notification name] should be @"ApnsNotification" for received Apns Notififications + if ([[notification name] isEqualToString:@"ApnsNotification"]) { + NSDictionary *notificaionInfo = [[NSDictionary alloc] initWithDictionary:[notification userInfo]]; + if ([[notificaionInfo objectForKey:@"A"] isEqualToString:applicationKey]) { + + NSString *ortcMessage = [NSString stringWithFormat:@"a[\"{\\\"ch\\\":\\\"%@\\\",\\\"m\\\":\\\"%@\\\"}\"]", [notificaionInfo objectForKey:@"C"], [notificaionInfo objectForKey:@"M"]]; + [self parseReceivedMessage:ortcMessage]; + } + } + + // [notification name] should be @"ApnsRegisterError" if an error ocured on RegisterForRemoteNotifications + if ([[notification name] isEqualToString:@"ApnsRegisterError"]) { + [self delegateExceptionCallback:self error:[[notification userInfo] objectForKey:@"ApnsRegisterError"]]; + } +} + + +- (void) parseReceivedNotifications { + + NSMutableDictionary *notificationsDict = [[NSMutableDictionary alloc] initWithDictionary:[[NSUserDefaults standardUserDefaults] objectForKey:NOTIFICATIONS_KEY]]; + NSMutableArray *receivedMessages = [[NSMutableArray alloc] initWithArray:[notificationsDict objectForKey:applicationKey]]; + //NSMutableArray *messages = [[NSMutableArray alloc] initWithArray:receivedMessages]; + + for (NSString *message in receivedMessages) { + [self parseReceivedMessage:message]; + //[receivedMessages removeObject:message]; + } + [receivedMessages removeAllObjects]; + + [notificationsDict setObject:receivedMessages forKey:applicationKey]; + [[NSUserDefaults standardUserDefaults] setObject:notificationsDict forKey:NOTIFICATIONS_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + ++ (void) removeReceivedNotifications { + + NSMutableDictionary *notificationsDict = [[NSMutableDictionary alloc] initWithDictionary:[[NSUserDefaults standardUserDefaults] objectForKey:NOTIFICATIONS_KEY]]; + [notificationsDict removeAllObjects]; + + [[NSUserDefaults standardUserDefaults] setObject:notificationsDict forKey:NOTIFICATIONS_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + + +#pragma mark Callbacks + +/* + * Calls the onConnected callback if defined. + */ +- (void)delegateConnectedCallback:(OrtcClient*) ortc { + if (_ortcDelegate != nil && [_ortcDelegate respondsToSelector: @selector(onConnected:)]) + { + [_ortcDelegate performSelector: @selector(onConnected:) withObject:ortc]; + [self parseReceivedNotifications]; + } +} + +/* + * Calls the onDisconnected callback if defined. + */ +- (void)delegateDisconnectedCallback:(OrtcClient*) ortc { + if (_ortcDelegate != nil && [_ortcDelegate respondsToSelector: @selector(onDisconnected:)]) + { + [_ortcDelegate performSelector: @selector(onDisconnected:) withObject:ortc]; + } +} + +/* + * Calls the onSubscribed callback if defined. + */ +- (void)delegateSubscribedCallback:(OrtcClient*) ortc channel:(NSString*) channel { + if (_ortcDelegate != nil && [_ortcDelegate respondsToSelector: @selector(onSubscribed:channel:)]) + { + [_ortcDelegate performSelector: @selector(onSubscribed:channel:) withObject:ortc withObject:channel]; + } +} + +/* + * Calls the onUnsubscribed callback if defined. + */ +- (void)delegateUnsubscribedCallback:(OrtcClient*) ortc channel:(NSString*) channel { + if (_ortcDelegate != nil && [_ortcDelegate respondsToSelector: @selector(onUnsubscribed:channel:)]) + { + [_ortcDelegate performSelector: @selector(onUnsubscribed:channel:) withObject:ortc withObject:channel]; + } +} + +/* + * Calls the onException callback if defined. + */ +- (void)delegateExceptionCallback:(OrtcClient*) ortc error:(NSError*) aError { + if (_ortcDelegate != nil && [_ortcDelegate respondsToSelector: @selector(onException:error:)]) + { + [_ortcDelegate performSelector: @selector(onException:error:) withObject:ortc withObject:aError]; + } +} + +/* + * Calls the onReconnecting callback if defined. + */ +- (void)delegateReconnectingCallback:(OrtcClient*) ortc { + if (_ortcDelegate != nil && [_ortcDelegate respondsToSelector: @selector(onReconnecting:)]) + { + [_ortcDelegate performSelector: @selector(onReconnecting:) withObject: ortc]; + } +} + +/* + * Calls the onReconnected callback if defined. + */ +- (void)delegateReconnectedCallback:(OrtcClient*) ortc { + if (_ortcDelegate != nil && [_ortcDelegate respondsToSelector: @selector(onReconnected:)]) + { + [_ortcDelegate performSelector: @selector(onReconnected:) withObject: ortc]; + [self parseReceivedNotifications]; + } +} + + +@end + +@implementation ChannelSubscription + +@synthesize isSubscribing; +@synthesize isSubscribed; +@synthesize subscribeOnReconnected; +@synthesize withNotifications; +@synthesize onMessage; + +- (id)init { + if (self=[super init]) { + //do something here + } + + return self; +} + +@end + + +@implementation PresenceRequest + +@synthesize receivedData; +@synthesize isResponseJSON; + +- init { + if ((self = [super init])) { + + } + return self; +} + +- (void)get: (NSMutableURLRequest *)request { + self.isResponseJSON = true; + + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue new] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { + + NSString *dataStr=[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if(self.isResponseJSON){ + NSError* err = nil; + NSDictionary* dictionary = nil; + dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err]; + + if (err) { + if([dataStr caseInsensitiveCompare:@"null"] != NSOrderedSame ) { + NSMutableDictionary* errorDetail = [NSMutableDictionary dictionary]; + [errorDetail setValue:dataStr forKey:NSLocalizedDescriptionKey]; + NSError* error = [NSError errorWithDomain:@"OrtcClient" code:1 userInfo:errorDetail]; + self.callbackDictionary(error, nil); + } else { + self.callbackDictionary(nil, (NSDictionary*)@"null"); + } + } else { + self.callbackDictionary(nil, dictionary); + } + } else { + self.callback(nil, dataStr); + } + }]; + +// NSURLConnection* ret = [[NSURLConnection alloc] initWithRequest:request delegate:self]; +// if (ret == nil){ +// NSMutableDictionary* errorDetail = [NSMutableDictionary dictionary]; +// [errorDetail setValue:@"The connection can't be initialized." forKey:NSLocalizedDescriptionKey]; +// NSError* error = [NSError errorWithDomain:@"OrtcClient" code:1 userInfo:errorDetail]; +// if(self.isResponseJSON) +// self.callbackDictionary(error, nil); +// else +// self.callback(error, nil); +// } +} + +- (void)post: (NSMutableURLRequest *)request{ + self.isResponseJSON = false; + + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue new] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { + + NSString *dataStr=[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if(self.isResponseJSON){ + NSError* err = nil; + NSDictionary* dictionary = nil; + dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err]; + + if (err) { + if([dataStr caseInsensitiveCompare:@"null"] != NSOrderedSame ) { + NSMutableDictionary* errorDetail = [NSMutableDictionary dictionary]; + [errorDetail setValue:dataStr forKey:NSLocalizedDescriptionKey]; + NSError* error = [NSError errorWithDomain:@"OrtcClient" code:1 userInfo:errorDetail]; + self.callbackDictionary(error, nil); + } else { + self.callbackDictionary(nil, (NSDictionary*)@"null"); + } + } else { + self.callbackDictionary(nil, dictionary); + } + } else { + self.callback(nil, dataStr); + } + }]; + +// NSURLConnection* ret = [[NSURLConnection alloc] initWithRequest:request delegate:self]; +// if (ret == nil){ +// NSMutableDictionary* errorDetail = [NSMutableDictionary dictionary]; +// [errorDetail setValue:@"The connection can't be initialized." forKey:NSLocalizedDescriptionKey]; +// NSError* error = [NSError errorWithDomain:@"OrtcClient" code:1 userInfo:errorDetail]; +// if(self.isResponseJSON) +// self.callbackDictionary(error, nil); +// else +// self.callback(error, nil); +// } +} + +#pragma mark NSURLConnection delegate methods +- (NSURLRequest *)connection:(NSURLConnection *)connection + willSendRequest:(NSURLRequest *)request + redirectResponse:(NSURLResponse *)redirectResponse { + return request; +} + + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + [self.receivedData setLength:0]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + [self.receivedData appendData:data]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + if(self.isResponseJSON) + self.callbackDictionary(error, nil); + else + self.callback(error, nil); +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + NSString *dataStr=[[NSString alloc] initWithData:self.receivedData encoding:NSUTF8StringEncoding]; + if(self.isResponseJSON){ + NSError* err = nil; + NSDictionary* dictionary = nil; + dictionary = [NSJSONSerialization JSONObjectWithData:[dataStr dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&err]; + + if (err) { + if([dataStr caseInsensitiveCompare:@"null"] != NSOrderedSame ) { + NSMutableDictionary* errorDetail = [NSMutableDictionary dictionary]; + [errorDetail setValue:dataStr forKey:NSLocalizedDescriptionKey]; + NSError* error = [NSError errorWithDomain:@"OrtcClient" code:1 userInfo:errorDetail]; + self.callbackDictionary(error, nil); + } else { + self.callbackDictionary(nil, (NSDictionary*)@"null"); + } + } else { + self.callbackDictionary(nil, dictionary); + } + } else { + self.callback(nil, dataStr); + } +} + +@end + diff --git a/RCTRealtimeMessaging/RCTRealtimeMessaging.h b/RCTRealtimeMessaging/RCTRealtimeMessaging.h new file mode 100644 index 0000000..f78af68 --- /dev/null +++ b/RCTRealtimeMessaging/RCTRealtimeMessaging.h @@ -0,0 +1,20 @@ +// +// RCTRealtimeMessaging.h +// RCTRealtimeMessaging +// +// Created by Joao Caixinha on 02/04/15. +// Copyright (c) 2015 Realtime. All rights reserved. +// + +#import +#import "OrtcClient.h" +#import "RCTBridgeModule.h" +#import "RCTBridge.h" +#import "RCTEventDispatcher.h" + +@interface RCTRealtimeMessaging : NSObject +@property(retain, nonatomic)OrtcClient *ortcClient; +@property(retain, nonatomic)NSMutableDictionary *queue; +@property(retain, nonatomic)NSDictionary *pushInfo; + +@end diff --git a/RCTRealtimeMessaging/RCTRealtimeMessaging.m b/RCTRealtimeMessaging/RCTRealtimeMessaging.m new file mode 100644 index 0000000..1e6810e --- /dev/null +++ b/RCTRealtimeMessaging/RCTRealtimeMessaging.m @@ -0,0 +1,450 @@ +// +// RCTRealtimeMessaging.m +// RCTRealtimeMessaging +// +// Created by Joao Caixinha on 02/04/15. +// Copyright (c) 2015 Realtime. All rights reserved. +// + +#import "RCTRealtimeMessaging.h" +#import + +@implementation RCTRealtimeMessaging +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + + +- (id)init{ + self = [super init]; + if (self) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedNotification:) name:@"Notification" object:nil]; + } + return self; +} + +- (void)dealloc{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void) receivedNotification:(NSNotification *) notification{ + NSDictionary *userInfo = [notification userInfo]; + NSMutableDictionary *pushInfo = [[NSMutableDictionary alloc] init]; + [self handleCustom:pushInfo from:userInfo]; + _pushInfo = [pushInfo objectForKey:@"payload"]; + [self.bridge.eventDispatcher sendDeviceEventWithName:@"onPushNotification" + body:[pushInfo objectForKey:@"payload"]]; +} + +RCT_EXPORT_METHOD(checkForNotifications){ + if(_pushInfo){ + [self.bridge.eventDispatcher sendDeviceEventWithName:@"onPushNotification" + body:_pushInfo]; + _pushInfo = nil; + } +} + + +- (void)handleStd:(NSMutableDictionary*)pushInfo from:(NSDictionary*)userInfo +{ + NSString* msg = [userInfo objectForKey:@"M"]; + int num = 0; + NSUInteger len = [msg length]; + unichar buffer[len+1]; + [msg getCharacters: buffer range: NSMakeRange(0, len)]; + + NSString *finalM; + for (int i=0; i i + 1) { + finalM = [msg substringFromIndex:i+1]; + } + } + } + + NSError *error = nil; + NSData *jsonData = [finalM dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; + + if (json != nil) { + [pushInfo setObject:json forKey:@"payload"]; + }else + { + [pushInfo setObject:finalM forKey:@"payload"]; + } +} + + +- (void)handleCustom:(NSMutableDictionary*)pushInfo from:(NSDictionary*)userInfo +{ + NSMutableDictionary *payload = [[NSMutableDictionary alloc] init]; + for (NSString* key in [[userInfo objectForKey:@"aps"] allKeys]) { + if (![key isEqualToString:@"sound"] && ![key isEqualToString:@"badge"] && ![key isEqualToString:@"alert"]) { + [payload setObject:[[userInfo objectForKey:@"aps"] objectForKey:key] forKey:key]; + } + } + [pushInfo setObject:payload forKey:@"payload"]; +} + + + + +RCT_EXPORT_METHOD(connect:(id)connectionSettings id:(NSString*)pId){ + + NSString *appKey = [RCTConvert NSString:connectionSettings[@"appKey"]]; + NSString *clientConnMeta = [RCTConvert NSString:connectionSettings[@"connectionMetadata"]]; + NSString *url = [RCTConvert NSString:connectionSettings[@"url"]]; + NSString *clusterUrl = [RCTConvert NSString:connectionSettings[@"clusterUrl"]]; + NSString *token = [RCTConvert NSString:connectionSettings[@"token"]]; + + if (!_queue) { + _queue = [[NSMutableDictionary alloc] init]; + } + + OrtcClient *ortcClient = [_queue objectForKey:pId]; + + if (!ortcClient) { + ortcClient = [OrtcClient ortcClientWithConfig:self]; + [_queue setObject:ortcClient forKey:pId]; + } + + // Set connection properties + [ortcClient setConnectionMetadata:clientConnMeta]; + + if (url) { + [ortcClient setUrl:url]; + }else if (clusterUrl) + { + [ortcClient setClusterUrl:clusterUrl]; + } + + // Connect + [ortcClient connect:appKey authenticationToken:token]; + +} + +RCT_EXPORT_METHOD(sendMessage:(NSString*)message toChannel:(NSString*)channel usingClient:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient send:channel message:message]; +} + +/** + * Occurs when the client connects. + * + * @param ortc The ORTC object. + */ +- (void) onConnected:(OrtcClient*) ortc +{ + NSString *clientID = [[_queue allKeysForObject:ortc] objectAtIndex:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onConnected", clientID] + body:@{}]; +} + +RCT_EXPORT_METHOD(subscribe:(NSString*)channel subscribeOnReconnected:(BOOL)aSubscribeOnReconnected usingClient:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient subscribe:channel subscribeOnReconnected:aSubscribeOnReconnected onMessage:^(OrtcClient *ortc, NSString *channel, NSString *message) + { + + NSString *clientID = [[_queue allKeysForObject:ortc] objectAtIndex:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onMessage", clientID] + body:@{@"message": message, + @"channel": channel + }]; + }]; +} + +RCT_EXPORT_METHOD(subscribeWithNotifications:(NSString*) channel subscribeOnReconnected:(BOOL) aSubscribeOnReconnected usingClient:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient subscribeWithNotifications:channel subscribeOnReconnected:aSubscribeOnReconnected onMessage:^(OrtcClient *ortc, NSString *channel, NSString *message) { + + NSString *clientID = [[_queue allKeysForObject:ortc] objectAtIndex:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onMessage", clientID] + body:@{@"message": message, + @"channel": channel, + }]; + }]; +} + + +/** Enables presence for the specified channel with first 100 unique metadata if true. + + @warning This function will send your private key over the internet. Make sure to use secure connection. + @param url Server containing the presence service. + @param isCluster Specifies if url is cluster. + @param applicationKey Application key with access to presence service. + @param privateKey The private key provided when the ORTC service is purchased. + @param channel Channel with presence data active. + @param metadata Defines if to collect first 100 unique metadata. + @param callback Callback with error (NSError) and result (NSString) parameters + */ +RCT_EXPORT_METHOD(enablePresence:(NSString*) aUrl isCLuster:(BOOL) aIsCluster applicationKey:(NSString*) aApplicationKey privateKey:(NSString*) aPrivateKey channel:(NSString*) channel metadata:(BOOL) aMetadata usingClient:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient enablePresence:aUrl isCLuster:aIsCluster applicationKey:aApplicationKey privateKey:aPrivateKey channel:channel metadata:aMetadata callback:^(NSError *error, NSString *result) { + if (error) { + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onEnablePresence", clientID] + body:@{@"error": error.localizedDescription, + }]; + }else{ + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onEnablePresence", clientID] + body:@{@"result": result, + }]; + } + }]; +} + +/** Disables presence for the specified channel. + + @warning This function will send your private key over the internet. Make sure to use secure connection. + @param url Server containing the presence service. + @param isCluster Specifies if url is cluster. + @param applicationKey Application key with access to presence service. + @param privateKey The private key provided when the ORTC service is purchased. + @param channel Channel with presence data active. + @param callback Callback with error (NSError) and result (NSString) parameters + */ +RCT_EXPORT_METHOD(disablePresence:(NSString*) aUrl isCLuster:(BOOL) aIsCluster applicationKey:(NSString*) aApplicationKey privateKey:(NSString*) aPrivateKey channel:(NSString*)channel usingClient:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient disablePresence:aUrl isCLuster:aIsCluster applicationKey:aApplicationKey privateKey:aPrivateKey channel:channel callback:^(NSError *error, NSString *result) { + if (error) { + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onDisablePresence", clientID] + body:@{@"error": error.localizedDescription, + }]; + }else{ + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onDisablePresence", clientID] + body:@{@"result": result, + }]; + } + }]; +} + +/** + * Gets a NSDictionary indicating the subscriptions in the specified channel and if active the first 100 unique metadata. + * + * @param url Server containing the presence service. + * @param isCluster Specifies if url is cluster. + * @param applicationKey Application key with access to presence service. + * @param authenticationToken Authentication token with access to presence service. + * @param channel Channel with presence data active. + * @param callback Callback with error (NSError) and result (NSDictionary) parameters + */ +RCT_EXPORT_METHOD(presence:(NSString*) aUrl isCLuster:(BOOL) aIsCluster applicationKey:(NSString*) aApplicationKey authenticationToken:(NSString*) aAuthenticationToken channel:(NSString*) channel usingClient:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient presence:aUrl isCLuster:aIsCluster applicationKey:aApplicationKey authenticationToken:aAuthenticationToken channel:channel callback:^(NSError *error, NSDictionary *result) { + + if (error) { + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onPresence", clientID] + body:@{@"error": error.localizedDescription, + }]; + }else{ + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onPresence", clientID] + body:@{@"result": result, + }]; + } + }]; +} + + +/** + * Occurs when the client disconnects. + * + * @param ortc The ORTC object. + */ +- (void) onDisconnected:(OrtcClient*) ortc +{ + NSString *clientID = [[_queue allKeysForObject:ortc] objectAtIndex:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onDisconnected", clientID] + body:@{}]; +} +/** + * Occurs when the client subscribes to a channel. + * + * @param ortc The ORTC object. + * @param channel The channel name. + */ +- (void)onSubscribed:(OrtcClient*) ortc channel:(NSString*) channel +{ + NSString *clientID = [[_queue allKeysForObject:ortc] objectAtIndex:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onSubscribed", clientID] + body:@{@"channel":channel, + }]; +} +/** + * Occurs when the client unsubscribes from a channel. + * + * @param ortc The ORTC object. + * @param channel The channel name. + */ +- (void)onUnsubscribed:(OrtcClient*) ortc channel:(NSString*) channel +{ + NSString *clientID = [[_queue allKeysForObject:ortc] objectAtIndex:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onUnSubscribed", clientID] + body:@{@"channel":channel, + }]; +} + +/** + * Occurs when there is an exception. + * + * @param ortc The ORTC object. + * @param error The occurred exception. + */ +- (void)onException:(OrtcClient*) ortc error:(NSError*) error +{ + NSString *clientID = [[_queue allKeysForObject:ortc] objectAtIndex:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onException", clientID] + body:@{@"error":error.localizedDescription, + }]; +} + +/** + * Occurs when the client attempts to reconnect. + * + * @param ortc The ORTC object. + */ +- (void)onReconnecting:(OrtcClient*) ortc +{ + NSString *clientID = [[_queue allKeysForObject:ortc] objectAtIndex:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onReconnecting", clientID] + body:@{}]; +} +/** + * Occurs when the client reconnects. + * + * @param ortc The ORTC object. + */ +- (void)onReconnected:(OrtcClient*) ortc +{ + NSString *clientID = [[_queue allKeysForObject:ortc] objectAtIndex:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:[NSString stringWithFormat:@"%@-onReconnected", clientID] + body:@{}]; +} + + +/** + * Unsubscribes from a channel to stop receiving messages sent to it. + * + * @param channel The channel name. + */ +RCT_EXPORT_METHOD(unsubscribe:(NSString*) channel usingClient:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient unsubscribe:channel]; +} +/** + * Disconnects. + */ +RCT_EXPORT_METHOD(disconnect:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient disconnect]; +} +/** + * Indicates whether is subscribed to a channel or not. + * + * @param channel The channel name. + * + * @return TRUE if subscribed to the channel or FALSE if not. + */ +RCT_EXPORT_METHOD(isSubscribed:(NSString*) channel usingClient:(NSString*)clientID callback:(RCTResponseSenderBlock)callback) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + NSNumber* result = [ortcClient isSubscribed:channel]; + callback(@[result]); +} + +/** Saves the channels and its permissions for the authentication token in the ORTC server. + @warning This function will send your private key over the internet. Make sure to use secure connection. + @param url ORTC server URL. + @param isCluster Indicates whether the ORTC server is in a cluster. + @param authenticationToken The authentication token generated by an application server (for instance: a unique session ID). + @param authenticationTokenIsPrivate Indicates whether the authentication token is private (1) or not (0). + @param applicationKey The application key provided together with the ORTC service purchasing. + @param timeToLive The authentication token time to live (TTL), in other words, the allowed activity time (in seconds). + @param privateKey The private key provided together with the ORTC service purchasing. + @param permissions The channels and their permissions (w: write, r: read, p: presence, case sensitive). + @return TRUE if the authentication was successful or FALSE if it was not. + */ +RCT_EXPORT_METHOD(saveAuthentication:(NSString*) url isCLuster:(BOOL) isCluster authenticationToken:(NSString*) authenticationToken authenticationTokenIsPrivate:(BOOL) authenticationTokenIsPrivate applicationKey:(NSString*) applicationKey timeToLive:(int) timeToLive privateKey:(NSString*) privateKey permissions:(id) permissions usingClient:(NSString*)clientID callback:(RCTResponseSenderBlock)callback) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + BOOL result = [ortcClient saveAuthentication:url isCLuster:isCluster authenticationToken:authenticationToken authenticationTokenIsPrivate:authenticationTokenIsPrivate applicationKey:applicationKey timeToLive:timeToLive privateKey:privateKey permissions:permissions]; + callback(@[[NSNumber numberWithBool:result]]); +} + + +/** + * Get heartbeat interval. + */ +RCT_EXPORT_METHOD(getHeartbeatTime:(NSString*)clientID callback:(RCTResponseSenderBlock)callback) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + int result = ortcClient.getHeartbeatTime; + callback(@[[NSNumber numberWithInt:result]]); +} +/** + * Set heartbeat interval. + */ +RCT_EXPORT_METHOD(setHeartbeatTime:(int)newHeartbeatTime usingClient:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient setHeartbeatTime:newHeartbeatTime]; +} +/** + * Get how many times can the client fail the heartbeat. + */ +RCT_EXPORT_METHOD(getHeartbeatFails:(NSString*)clientID callback:(RCTResponseSenderBlock)callback) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + int result = [ortcClient getHeartbeatFails]; + callback(@[[NSNumber numberWithInt:result]]); +} +/** + * Set heartbeat fails. Defines how many times can the client fail the heartbeat. + */ +RCT_EXPORT_METHOD(setHeartbeatFails:(int) newHeartbeatFails usingClient:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient setHeartbeatFails:newHeartbeatFails]; +} +/** + * Indicates whether heartbeat is active or not. + */ +RCT_EXPORT_METHOD(isHeartbeatActive:(NSString*)clientID callback:(RCTResponseSenderBlock)callback) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + BOOL result = [ortcClient isHeartbeatActive]; + callback(@[[NSNumber numberWithInt:result]]); +} +/** + * Enables the client heartbeat + */ +RCT_EXPORT_METHOD(enableHeartbeat:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient enableHeartbeat]; +} +/** + * Disables the client heartbeat + */ +RCT_EXPORT_METHOD(disableHeartbeat:(NSString*)clientID) +{ + OrtcClient *ortcClient = [_queue objectForKey:clientID]; + [ortcClient disableHeartbeat]; +} + + +RCT_EXPORT_METHOD(setDEVICE_TOKEN:(NSString *) deviceToken) +{ + [OrtcClient setDEVICE_TOKEN:deviceToken]; +} + + + +@end diff --git a/RCTRealtimeMessagingIOS.js b/RCTRealtimeMessagingIOS.js new file mode 100644 index 0000000..26a0dc0 --- /dev/null +++ b/RCTRealtimeMessagingIOS.js @@ -0,0 +1,147 @@ +// Created by Joao Caixinha on 02/04/15. +// Copyright (c) 2015 Realtime. All rights reserved. +// + +/** + * @providesModule RCTRealtimeMessagingIOS + * @flow + */ + +'use strict'; + +var React = require('react-native'); +var NativeModules = React.NativeModules; +var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +var ortcClient = NativeModules.RealtimeMessaging; +var RTEvents = {}; +var instances = 0; + +class RCTRealtimeMessagingIOS extends React.Component { + id: String; + + constructor(props) { + super(props); + this.id = instances++; + } + + RTConnect(config){ + ortcClient.connect(config, this.id); + } + + + RTDisconnect(){ + ortcClient.disconnect(this.id); + } + + RTSubscribe(channel, subscribeOnReconnect: boolean){ + ortcClient.subscribe(channel, subscribeOnReconnect, this.id); + } + + RTSubscribeWithNotifications(channel, subscribeOnReconnect: boolean){ + ortcClient.subscribeWithNotifications(channel, subscribeOnReconnect, this.id); + } + + RTUnsubscribe(channel){ + ortcClient.unsubscribe(channel, this.id); + } + + RTSendMessage(message, channel){ + ortcClient.sendMessage(message, channel, this.id); + } + + RTEnablePresence(aUrl, aIsCluster:boolean, aApplicationKey, aPrivateKey, channel, aMetadata){ + ortcClient.enablePresence(aUrl, aIsCluster, aApplicationKey, aPrivateKey, channel, aMetadata, this.id); + } + + RTDisablePresence(aUrl, aIsCluster:boolean, aApplicationKey, aPrivateKey, channel, aMetadata){ + ortcClient.disablePresence(aUrl, aIsCluster, aApplicationKey, aPrivateKey, channel, this.id); + } + + RTPresence(aUrl, aIsCluster:boolean, aApplicationKey, aAuthenticationToken, channel){ + ortcClient.presence(aUrl, aIsCluster, aApplicationKey, aAuthenticationToken, channel, this.id); + } + + RTIsSubscribed(channel, callBack: Function){ + ortcClient.isSubscribed(channel, this.id, callBack); + } + + RTSaveAuthentication(url, isCluster, authenticationToken, authenticationTokenIsPrivate, applicationKey, timeToLive, privateKey, permissions, callBack: Function){ + ortcClient.saveAuthentication(url, isCluster, authenticationToken, authenticationTokenIsPrivate, applicationKey, timeToLive, privateKey, permissions, this.id, callBack); + } + + RTGetHeartbeatTime(callBack: Function){ + ortcClient.getHeartbeatTime(this.id, callBack); + } + + RTSetHeartbeatTime(newHeartbeatTime){ + ortcClient.setHeartbeatTime(newHeartbeatTime, this.id); + } + + RTGetHeartbeatFails(callBack: Function){ + ortcClient.getHeartbeatFails(this.id, callBack); + } + + RTSetHeartbeatFails(newHeartbeatFails){ + ortcClient.setHeartbeatFails(newHeartbeatFails, this.id); + } + + RTIsHeartbeatActive(callBack: Function){ + ortcClient.isHeartbeatActive(this.id, callBack); + } + + RTEnableHeartbeat(){ + ortcClient.enableHeartbeat(this.id); + } + + RTDisableHeartbeat(){ + ortcClient.disableHeartbeat(this.id); + } + + /* + Events list + - onConnected + - onDisconnect + - onReconnect + - onReconnecting + - onSubscribed + - onUnSubscribed + - onExcption + - onMessage + - onPresence + - onDisablePresence + - onEnablePresence + */ + + RTCustomPushNotificationListener(callBack: Function){ + require('RCTDeviceEventEmitter').addListener( + 'onPushNotification', + callBack + ); + ortcClient.checkForNotifications(); + }; + + + RTEventListener(notification, callBack: Function){ + var modNotification = String(this.id) + '-' + notification; + var channelExists = RTEvents[modNotification]; + if (channelExists){ + this.RTRemoveEventListener(notification); + } + + RTEvents[modNotification] = ( + require('RCTDeviceEventEmitter').addListener( + modNotification, + callBack + ) + ); + }; + + RTRemoveEventListener(notification) + { + var modNotification = String(this.id) + '-' + notification; + RTEvents[modNotification].remove(), + delete RTEvents[modNotification]; + }; +} + +module.exports = RCTRealtimeMessagingIOS; diff --git a/README.md b/README.md index f2c5d52..360185e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,899 @@ -# RCTRealtimeMessagingIOS -Realtime Cloud Messaging React Native SDK for iOS +#Realtime Messaging SDK for React-Native + +[Realtime Cloud Messaging](http://framework.realtime.co/messaging) is a highly-scalable pub/sub message broker, allowing you to broadcast messages to millions of users, reliably and securely. It's all in the cloud so you don't need to manage servers. + +[React Native](http://facebook.github.io/react-native/) enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. + + +More information can be found on the +[Realtime native iOS SDK reference documentation](http://messaging-public.realtime.co/documentation/ios/2.1.0/index.html). + + +##Installation + +* Create a new react-native project. [Check react-native getting started](http://facebook.github.io/react-native/docs/getting-started.html#content) + +* On the terminal, go to PROJECT_DIR/node_modules/react-native. + +* Execute + + npm install --save react-native-realtimemessaging-ios + +* Drag RCTRealtimeMessaging.xcodeproj from the node_modules/react-native- realtimemessaging-ios folder into your XCode project. Click on the project in XCode, goto Build Phases then Link Binary With Libraries and add libRCTRealtimeMessaging.a + +* Drag RCTRealtimeMessaging.js to the root of your project. + + You are ready to go. + +--- + + + +##ReatimeMessagingIOS class reference + +###Import ReatimeMessaging to your project + + var module = require('RCTRealtimeMessagingIOS'); + var RCTRealtimeMessaging = new module(); + +###Event handling + +In order to get event notifications from the native SDK, the JavaScript interface has two methods for adding and removing event registration. + +**RTEventListener(notification, callBack: Function)**
+ +RTEventListener registers a given event name on the ***notification*** field and a given ***callback function*** to be fired when the event occurs. + +***Example:*** + + var module = require('RCTRealtimeMessagingIOS'); + var RCTRealtimeMessaging = new module(); + + RCTRealtimeMessaging.RTEventListener("onConnected",this._onConnected), + +**RTRemoveEventListener(notification)** + +RTRemoveEventListener removes an event registration. After this method when the event occurs the ***callback*** will not be fired. + +***Example:*** + + var module = require('RCTRealtimeMessagingIOS'); + var RCTRealtimeMessaging = new module(); + + RCTRealtimeMessaging.RTEventListener("onConnected",this._onConnected), + RCTRealtimeMessaging.RTRemoveEventListener("onConnected"), + + + +***Complete event list:*** + +* onConnected - Occurs when the client connects + +* onDisconnect - Occurs when the client disconnects + +* onReconnect - Occurs when the client reconnects + +* onReconnecting - Occurs when the client is attempting to reconnect + +* onSubscribed - Occurs when the client has successfully subscribed a channel. The event notification data is `{"channel":channel}` + +* onUnSubscribed - Occurs when the client has successfully unsubscribed a channel. The event notification data is `{"channel":channel}` + +* onException - Occurs when there is an exception. The event notification data is `{"error":error.localizedDescription}` + +* onMessage - Occurs when a message is received. The event notification data is `{"message": message,"channel": channel}` + +* onPresence - Gets the subscriptions in the specified channel and if active the first 100 unique connection metadata: + - On success -> `{"result": result}` + - On error -> `{"error": error}` + +* onEnablePresence - Enables presence for the specified channel with the first 100 unique connection metadata: + - On success -> `{"result": result}` + - On error -> `{"error": error}` + +* onDisablePresence - Disables presence for the specified channel: + - On success -> `{"result": result}` + - On error -> `{"error": error}` + +###Push notification handling ( available from 1.0.6 ) + +####Configure your project for push notifications handling + +To configure your react-native project to receive push notifications you must follow [this guide](http://messaging-public.realtime.co/documentation/starting-guide/mobilePushAPNS.html) for the iOS platform. +After this process you must drag AppDelegate+RealtimeRCTPushNotifications(.m, .h) category from RCTRealtimeMessaging plugin folder to your project, where AppDelegate class is, and you are ready to go. + +####Handling automatic push notifications through javascript + +To receive push notifications in a RealtimeMessaging channel you must use the SubscribeWithNotifications method. The onMessage event listener will be the only entry point for automatic push notifications(sent using a Realtime client send method), so when the application starts you must connect and subscribe the channels for handling that type of notifications. + +***Example:*** + + RCTRealtimeMessaging.RTSubscribeWithNotifications(this.state.channel, true); + RCTRealtimeMessaging.RTEventListener("onMessage",this._onMessage), + _onMessage: function(messageEvent) + { + this._log("Received message or automatic notification: ["+messageEvent.message+"] on channel ["+ messageEvent.channel+"]"); + }, + +####Handling custom push notifications through javascript + +For handling custom push notifications ( sent using the Realtime mobile push notifications REST API) we added the following event listener: + +* RTCustomPushNotificationListener(callBack: Function) + +***Example:*** + + componentDidMount: function(){ + RCTRealtimeMessaging.RTCustomPushNotificationListener(this._onNotification); + }, + + _onNotification: function(data) + { + this._log("Received notification: " + JSON.stringify(data)); + }, + +---------- + +###Methods + +#####RTConnect(config) + +Connects the client to Realtime server with the given configuration. + +**Parameters** + +* appkey - Realtime application key +* token - Authentication token +* connectionMetadata - Connection metadata string +* clusterUrl or url - The Realtime Messaging cluster or server URL + +***Example:*** + + RCTRealtimeMessaging.RTEventListener("onConnected",function(){ + console.log('Connected to Realtime Messaging'); + }), + + RCTRealtimeMessaging.RTConnect( + { + appKey:this.state.appKey, + token:this.state.token, + connectionMetadata:this.state.connectionMetadata, + clusterUrl:this.state.clusterUrl + }); + + +---------- + +#####RTDisconnect() + +Disconnects the client from the Realtime server. + +***Example:*** + + RCTRealtimeMessaging.RTEventListener("onDisconnect", function(){ + console.log('Disconnected from Realtime Messaging'); + }), + + RCTRealtimeMessaging.RTDisconnect(); + +---------- + +#####RTSubscribe(channel, subscribeOnReconnect: boolean) + +Subscribes a pub/sub channel to receive messages. + +**Parameters** + +* channel - Channel name + +* subscribeOnReconnected - +Indicates whether the client should subscribe the channel when reconnected (if it was previously subscribed when connected). + +***Example:*** + + RCTRealtimeMessaging.RTEventListener("onSubscribed", function(subscribedEvent){ + console.log('Subscribed channel: ' + subscribedEvent.channel); + }), + + RCTRealtimeMessaging.RTSubscribe("MyChannel", true); + +---------- + +#####RTSubscribeWithNotifications(channel, subscribeOnReconnect: boolean) + +Subscribes a pub/sub channel with Push Notifications Service, to receive messages even if the app is not in foreground. + +**Parameters** + +* channel - Channel name + +* subscribeOnReconnected - +Indicates whether the client should subscribe to the channel when reconnected (if it was previously subscribed when connected). + +***Example:*** + + RCTRealtimeMessaging.RTSubscribeWithNotifications("MyChannel", true); + +---------- + +#####RTUnsubscribe(channel) + +Unsubscribes a channel. + +**Parameters** + +* channel - Channel name. + +***Example:*** + + RCTRealtimeMessaging.RTUnsubscribe("MyChannel"); + +---------- + +#####RTSendMessage(message, channel) + +Sends a message to a pub/sub channel. + +**Parameters** + +* channel - Channel name + +* message - The message to send (a string/stringified JSON object) + +***Example:*** + + RCTRealtimeMessaging.RTSendMessage("Hello World","MyChannel"); + +---------- + +#####RTEnablePresence(aUrl, aIsCluster:boolean, aApplicationKey, aPrivateKey, channel, aMetadata) + +Enables presence for the specified channel with first 100 unique connection metadata. + +**Parameters** + +* channel - Channel to enable presence + +* applicationKey - Realtime application key + +* isCluster - Specifies if url is a Realtime cluster + +* metadata - Collect the first 100 unique connection metadata of subscribers + +* privateKey - The Realtime application private key + +* url - Realtime server or cluster URL + +***Example:*** + + RCTRealtimeMessaging.RTEventListener("onEnablePresence", function(event){ + if(event.result){ + console.log('Realtime enablePresence result: ' + event.result); + }else{ + console.log('Realtime enablePresence error: ' + event.error); + } + }), + + RCTRealtimeMessaging.RTEnablePresence(aUrl, aIsCluster, aApplicationKey, aPrivateKey, channel, aMetadata); + + +---------- + +#####RTDisablePresence(aUrl, aIsCluster:boolean, aApplicationKey, aPrivateKey, channel, aMetadata) + +Disables presence for the specified channel. + +**Parameters** + +* channel - Channel to disable presence + +* applicationKey - Realtime application key + +* url - Realtime server or cluster URL + +* isCluster - Specifies if url is a Realtime cluster +* +* privateKey - The Realtime application private key + +***Example:*** + + RCTRealtimeMessaging.RTEventListener("onDisablePresence", function(event){ + if(event.result){ + console.log('Realtime disablePresence result: ' + event.result); + }else{ + console.log('Realtime disablePresence error: ' + event.error); + } + }), + + RCTRealtimeMessaging.RTDisablePresence(aUrl, aIsCluster, aApplicationKey, aPrivateKey, channel, aMetadata); + +---------- + +#####RTPresence(aUrl, aIsCluster:boolean, aApplicationKey, aAuthenticationToken, channel) + +Gets a dictionary with the total number of subscriptions in the specified channel and if active the first 100 unique connection metadata of the subscribers. + +**Parameters** + +* channel - Channel with presence data active + +* applicationKey - Realtime application key + +* url - Realtime server or cluster URL + +* isCluster - Specifies if url is a Realtime cluster + +* authenticationToken - Authentication token with permissions to the presence service + +***Example:*** + + RCTRealtimeMessaging.RTEventListener("onPresence", function(event){ + if(event.result){ + console.log('Realtime presence result: ' + JSON.stringify(event.result)); + }else{ + console.log('Realtime presence error: ' + event.error); + } + }), + + RCTRealtimeMessaging.RTPresence(aUrl, aIsCluster, aApplicationKey, aAuthenticationToken, channel); + +---------- + +#####RTIsSubscribed(channel, callBack: function) + +Indicates whether a given channel is currently subscribed. + +**Parameters** + +* channel - Channel name. + +* callback - Callback function to be called with the result (true or false). + +***Example:*** + + RCTRealtimeMessaging.RTIsSubscribed("MyChannel", function(result){ + if(result == true){ + console.log('channel is subscribed'); + }else{ + console.log('channel is not subscribed'); + } + }); + +---------- + +#####RTSaveAuthentication(url, isCluster, authenticationToken, authenticationTokenIsPrivate, applicationKey, timeToLive, privateKey, permissions, callBack: function) + +Authenticates a token with the given channel permissions. + +**Parameters** + +* url - Realtime server or cluster URL + +* isCluster - Specifies if url is a Realtime cluster + +* authenticationToken - The token to authenticate + +* authenticationTokenIsPrivate - +Indicates whether the authentication token is private (1) or not (0). Private tokens can only be used by one client + +* applicationKey - +Realtime application key + +* timeToLive - +The allowed inactivity time (in seconds) for the authenticated token + +* privateKey - The Realtime application private key + +* permissions - +The channels and their permissions (w: write, r: read, p: presence) + +* callback - +Callback function with the result (true or false). + +***Example:*** + + RCTRealtimeMessaging.RTSaveAuthentication(url, isCluster, authenticationToken, authenticationTokenIsPrivate, applicationKey, timeToLive, privateKey, permissions, function(result){ + + if(result == true){ + console.log('Authentication saved successfully'); + }else{ + console.log('Error saving authentication'); + } + }); + +---------- + +#####RTGetHeartbeatTime(callBack: function) + +Get the client heartbeat interval. + +**Parameters** + +* callback - +Callback function with the heartbeat interval value + +***Example:*** + + RCTRealtimeMessaging.RTGetHeartbeatTime(function(result){ + console.log('HeartbeatTime for this client is: ' + result); + }); + +---------- + +#####RTSetHeartbeatTime(newHeartbeatTime) + +Sets the client heartbeat interval. + +**Parameters** + +* newHeartbeatTime - The new heartbeat interval + +***Example:*** + + RCTRealtimeMessaging.RTSetHeartbeatTime(10); + +---------- + +#####RTGetHeartbeatFails(callBack: function) + +Number of times the heartbeat can fail before the connection is reconnected + +**Parameters** + +* callBack - The callback function to get the HeartbeatFails value + +***Example:*** + + RCTRealtimeMessaging.RTGetHeartbeatFails(function(result){ + console.log('HeartbeatFails Time for this client is: ' + result); + }); + +---------- + +#####RTSetHeartbeatFails(newHeartbeatFails) + +Sets the number of times the heartbeat can fail before the connection is reconnected + +**Parameters** + +* newHeartbeatFails - The new heartbeat fails value + +***Example:*** + + RCTRealtimeMessaging.RTSetHeartbeatFails(3); + +---------- + +#####RTIsHeartbeatActive(callBack: function) + +Indicates whether the client heartbeat is active or not. + +**Parameters** + +* callBack - The callback function with the result + +***Example:*** + + RCTRealtimeMessaging.RTIsHeartbeatActive(function(result){ + if(result == true){ + console.log('heartbeat active'); + }else{ + console.log('heartbeat inactive'); + } + }); + +---------- + +#####RTEnableHeartbeat() + +Enables the client heartbeat. + +***Example:*** + + RCTRealtimeMessaging.RTEnableHeartbeat() + +---------- + +#####RTDisableHeartbeat() + +Disables the client heartbeat. + +***Example:*** + + RCTRealtimeMessaging.RTDisableHeartbeat() + + + +## Full example ( index.ios.js ) + + 'use strict'; + + var React = require('react-native'); + var module = require('RCTRealtimeMessagingIOS'); + var RCTRealtimeMessaging = new module(); + + var messages = []; + + var { + AppRegistry, + Image, + StyleSheet, + Text, + Navigator, + TextInput, + ScrollView, + TouchableHighlight, + ListView, + View + } = React; + + + var RealtimeRCT = React.createClass({ + + doConnect: function(){ + this._log('Trying to connect!'); + + RCTRealtimeMessaging.RTEventListener("onConnected",this._onConnected), + RCTRealtimeMessaging.RTEventListener("onDisconnected",this._onDisconnected), + RCTRealtimeMessaging.RTEventListener("onSubscribed",this._onSubscribed), + RCTRealtimeMessaging.RTEventListener("onUnSubscribed",this._onUnSubscribed), + RCTRealtimeMessaging.RTEventListener("onException",this._onException), + RCTRealtimeMessaging.RTEventListener("onMessage",this._onMessage), + RCTRealtimeMessaging.RTEventListener("onPresence",this._onPresence); + + RCTRealtimeMessaging.RTConnect( + { + appKey:this.state.appKey, + token:this.state.token, + connectionMetadata:this.state.connectionMetadata, + clusterUrl:this.state.clusterUrl + }); + }, + + + componentWillUnmount: function() { + RCTRealtimeMessaging.RTDisconnect(); + }, + + doDisconnect:function(){ + RCTRealtimeMessaging.RTDisconnect(); + }, + + doSubscribe: function(){ + RCTRealtimeMessaging.RTSubscribe(this.state.channel, true); + }, + + doUnSubscribe: function(){ + RCTRealtimeMessaging.RTUnsubscribe(this.state.channel); + }, + + doSendMessage: function(){ + RCTRealtimeMessaging.RTSendMessage(this.state.message, this.state.channel); + }, + + doPresence: function(){ + RCTRealtimeMessaging.RTPresence( + this.state.clusterUrl, + true, + this.state.appKey, + this.state.token, + this.state.channel + ); + }, + + doSegue: function(){ + this.props.navigator.push({ + title: NavigatorIOSExample.title, + component: EmptyPage, + rightButtonTitle: 'Cancel', + onRightButtonPress: () => this.props.navigator.pop(), + passProps: { + text: 'This page has a right button in the nav bar', + } + }); + }, + + _onException: function(exceptionEvent){ + this._log("Exception:" + exceptionEvent.error); + }, + + _onConnected: function() + { + this._log("connected"); + }, + + + _onDisconnected: function(){ + this._log("disconnected"); + }, + + _onSubscribed: function(subscribedEvent) + { + this._log("subscribed channel " + subscribedEvent.channel); + }, + + _onUnSubscribed: function(unSubscribedEvent) + { + this._log("unsubscribed channel " + unSubscribedEvent.channel); + }, + + _onMessage: function(messageEvent) + { + this._log("received message: ["+messageEvent.message+"] on channel [" + messageEvent.channel+"]"); + }, + + _onPresence: function(presenceEvent){ + if (presenceEvent.error) { + this._log("Error getting presence: " + presenceEvent.error); + }else + { + this._log("Presence data: " + JSON.stringify(presenceEvent.result)); + }; + }, + + + getInitialState: function() { + return { + clusterUrl: "http://ortc-developers.realtime.co/server/2.1/", + token: "SomeAuthenticatedToken", + appKey: "YOUR_APP_KEY", + channel: "yellow", + connectionMetadata: "clientConnMeta", + message: "some message", + dataSource: new ListView.DataSource({ + rowHasChanged: (row1, row2) => row1 !== row2, + }), + }; + }, + + _renderRow: function(rowData: string, sectionID: number, rowID: number) { + return ( + + + + + {rowData} + + + + + + ); + }, + + _log: function(message: string) + { + var time = this.getFormattedDate(); + time += " - " + message + var temp = []; + temp[0] = time; + + for (var i = 0; i < messages.length; i++) { + temp[i+1] = messages[i]; + }; + messages = temp; + + this.setState({ + dataSource: this.getDataSource(messages) + }); + }, + + getFormattedDate: function() { + var date = new Date(); + var str = date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); + return str; + }, + + getDataSource: function(messages: Array): ListView.DataSource { + return this.state.dataSource.cloneWithRows(messages); + }, + + render: function() { + return ( + + + + clusterUrl: + + this.setState({server: text})} + /> + + + + + Authentication Token: + + this.setState({token: text})} + /> + + + Application Key: + + this.setState({appKey: text})} + /> + + + + + Channel: + + this.setState({channel: text})} + /> + + + Connection Metadata: + + this.setState({connectionMetadata: text})} + /> + + + + + Message: + + this.setState({message: text})} + /> + + + + + + + Connect + + + + + + Disconnect + + + + + + Subscribe + + + + + + Unsubscribe + + + + + + Send + + + + + + Presence + + + + + + + + )} + }); + + var styles = StyleSheet.create({ + container: { + marginTop: 30, + margin: 5, + backgroundColor: '#FFFFFF', + }, + list: { + flexDirection: 'column', + backgroundColor: '#F6F6F6', + height:150, + }, + rowView:{ + alignItems: 'stretch', + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent:'center', + }, + button:{ + margin: 5, + }, + margin:{ + + }, + custom:{ + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent:'space-between', + }, + textInput:{ + height: 30, + borderColor: 'gray', + borderWidth: 1, + borderRadius: 4, + padding: 5, + fontSize: 15, + }, + + halfTextInput:{ + height: 30, + borderColor: 'gray', + borderWidth: 1, + borderRadius: 4, + padding: 5, + fontSize: 15, + width: 153, + }, + tryAgain: { + backgroundColor: '#336699', + padding: 13, + borderRadius: 5, + }, + tryAgainText: { + color: '#ffffff', + fontSize: 14, + fontWeight: '500', + }, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 10, + }, + instructions: { + textAlign: 'center', + color: '#333333', + }, + row: { + flexDirection: 'row', + justifyContent: 'center', + padding: 10, + backgroundColor: '#F6F6F6', + }, + separator: { + height: 1, + backgroundColor: '#CCCCCC', + }, + thumb: { + width: 64, + height: 64, + }, + text: { + flex: 1, + fontSize: 13, + }, + }); + + AppRegistry.registerComponent('RealtimeRCT', () => RealtimeRCT); + + +## Authors +Realtime.co + + + + + diff --git a/SocketRocket/NSData+SRB64Additions.h b/SocketRocket/NSData+SRB64Additions.h new file mode 100644 index 0000000..115108d --- /dev/null +++ b/SocketRocket/NSData+SRB64Additions.h @@ -0,0 +1,23 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@interface NSData (SRB64Additions) + +- (NSString *)SR_stringByBase64Encoding; + +@end diff --git a/SocketRocket/RCTSRWebSocket.h b/SocketRocket/RCTSRWebSocket.h new file mode 100644 index 0000000..ccaa464 --- /dev/null +++ b/SocketRocket/RCTSRWebSocket.h @@ -0,0 +1,132 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +typedef enum { + RCTSR_CONNECTING = 0, + RCTSR_OPEN = 1, + RCTSR_CLOSING = 2, + RCTSR_CLOSED = 3, +} RCTSRReadyState; + +typedef enum RCTSRStatusCode : NSInteger { + RCTSRStatusCodeNormal = 1000, + RCTSRStatusCodeGoingAway = 1001, + RCTSRStatusCodeProtocolError = 1002, + RCTSRStatusCodeUnhandledType = 1003, + // 1004 reserved. + RCTSRStatusNoStatusReceived = 1005, + // 1004-1006 reserved. + RCTSRStatusCodeInvalidUTF8 = 1007, + RCTSRStatusCodePolicyViolated = 1008, + RCTSRStatusCodeMessageTooBig = 1009, +} RCTSRStatusCode; + +@class RCTSRWebSocket; + +extern NSString *const RCTSRWebSocketErrorDomain; +extern NSString *const RCTSRHTTPResponseErrorKey; + +#pragma mark - RCTSRWebSocketDelegate + +@protocol RCTSRWebSocketDelegate; + +#pragma mark - RCTSRWebSocket + +@interface RCTSRWebSocket : NSObject + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, readonly) RCTSRReadyState readyState; +@property (nonatomic, readonly, strong) NSURL *url; + +// This returns the negotiated protocol. +// It will be nil until after the handshake completes. +@property (nonatomic, readonly, copy) NSString *protocol; + +// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithURLRequest:(NSURLRequest *)request; + +// Some helper constructors. +- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +- (instancetype)initWithURL:(NSURL *)url; + +// Delegate queue will be dispatch_main_queue by default. +// You cannot set both OperationQueue and dispatch_queue. +- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; +- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; + +// By default, it will schedule itself on +[NSRunLoop RCTSR_networkRunLoop] using defaultModes. +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; + +// RCTSRWebSockets are intended for one-time-use only. Open should be called once and only once. +- (void)open; + +- (void)close; +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; + +// Send a UTF8 String or Data. +- (void)send:(id)data; + +// Send Data (can be nil) in a ping message. +- (void)sendPing:(NSData *)data; + +@end + +#pragma mark - RCTSRWebSocketDelegate + +@protocol RCTSRWebSocketDelegate + +// message will either be an NSString if the server is using text +// or NSData if the server is using binary. +- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message; + +@optional + +- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket; +- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error; +- (void)webSocket:(RCTSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; +- (void)webSocket:(RCTSRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload; + +@end + +#pragma mark - NSURLRequest (CertificateAdditions) + +@interface NSURLRequest (CertificateAdditions) + +@property (nonatomic, readonly, copy) NSArray *RCTSR_SSLPinnedCertificates; + +@end + +#pragma mark - NSMutableURLRequest (CertificateAdditions) + +@interface NSMutableURLRequest (CertificateAdditions) + +@property (nonatomic, copy) NSArray *RCTSR_SSLPinnedCertificates; + +@end + +#pragma mark - NSRunLoop (RCTSRWebSocket) + +@interface NSRunLoop (RCTSRWebSocket) + ++ (NSRunLoop *)RCTSR_networkRunLoop; + +@end diff --git a/SocketRocket/base64.h b/SocketRocket/base64.h new file mode 100644 index 0000000..c345e2e --- /dev/null +++ b/SocketRocket/base64.h @@ -0,0 +1,34 @@ +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#ifndef SocketRocket_base64_h +#define SocketRocket_base64_h + +#include + +extern int +b64_ntop(u_char const *src, + size_t srclength, + char *target, + size_t targsize); + +extern int +b64_pton(char const *src, + u_char *target, + size_t targsize); + + +#endif diff --git a/package.json b/package.json new file mode 100644 index 0000000..5c8788a --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "react-native-realtimemessaging-ios", + "version": "1.0.8", + "description": "The Realtime Framework Cloud Messaging Pub/Sub client for React-Native", + "main": "RCTRealtimeMessagingIOS.js", + "scripts": { + "start": "exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/realtime-framework/RCTRealtimeMessaging.git" + }, + "keywords": [ + "react-component", + "reactnative", + "react-native", + "RealtimeMessaging", + "Realtime", + "ios" + ], + "author": "Realtime", + "license": "MIT", + "bugs": { + "url": "https://github.com/realtime-framework/RCTRealtimeMessaging/issues" + }, + "homepage": "https://github.com/realtime-framework/RCTRealtimeMessaging" +}