├── .github ├── PULL_REQUEST_TEMPLATE.md └── stale.yml ├── .gitignore ├── .swiftlint.yml ├── CHANGELOG.md ├── Cartfile.private ├── Cartfile.resolved ├── LICENSE ├── Package.resolved ├── Package.swift ├── PushNotifications.podspec ├── PushNotifications.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── PushNotifications ├── PushNotifications.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── PushNotifications.xcscheme ├── PushNotifications │ ├── Info.plist │ └── PushNotifications.h └── PushNotificationsTests │ ├── Info.plist │ └── PushNotificationsTests-Bridging-Header.h ├── README.md ├── Scripts └── generate-api-docs.sh ├── Sources ├── Extensions │ ├── Array+CalculateMD5Hash.swift │ ├── Encodable+Encode.swift │ ├── String+HexStringToData.swift │ └── URL+NetworkService.swift ├── Helpers │ ├── DeviceTokenHelper.swift │ └── MD5.swift ├── Models │ ├── AuthData.swift │ ├── Constants.swift │ ├── Device.swift │ ├── FeatureFlags.swift │ ├── HTTPMethod.swift │ ├── InstanceId.swift │ ├── InterestValidationError.swift │ ├── Interests.swift │ ├── Metadata.swift │ ├── MultipleInvalidInterestsError.swift │ ├── PersistenceConstants.swift │ ├── PublishId.swift │ ├── PushNotificationsAPIError.swift │ ├── PushNotificationsError.swift │ ├── PusherAlreadyRegisteredError.swift │ ├── Reason.swift │ ├── Register.swift │ ├── RemoteNotificationType.swift │ ├── SDK.swift │ ├── ServerSyncEvent.swift │ ├── ServerSyncJob.swift │ ├── SystemVersion.swift │ └── Token.swift ├── Protocols │ ├── PropertyListReadable.swift │ ├── PushNotificationsNetworkable.swift │ ├── ReportEventType.swift │ ├── RetryStrategy.swift │ └── TokenProvider.swift ├── PushNotifications.swift ├── PushNotificationsStatic.swift └── Services │ ├── BeamsTokenProvider.swift │ ├── DeviceStateStore.swift │ ├── EventTypeHandler.swift │ ├── NetworkService.swift │ ├── ServerSyncEventHandler.swift │ ├── ServerSyncJobStore.swift │ └── ServerSyncProcessHandler.swift ├── Tests ├── .swiftlint.yml ├── Extensions │ └── Array+ContainsSameElements.swift ├── Info.plist ├── Integration │ ├── ApplicationStartTests.swift │ ├── ClearAllStateTest.swift │ ├── DeviceInterestsTest.swift │ ├── DeviceRegistrationTests.swift │ ├── MultipleClassInstanceSupportTest.swift │ ├── MultipleInstanceSupportTest.swift │ ├── ReportEventTypeTests.swift │ ├── SetUserIdTest.swift │ ├── StopTests.swift │ ├── TestAPIClientHelper.swift │ └── TestHelper.swift └── Unit │ ├── Extensions │ └── ArrayContainsSameElementsTests.swift │ ├── Helpers │ ├── DeviceTokenHelperTests.swift │ └── InterestsMD5HashTests.swift │ ├── Models │ ├── AuthDataTests.swift │ ├── ConstantsTests.swift │ ├── FeatureFlagsTests.swift │ ├── InterestsTests.swift │ ├── PublishIdTests.swift │ ├── ReasonTests.swift │ ├── RegisterTests.swift │ ├── SDKTests.swift │ └── SystemVersionTests.swift │ └── Services │ ├── BeamsTokenProviderTests.swift │ ├── DeviceStateStoreTests.swift │ ├── DeviceTests.swift │ ├── EventTypeHandlerTests.swift │ ├── InstanceDeviceStateStoreTests.swift │ ├── InterestPersistableTests.swift │ ├── ServerSyncJobStoreTests.swift │ ├── ServerSyncProcessHandlerTests.swift │ └── UserPersistableTests.swift ├── docs ├── Classes.html ├── Classes │ ├── AuthData.html │ ├── BeamsTokenProvider.html │ ├── InstanceDeviceStateStore.html │ ├── PushNotifications.html │ └── PushNotificationsStatic.html ├── Enums.html ├── Enums │ ├── InvalidInterestError.html │ ├── MultipleInvalidInterestsError.html │ ├── PushNotificationsError.html │ ├── PusherAlreadyRegisteredError.html │ ├── RemoteNotificationType.html │ ├── Result.html │ └── TokenProviderError.html ├── Functions.html ├── Protocols.html ├── Protocols │ ├── InterestsChangedDelegate.html │ └── TokenProvider.html ├── Structs.html ├── badge.svg ├── css │ ├── highlight.css │ └── jazzy.css ├── docsets │ ├── PushNotifications.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ ├── AuthData.html │ │ │ │ ├── BeamsTokenProvider.html │ │ │ │ ├── InstanceDeviceStateStore.html │ │ │ │ ├── PushNotifications.html │ │ │ │ └── PushNotificationsStatic.html │ │ │ ├── Enums.html │ │ │ ├── Enums │ │ │ │ ├── InvalidInterestError.html │ │ │ │ ├── MultipleInvalidInterestsError.html │ │ │ │ ├── PushNotificationsError.html │ │ │ │ ├── PusherAlreadyRegisteredError.html │ │ │ │ ├── RemoteNotificationType.html │ │ │ │ ├── Result.html │ │ │ │ └── TokenProviderError.html │ │ │ ├── Functions.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── InterestsChangedDelegate.html │ │ │ │ └── TokenProvider.html │ │ │ ├── Structs.html │ │ │ ├── badge.svg │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ ├── gh.png │ │ │ │ └── spinner.gif │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ ├── jazzy.search.js │ │ │ │ ├── jquery.min.js │ │ │ │ ├── lunr.min.js │ │ │ │ └── typeahead.jquery.js │ │ │ ├── search.json │ │ │ └── undocumented.json │ │ │ └── docSet.dsidx │ └── PushNotifications.tgz ├── img │ ├── carat.png │ ├── dash.png │ ├── gh.png │ └── spinner.gif ├── index.html ├── js │ ├── jazzy.js │ ├── jazzy.search.js │ ├── jquery.min.js │ ├── lunr.min.js │ └── typeahead.jquery.js ├── search.json └── undocumented.json ├── push-notifications-ios ├── push-notifications-ios.xcodeproj │ └── project.pbxproj └── push-notifications-ios │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.swift │ └── push-notifications-ios.entitlements ├── push-notifications-mac-objc ├── push-notifications-mac-objc.xcodeproj │ └── project.pbxproj └── push-notifications-mac-objc │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.h │ ├── ViewController.m │ ├── main.m │ ├── push-notifications-mac-objc.entitlements │ └── push_notifications_mac_objc.entitlements ├── push-notifications-mac ├── push-notifications-mac.xcodeproj │ └── project.pbxproj └── push-notifications-mac │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.swift │ └── push_notifications_mac.entitlements └── push-notifications-objc ├── push-notifications-objc.xcodeproj └── project.pbxproj └── push-notifications-objc ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m ├── main.m └── push-notifications-objc.entitlements /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What? 2 | 3 | ... 4 | 5 | #### Why? 6 | 7 | ... 8 | 9 | ---- 10 | CC @pusher/mobile 11 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 365 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 7 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - pinned 16 | - security 17 | 18 | # Set to true to ignore issues with an assignee (defaults to false) 19 | exemptAssignees: true 20 | 21 | # Comment to post when marking as stale. Set to `false` to disable 22 | markComment: > 23 | This issue has been automatically marked as stale because it has not had 24 | recent activity. It will be closed if no further activity occurs. If you'd 25 | like this issue to stay open please leave a comment indicating how this issue 26 | is affecting you. Thankyou. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | .swiftpm/ 42 | swiftpm/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots 68 | fastlane/test_output 69 | 70 | # macOS 71 | 72 | .DS_Store 73 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - .build 3 | - Sources/Helpers/MD5.swift # this dependency will be removed in the future version of the SDK 4 | - Carthage 5 | - Package.swift 6 | 7 | opt_in_rules: 8 | - anyobject_protocol 9 | - closure_end_indentation 10 | - closure_spacing 11 | - explicit_init 12 | - modifier_order 13 | - prefer_self_type_over_type_of_self 14 | - sorted_imports 15 | - test_case_accessibility 16 | - unneeded_parentheses_in_closure_argument 17 | - vertical_whitespace_between_cases 18 | - vertical_whitespace_closing_braces 19 | 20 | identifier_name: 21 | excluded: 22 | - id 23 | 24 | disabled_rules: 25 | - line_length 26 | 27 | # This generates a compiler error if more than this many SwiftLint warnings are present 28 | # (This threshold can become more restrictive as remaining warnings are resolved via refactoring) 29 | warning_threshold: 12 -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "AliSoftware/OHHTTPStubs" 2 | github "Quick/Nimble" 3 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "AliSoftware/OHHTTPStubs" "9.1.0" 2 | github "Quick/Nimble" "v9.2.0" 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pusher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CwlCatchException", 6 | "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "f809deb30dc5c9d9b78c872e553261a61177721a", 10 | "version": "2.0.0" 11 | } 12 | }, 13 | { 14 | "package": "CwlPreconditionTesting", 15 | "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "02b7a39a99c4da27abe03cab2053a9034379639f", 19 | "version": "2.0.0" 20 | } 21 | }, 22 | { 23 | "package": "Nimble", 24 | "repositoryURL": "https://github.com/Quick/Nimble", 25 | "state": { 26 | "branch": null, 27 | "revision": "af1730dde4e6c0d45bf01b99f8a41713ce536790", 28 | "version": "9.2.0" 29 | } 30 | }, 31 | { 32 | "package": "OHHTTPStubs", 33 | "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs", 34 | "state": { 35 | "branch": null, 36 | "revision": "12f19662426d0434d6c330c6974d53e2eb10ecd9", 37 | "version": "9.1.0" 38 | } 39 | }, 40 | { 41 | "package": "SourceKitten", 42 | "repositoryURL": "https://github.com/jpsim/SourceKitten.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "7f4be006fe73211b0fd9666c73dc2f2303ffa756", 46 | "version": "0.31.0" 47 | } 48 | }, 49 | { 50 | "package": "swift-argument-parser", 51 | "repositoryURL": "https://github.com/apple/swift-argument-parser.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "9564d61b08a5335ae0a36f789a7d71493eacadfc", 55 | "version": "0.3.2" 56 | } 57 | }, 58 | { 59 | "package": "SwiftLint", 60 | "repositoryURL": "https://github.com/realm/SwiftLint", 61 | "state": { 62 | "branch": null, 63 | "revision": "180d94132758dd183124ab1e63d6aa8e10023ec2", 64 | "version": "0.43.1" 65 | } 66 | }, 67 | { 68 | "package": "SwiftyTextTable", 69 | "repositoryURL": "https://github.com/scottrhoyt/SwiftyTextTable.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", 73 | "version": "0.9.0" 74 | } 75 | }, 76 | { 77 | "package": "SWXMLHash", 78 | "repositoryURL": "https://github.com/drmohundro/SWXMLHash.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "9183170d20857753d4f331b0ca63f73c60764bf3", 82 | "version": "5.0.2" 83 | } 84 | }, 85 | { 86 | "package": "Yams", 87 | "repositoryURL": "https://github.com/jpsim/Yams.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "9ff1cc9327586db4e0c8f46f064b6a82ec1566fa", 91 | "version": "4.0.6" 92 | } 93 | } 94 | ] 95 | }, 96 | "version": 1 97 | } 98 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package(name: "PushNotifications", 6 | platforms: [.macOS(.v10_10), 7 | .iOS(.v10)], 8 | products: [ 9 | .library(name: "PushNotifications", 10 | targets: ["PushNotifications"]) 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/Quick/Nimble", 14 | .upToNextMajor(from: "9.2.0")), 15 | .package(url: "https://github.com/AliSoftware/OHHTTPStubs", 16 | .upToNextMajor(from: "9.1.0")), 17 | // Source code linting 18 | .package(url: "https://github.com/realm/SwiftLint", 19 | .upToNextMajor(from: "0.43.1")) 20 | ], 21 | targets: [ 22 | .target(name: "PushNotifications", 23 | path: "Sources"), 24 | .testTarget(name: "PushNotificationsTests", 25 | dependencies: ["PushNotifications", 26 | "Nimble", 27 | "OHHTTPStubs", 28 | "OHHTTPStubsSwift"], 29 | path: "Tests") 30 | ], 31 | swiftLanguageVersions: [.v5]) 32 | -------------------------------------------------------------------------------- /PushNotifications.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'PushNotifications' 3 | s.version = '4.0.0' 4 | s.summary = 'PushNotifications SDK' 5 | s.homepage = 'https://github.com/pusher/push-notifications-swift' 6 | s.license = 'MIT' 7 | s.author = { "Pusher Limited" => "support@pusher.com" } 8 | s.source = { git: "https://github.com/pusher/push-notifications-swift.git", tag: s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/pusher' 10 | s.documentation_url = 'https://pusher.github.io/push-notifications-swift/Classes/PushNotifications.html' 11 | 12 | s.requires_arc = true 13 | s.source_files = 'Sources/**/*.swift' 14 | 15 | s.ios.deployment_target = '10.0' 16 | s.osx.deployment_target = '10.10' 17 | end 18 | -------------------------------------------------------------------------------- /PushNotifications.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PushNotifications.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PushNotifications.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PushNotifications/PushNotifications.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PushNotifications/PushNotifications.xcodeproj/xcshareddata/xcschemes/PushNotifications.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 64 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /PushNotifications/PushNotifications/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /PushNotifications/PushNotifications/PushNotifications.h: -------------------------------------------------------------------------------- 1 | #import 2 | #if TARGET_OS_IPHONE 3 | #import 4 | #endif 5 | //! Project version number for PushNotifications. 6 | FOUNDATION_EXPORT double PushNotificationsVersionNumber; 7 | 8 | //! Project version string for PushNotifications. 9 | FOUNDATION_EXPORT const unsigned char PushNotificationsVersionString[]; 10 | 11 | // In this header, you should import all the public headers of your framework using statements like #import 12 | -------------------------------------------------------------------------------- /PushNotifications/PushNotificationsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 4.0.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PushNotifications/PushNotificationsTests/PushNotificationsTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pusher Beams iOS & macOS SDK 2 | 3 | ![Build Status](https://app.bitrise.io/app/2798096bb06e322f/status.svg?token=GHiO2KcqAY_UDS8g8M-f5g) 4 | [![codecov](https://codecov.io/gh/pusher/push-notifications-swift/branch/master/graph/badge.svg)](https://codecov.io/gh/pusher/push-notifications-swift) 5 | [![Latest Release](https://img.shields.io/github/v/release/pusher/push-notifications-swift)](https://github.com/pusher/push-notifications-swift/releases) 6 | [![API Docs](https://img.shields.io/badge/Docs-here!-lightgrey)](https://pusher.github.io/push-notifications-swift/) 7 | [![Supported Platforms](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpusher%2Fpush-notifications-swift%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/pusher/push-notifications-swift) 8 | [![Swift Versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpusher%2Fpush-notifications-swift%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/pusher/push-notifications-swift) 9 | [![Twitter](https://img.shields.io/badge/twitter-@Pusher-blue.svg?style=flat)](http://twitter.com/Pusher) 10 | [![LICENSE](https://img.shields.io/github/license/pusher/push-notifications-swift)](https://github.com/pusher/push-notifications-swift/blob/main/LICENSE) 11 | 12 | ## Example Code 13 | 14 | - [iOS with Swift](https://github.com/pusher/push-notifications-swift/blob/master/push-notifications-ios/push-notifications-ios/AppDelegate.swift) 15 | - [iOS with Objective-C](https://github.com/pusher/push-notifications-swift/blob/master/push-notifications-objc/push-notifications-objc/AppDelegate.m) 16 | - [macOS with Swift](https://github.com/pusher/push-notifications-swift/blob/master/push-notifications-mac/push-notifications-mac/AppDelegate.swift) 17 | - [macOS with Objective-C](https://github.com/pusher/push-notifications-swift/blob/master/push-notifications-mac-objc/push-notifications-mac-objc/AppDelegate.m) 18 | 19 | ## Building and Running 20 | 21 | ### Minimum Requirements 22 | 23 | - Swift 5.0+ 24 | - [Xcode 12.0 and above](https://itunes.apple.com/us/app/xcode/id497799835) - The easiest way to get Xcode is from the [App Store](https://itunes.apple.com/us/app/xcode/id497799835?mt=12), but you can also download it from [developer.apple.com](https://developer.apple.com/) if you have an AppleID registered with an Apple Developer account. 25 | 26 | ## Installation 27 | 28 | ### CocoaPods 29 | 30 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: 31 | 32 | ```bash 33 | $ gem install cocoapods 34 | ``` 35 | 36 | > CocoaPods version 1.3.1 or newer is recommended to build Pusher Beams. 37 | 38 | To integrate Pusher Beams into your Xcode project using CocoaPods, specify it in your `Podfile`: 39 | 40 | ```ruby 41 | source 'https://github.com/CocoaPods/Specs.git' 42 | platform :ios, '10.0' 43 | use_frameworks! 44 | 45 | # Replace `` with your app's target name. 46 | target '' do 47 | pod 'PushNotifications', '~> 4.0.0' 48 | end 49 | ``` 50 | 51 | Then, run the following command: 52 | 53 | ```bash 54 | $ pod install 55 | ``` 56 | 57 | ### Carthage 58 | 59 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. 60 | 61 | > Carthage version 0.26.2 or newer is recommended to build Pusher Beams. 62 | 63 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 64 | 65 | ```bash 66 | $ brew update 67 | $ brew install carthage 68 | ``` 69 | 70 | To integrate Pusher Beams into your Xcode project using Carthage, specify it in your `Cartfile`: 71 | 72 | ```ogdl 73 | github "pusher/push-notifications-swift" 74 | ``` 75 | 76 | Continue following the steps below depending on the platform that you're building the dependency for: 77 | 78 | - If you're building for OS X, follow [this](https://github.com/Carthage/Carthage#if-youre-building-for-os-x) guide. 79 | - If you're building for iOS, tvOS, or watchOS, follow [this](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos) guide. 80 | 81 | ### Swift Package Manager 82 | 83 | [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. 84 | 85 | 86 | #### Manual Xcode integration 87 | 88 | To integrate Pusher Beams into your Xcode project using Swift Package Manager, in your Xcode choose `File` > `Swift Packages` > `Add Package Dependency...` and provide the following URL: 89 | 90 | ``` 91 | https://github.com/pusher/push-notifications-swift 92 | ``` 93 | 94 | #### Swift Package Manager dependency 95 | 96 | To add Pusher Beams as a dependency of your own package use the follwing code: 97 | 98 | ```swift 99 | dependencies: [ 100 | .package(url: "https://github.com/pusher/push-notifications-swift.git", from: "4.0.0") 101 | ] 102 | ``` 103 | 104 | ## Migrating from 2.x.x 105 | 106 | We now require you to start beams before you can use the library, for example to register any interests. This means the following code would no longer work and log an error to the console: 107 | 108 | ```Swift 109 | try! PushNotifications.shared.addDeviceInterest("donuts") 110 | PushNotifications.shared.start("YOUR_INSTANCE_ID") 111 | ``` 112 | 113 | You now need to replace it with the following: 114 | 115 | ```Swift 116 | PushNotifications.shared.start("YOUR_INSTANCE_ID") 117 | try! PushNotifications.shared.addDeviceInterest("donuts") 118 | ``` 119 | 120 | We recommend start is always called in the `application didFinishLaunchingWithOptions` callback. Note that you can still control when you show the request for push notification prompt, start does not call this prompt. 121 | 122 | ## Running Tests 123 | 124 | ### Generating Test Coverage Reports 125 | 126 | We're using [Slather](https://github.com/SlatherOrg/slather) for generating test coverage reports locally and [Codecov](https://codecov.io/) when pull requests are submitted. 127 | 128 | ### Using Slather 129 | 130 | Create a report as static html pages by running: 131 | 132 | ```bash 133 | slather coverage --html --scheme PushNotifications --workspace PushNotifications.xcworkspace/ PushNotifications/PushNotifications.xcodeproj/ 134 | ``` 135 | 136 | Open the html reports: 137 | 138 | ```bash 139 | open 'html/index.html' 140 | ``` 141 | 142 | ## Pusher Beams Reference 143 | 144 | - Autogenerated reference [docs](https://pusher.github.io/push-notifications-swift/Classes/PushNotifications.html). 145 | - Pusher Beams iOS SDK [docs](https://docs.pusher.com/beams/reference/ios). 146 | 147 | ## Communication 148 | 149 | - Found a bug? Please open an [issue](https://github.com/pusher/push-notifications-swift/issues). 150 | - Have a feature request. Please open an [issue](https://github.com/pusher/push-notifications-swift/issues). 151 | - If you want to contribute, please submit a [pull request](https://github.com/pusher/push-notifications-swift/pulls) (preferrably with some tests). 152 | 153 | ## Credits 154 | 155 | Pusher Beams is owned and maintained by [Pusher](https://pusher.com). 156 | 157 | ## License 158 | 159 | Pusher Beams is released under the MIT license. See [LICENSE](https://github.com/pusher/push-notifications-swift/blob/master/LICENSE) for details. 160 | -------------------------------------------------------------------------------- /Scripts/generate-api-docs.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | if ! which jazzy >/dev/null; then 6 | echo "Error: Jazzy not installed, see https://github.com/realm/Jazzy or run 'gem install jazzy' to install it" 7 | exit 1 8 | fi 9 | 10 | if ! which jq >/dev/null; then 11 | echo "Error: jq not installed, run 'brew install jq' to install it" 12 | exit 1 13 | fi 14 | 15 | read -p 'Enter release tag (without quotes): ' RELEASE_TAG 16 | 17 | AUTHOR_NAME="Pusher Limited" 18 | AUTHOR_URL="https://pusher.com" 19 | GITHUB_ORIGIN=$(git remote get-url origin) 20 | GITHUB_URL=${GITHUB_ORIGIN%".git"} 21 | MODULE_NAME=$(swift package dump-package | jq --raw-output '.name') 22 | 23 | echo "Generating public API docs from release tag $RELEASE_TAG" 24 | 25 | # The 'arch -x86_64' command is redundant on Intel Macs, and runs Jazzy under Rosetta 2 on Apple Silicon Macs for compatibility 26 | arch -x86_64 jazzy \ 27 | --module $MODULE_NAME \ 28 | --module_version $RELEASE_TAG \ 29 | --swift-build-tool spm \ 30 | --author $AUTHOR_NAME \ 31 | --author_url $AUTHOR_URL \ 32 | --github_url $GITHUB_URL \ 33 | --github-file-prefix $GITHUB_URL/tree/$RELEASE_TAG -------------------------------------------------------------------------------- /Sources/Extensions/Array+CalculateMD5Hash.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Array where Element == String { 4 | func calculateMD5Hash() -> String { 5 | let sortedArray = self.sorted() 6 | let elementsJoined = String(sortedArray.joined(separator: ",")) 7 | 8 | return MD5(elementsJoined) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Extensions/Encodable+Encode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Encodable { 4 | func encode() throws -> Data { 5 | return try JSONEncoder().encode(self) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Extensions/String+HexStringToData.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | func hexStringToData() -> Data? { 5 | let len = self.count / 2 6 | var data = Data(capacity: len) 7 | for index in 0.. String { 12 | return "https://\(instanceId).\(hostName)/device_api/v1/instances/\(instanceId)/devices/apns" 13 | } 14 | 15 | private static func deviceEndpoint(instanceId: String, 16 | deviceId: String) -> String { 17 | return "\(devicesEndpoint(instanceId: instanceId))/\(deviceId)" 18 | } 19 | 20 | private static func eventEndpoint(instanceId: String) -> String { 21 | return "https://\(instanceId).\(hostName)/reporting_api/v2/instances/\(instanceId)/events" 22 | } 23 | 24 | // MARK: - Endpoint URLs 25 | 26 | static func devices(instanceId: String) -> URL? { 27 | return URL(string: devicesEndpoint(instanceId: instanceId)) 28 | } 29 | 30 | static func device(instanceId: String, 31 | deviceId: String) -> URL? { 32 | return URL(string: deviceEndpoint(instanceId: instanceId, deviceId: deviceId)) 33 | } 34 | 35 | static func interests(instanceId: String, 36 | deviceId: String) -> URL? { 37 | return URL(string: "\(deviceEndpoint(instanceId: instanceId, deviceId: deviceId))/interests") 38 | } 39 | 40 | static func interest(instanceId: String, 41 | deviceId: String, 42 | interest: String) -> URL? { 43 | return URL(string: "\(deviceEndpoint(instanceId: instanceId, deviceId: deviceId))/interests/\(interest)") 44 | } 45 | 46 | static func events(instanceId: String) -> URL? { 47 | return URL(string: eventEndpoint(instanceId: instanceId)) 48 | } 49 | 50 | static func metadata(instanceId: String, 51 | deviceId: String) -> URL? { 52 | return URL(string: "\(deviceEndpoint(instanceId: instanceId, deviceId: deviceId))/metadata") 53 | } 54 | 55 | static func user(instanceId: String, 56 | deviceId: String) -> URL? { 57 | return URL(string: "\(deviceEndpoint(instanceId: instanceId, deviceId: deviceId))/user") 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Helpers/DeviceTokenHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // https://stackoverflow.com/a/40031342 4 | extension Data { 5 | func hexadecimalRepresentation() -> String { 6 | return map { String(format: "%02.2hhx", $0) }.joined() 7 | } 8 | } 9 | 10 | // https://stackoverflow.com/a/26502285 11 | extension String { 12 | func toData() -> Data? { 13 | var data = Data(capacity: self.count / 2) 14 | 15 | guard let regex = try? NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive) else { 16 | return nil 17 | } 18 | regex.enumerateMatches(in: self, range: NSRange(location: 0, length: utf16.count)) { match, _, _ in 19 | let byteString = (self as NSString).substring(with: match!.range) 20 | var num = UInt8(byteString, radix: 16)! 21 | data.append(&num, count: 1) 22 | } 23 | 24 | guard data.count > 0 else { 25 | return nil 26 | } 27 | 28 | return data 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Models/AuthData.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Authentication data that is provided to a `TokenProvider`, such as `BeamsTokenProvider`. 4 | @objc public class AuthData: NSObject { 5 | 6 | /// The headers to attach to the `URLRequest` triggered by the `TokenProvider` when calling `fetchToken(userId:completion:)`. 7 | public let headers: [String: String] 8 | 9 | /// The query parameters to attach to the `URLRequest` triggered by the `TokenProvider` when calling `fetchToken(userId:completion:)`. 10 | public let queryParams: [String: String] 11 | 12 | /// Create an `AuthData` instance based on some `headers` and `queryParams`. 13 | /// - Parameters: 14 | /// - headers: A `Dictionary` of header key / value pairs. 15 | /// - queryParams: A `Dictionary` of query parameters key / value pairs. 16 | @objc public init(headers: [String: String], queryParams: [String: String]) { 17 | self.headers = headers 18 | self.queryParams = queryParams 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Models/Constants.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Constants { 4 | struct DispatchQueue { 5 | static let preIISOperationQueue = "com.pusher.pushnotifications.pre.iis.operation.queue" 6 | static let persistenceStorageOperationQueue = "com.pusher.pushnotifications.persistence.storage.operation.queue" 7 | } 8 | 9 | struct ReportEventType { 10 | static let open = "Open" 11 | static let delivery = "Delivery" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Models/Device.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Device: Decodable { 4 | var id: String 5 | var initialInterestSet: Set? 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Models/FeatureFlags.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct FeatureFlags { 4 | static let DeliveryTrackingEnabled = true 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Models/HTTPMethod.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum HTTPMethod: String { 4 | case DELETE 5 | case GET 6 | case POST 7 | case PUT 8 | } 9 | -------------------------------------------------------------------------------- /Sources/Models/InstanceId.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct InstanceId { 4 | let id: String 5 | 6 | init?(userInfo: [AnyHashable: Any]) { 7 | let data = userInfo["data"] as? [String: Any] 8 | let pusher = data?["pusher"] as? [String: Any] 9 | if let instanceId = pusher?["instanceId"] as? String { 10 | self.id = instanceId 11 | } else { 12 | return nil 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Models/InterestValidationError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | Error thrown by PushNotifications. 5 | 6 | *Values* 7 | 8 | `invalidName` The interest name is invalid. 9 | */ 10 | public enum InvalidInterestError: Error { 11 | /** 12 | Invalid interest name. 13 | 14 | - Parameter: interest 15 | */ 16 | case invalidName(String) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Models/Interests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Interests: Encodable { 4 | let interests: [String] 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Models/Metadata.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Metadata: Equatable, Codable { 4 | let sdkVersion: String? 5 | let iosVersion: String? 6 | let macosVersion: String? 7 | 8 | static var current: Metadata = { 9 | let sdkVersion = SDK.version 10 | let systemVersion = SystemVersion.version 11 | 12 | #if os(iOS) 13 | return Metadata(sdkVersion: sdkVersion, iosVersion: systemVersion, macosVersion: nil) 14 | #elseif os(OSX) 15 | return Metadata(sdkVersion: sdkVersion, iosVersion: nil, macosVersion: systemVersion) 16 | #endif 17 | }() 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Models/MultipleInvalidInterestsError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | Error thrown by PushNotifications. 5 | 6 | *Values* 7 | 8 | `invalidNames` The interest names are invalid. 9 | */ 10 | public enum MultipleInvalidInterestsError: Error { 11 | /** 12 | Invalid interest names. 13 | 14 | - Parameter: interests 15 | */ 16 | case invalidNames([String]) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Models/PersistenceConstants.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct PersistenceConstants { 4 | struct UserDefaults { 5 | static func suiteName(instanceId: String?) -> String { 6 | if instanceId == nil { 7 | return "PushNotifications" 8 | } else { 9 | return "PushNotifications.\(instanceId!)" 10 | } 11 | } 12 | 13 | static let globalSuiteName = "PushNotificationsInstances" 14 | static let metadataSDKVersion = "com.pusher.sdk.metadata.sdkVersion" 15 | static let metadataiOSVersion = "com.pusher.sdk.metadata.iosVersion" 16 | static let metadataMacOSVersion = "com.pusher.sdk.metadata.macosVersion" 17 | static let deviceId = "com.pusher.sdk.deviceId" 18 | static let deviceAPNsToken = "com.pusher.sdk.deviceAPNsToken" 19 | static let instanceId = "com.pusher.sdk.instanceId" 20 | } 21 | 22 | struct PersistenceService { 23 | static let instanceIdsPrefix = "com.pusher.sdk.instanceIds" 24 | static let prefix = "com.pusher.sdk.interests" 25 | static let userId = "com.pusher.sdk.user.id" 26 | static let hashKey = "interestsHash" 27 | static let globalScopeId = "com.pusher.sdk" 28 | } 29 | 30 | struct PushNotificationsInstancePersistence { 31 | static let userId = "com.pusher.sdk.pni.user.id.called.with" 32 | static let startJob = "com.pusher.sdk.pni.start.job.enqueued" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Models/PublishId.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct PublishId { 4 | let id: String? 5 | 6 | init(userInfo: [AnyHashable: Any]) { 7 | let data = userInfo["data"] as? [String: Any] 8 | let pusher = data?["pusher"] as? [String: Any] 9 | self.id = pusher?["publishId"] as? String 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Models/PushNotificationsAPIError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum PushNotificationsAPIError: Error, CustomDebugStringConvertible { 4 | case deviceNotFound 5 | case badRequest(reason: String) 6 | case badJWT(reason: String) 7 | case genericError(reason: String) 8 | case badDeviceToken(reason: String) 9 | case couldNotCreateDevice 10 | 11 | var debugDescription: String { 12 | switch self { 13 | case .deviceNotFound: 14 | return "Device Not Found" 15 | case .badRequest(let reason): 16 | return "Bad Request: \(reason)" 17 | case .badJWT(let reason): 18 | return "Bad JWT: \(reason)" 19 | case .genericError(let reason): 20 | return "Error: \(reason)" 21 | case .badDeviceToken(let reason): 22 | return "Bad Device Token: \(reason)" 23 | case .couldNotCreateDevice: 24 | return "Device could not be created" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Models/PushNotificationsError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | Error thrown by PushNotifications. 5 | 6 | *Values* 7 | 8 | `error` General error message. 9 | */ 10 | public enum PushNotificationsError: Error { 11 | /** 12 | General error. 13 | 14 | - Parameter: error message. 15 | */ 16 | case error(String) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Models/PusherAlreadyRegisteredError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | Error thrown by PushNotifications. 5 | 6 | *Values* 7 | 8 | `instanceId` The instance id was already registered. 9 | */ 10 | public enum PusherAlreadyRegisteredError: Error { 11 | /** 12 | Instance id was already registered with Pusher. 13 | 14 | - Parameter: instanceId 15 | */ 16 | case instanceId(String) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Models/Reason.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Reason: Decodable { 4 | var description: String 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Models/Register.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Register: Codable { 4 | let token: String 5 | let bundleIdentifier: String 6 | let metadata: Metadata 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Models/RemoteNotificationType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // swiftlint:disable identifier_name 4 | 5 | /** 6 | Remote Notification Type provides an option to ignore Pusher initiated related features. 7 | Whenever you receive push notification the [handleNotification(userInfo:)](https://docs.pusher.com/beams/reference/ios#handle-notification) method should be called. 8 | Sometimes, these notifications are just for Pusher SDK to handle. 9 | 10 | *Values* 11 | 12 | `ShouldIgnore` It's safe to ignore Pusher initiated notification. 13 | 14 | `ShouldProcess` Do not ignore notification as it may contain additional data. 15 | */ 16 | @objc public enum RemoteNotificationType: Int { 17 | /** 18 | Ignore Pusher initiated notification. 19 | */ 20 | case ShouldIgnore 21 | /** 22 | Do not ignore notification. 23 | */ 24 | case ShouldProcess 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Models/SDK.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct SDK { 4 | static let version = "4.0.0" 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Models/ServerSyncEvent.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum ServerSyncEvent { 4 | case interestsChangedEvent(interests: [String]) 5 | case userIdSetEvent(userId: String, error: Error?) 6 | case stopEvent 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Models/SystemVersion.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct SystemVersion { 4 | static var version: String { 5 | let operatingSystemVersion = ProcessInfo().operatingSystemVersion 6 | let majorVersion = operatingSystemVersion.majorVersion 7 | let minorVersion = operatingSystemVersion.minorVersion 8 | let patchVersion = operatingSystemVersion.patchVersion 9 | 10 | return "\(majorVersion).\(minorVersion).\(patchVersion)" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Models/Token.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Token: Decodable { 4 | var token: String 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Protocols/PropertyListReadable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol PropertyListReadable { 4 | func propertyListRepresentation() -> [String: Any] 5 | init(propertyListRepresentation: [String: Any]) 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Protocols/PushNotificationsNetworkable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | typealias CompletionHandler = (_ result: T) -> Void 4 | 5 | protocol PushNotificationsNetworkable { 6 | func register(instanceId: String, 7 | deviceToken: String, metadata: Metadata, 8 | retryStrategy: RetryStrategy) -> Result 9 | 10 | func subscribe(instanceId: String, 11 | deviceId: String, 12 | interest: String, 13 | retryStrategy: RetryStrategy) -> Result 14 | 15 | func setSubscriptions(instanceId: String, 16 | deviceId: String, 17 | interests: [String], 18 | retryStrategy: RetryStrategy) -> Result 19 | 20 | func unsubscribe(instanceId: String, 21 | deviceId: String, 22 | interest: String, 23 | retryStrategy: RetryStrategy) -> Result 24 | 25 | func track(instanceId: String, 26 | deviceId: String, 27 | eventType: ReportEventType, 28 | retryStrategy: RetryStrategy) -> Result 29 | 30 | func syncMetadata(instanceId: String, 31 | deviceId: String, 32 | metadata: Metadata, 33 | retryStrategy: RetryStrategy) -> Result 34 | 35 | func setUserId(instanceId: String, 36 | deviceId: String, 37 | token: String, 38 | retryStrategy: RetryStrategy) -> Result 39 | 40 | func deleteDevice(instanceId: String, 41 | deviceId: String, 42 | retryStrategy: RetryStrategy) -> Result 43 | 44 | func getDevice(instanceId: String, 45 | deviceId: String, 46 | retryStrategy: RetryStrategy) -> Result 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Protocols/ReportEventType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol ReportEventType: Encodable { 4 | func getInstanceId() -> String 5 | } 6 | 7 | struct OpenEventType: ReportEventType, Codable { 8 | let event: String 9 | let instanceId: String 10 | let publishId: String 11 | let deviceId: String 12 | let userId: String? 13 | let timestampSecs: UInt 14 | 15 | init(event: String = Constants.ReportEventType.open, instanceId: String, publishId: String, deviceId: String, userId: String?, timestampSecs: UInt) { 16 | self.event = event 17 | self.instanceId = instanceId 18 | self.publishId = publishId 19 | self.deviceId = deviceId 20 | self.userId = userId 21 | self.timestampSecs = timestampSecs 22 | } 23 | 24 | func getInstanceId() -> String { 25 | return self.instanceId 26 | } 27 | } 28 | 29 | struct DeliveryEventType: ReportEventType, Codable { 30 | let event: String 31 | let instanceId: String 32 | let publishId: String 33 | let deviceId: String 34 | let userId: String? 35 | let timestampSecs: UInt 36 | let appInBackground: Bool 37 | let hasDisplayableContent: Bool 38 | let hasData: Bool 39 | 40 | init(event: String = Constants.ReportEventType.delivery, instanceId: String, publishId: String, deviceId: String, userId: String?, timestampSecs: UInt, appInBackground: Bool, hasDisplayableContent: Bool, hasData: Bool) { 41 | self.event = event 42 | self.instanceId = instanceId 43 | self.publishId = publishId 44 | self.deviceId = deviceId 45 | self.userId = userId 46 | self.timestampSecs = timestampSecs 47 | self.appInBackground = appInBackground 48 | self.hasDisplayableContent = hasDisplayableContent 49 | self.hasData = hasData 50 | } 51 | 52 | func getInstanceId() -> String { 53 | return self.instanceId 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/Protocols/RetryStrategy.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol RetryStrategy { 4 | func retry(function: () -> Result) -> Result 5 | } 6 | 7 | struct JustDont: RetryStrategy { 8 | func retry(function: () -> Result) -> Result { 9 | let result = function() 10 | 11 | switch result { 12 | case .failure(let error): 13 | print("[PushNotifications]: Network error: \(error.debugDescription)") 14 | return result 15 | 16 | case .success: 17 | return result 18 | } 19 | } 20 | } 21 | 22 | class WithInfiniteExpBackoff: RetryStrategy { 23 | private var retryCount = 0 24 | 25 | func retry(function: () -> Result) -> Result { 26 | while true { 27 | let result = function() 28 | 29 | switch result { 30 | case .failure(let error): 31 | switch error { 32 | case .deviceNotFound, .badRequest, .badJWT, .badDeviceToken, .couldNotCreateDevice: 33 | // Not recoverable cases. 34 | return result 35 | 36 | case .genericError: 37 | print("[PushNotifications]: Network error: \(error.debugDescription)") 38 | self.retryCount += 1 39 | let delay = calculateExponentialBackoffMs(attemptCount: self.retryCount) 40 | Thread.sleep(forTimeInterval: TimeInterval(delay / 1000.0)) 41 | continue 42 | } 43 | 44 | case .success: 45 | return result 46 | } 47 | } 48 | } 49 | 50 | private let maxExponentialBackoffDelayMs = 64000.0 51 | private let baseExponentialBackoffDelayMs = 200.0 52 | private func calculateExponentialBackoffMs(attemptCount: Int) -> Double { 53 | return min(maxExponentialBackoffDelayMs, baseExponentialBackoffDelayMs * pow(2.0, Double(attemptCount))) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/Protocols/TokenProvider.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | TokenProviderError. 5 | 6 | *Values* 7 | 8 | `error` Token provider error message. 9 | */ 10 | public enum TokenProviderError: Error { 11 | /** 12 | Token provider error. 13 | 14 | - Parameter: Token provider error message. 15 | */ 16 | case error(String) 17 | } 18 | 19 | /** 20 | TokenProvider protocol. 21 | 22 | Conform to the TokenProvider protocol in order to generate the token for the user that you want to authenticate. 23 | */ 24 | @objc public protocol TokenProvider { 25 | /** 26 | Method `fetchToken` will return the token on success or error on failure. 27 | 28 | - Parameter userId: Id of the user that you want to generate the token for. 29 | - Parameter completion: The block to execute when operation succeeds or fails. 30 | 31 | - Precondition: `userId` should not be nil. 32 | */ 33 | func fetchToken(userId: String, completionHandler completion: @escaping (String, Error?) -> Void) throws 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Services/BeamsTokenProvider.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Used to generate tokens for users to authenticate against. 4 | @objc public final class BeamsTokenProvider: NSObject, TokenProvider { 5 | 6 | /// The authentication endpoint URL `String`. 7 | public let authURL: String 8 | 9 | /// A closure that returns an `AuthData` object. 10 | public let getAuthData: () -> AuthData 11 | 12 | /// Creates a `BeamsTokenProvider` instance. 13 | /// - Parameters: 14 | /// - authURL: The authentication endpoint URL `String`. 15 | /// - getAuthData: A closure that returns an `AuthData` object. 16 | @objc public init(authURL: String, getAuthData: @escaping () -> (AuthData)) { 17 | self.authURL = authURL 18 | self.getAuthData = getAuthData 19 | } 20 | 21 | /// Fetch a token for a given user to authenticate against. 22 | /// - Parameters: 23 | /// - userId: The user ID `String`. 24 | /// - completion: A closure containing a valid token `String` or an `Error`. 25 | public func fetchToken(userId: String, completionHandler completion: @escaping (String, Error?) -> Void) throws { 26 | let authData = getAuthData() 27 | let headers = authData.headers 28 | let queryParams = authData.queryParams 29 | 30 | let urlSession = URLSession(configuration: .ephemeral) 31 | 32 | guard var components = URLComponents(string: authURL) else { 33 | return completion("", TokenProviderError.error("URL string from the `authURL` is malformed.")) 34 | } 35 | 36 | var queryItems = queryParams.map { URLQueryItem(name: $0.key, value: $0.value) } 37 | queryItems.append(URLQueryItem(name: "user_id", value: userId)) 38 | components.queryItems = queryItems 39 | guard let url = components.url else { 40 | return completion("", TokenProviderError.error("There was a problem constructing URL from the `authURL`.")) 41 | } 42 | 43 | var urlRequest = URLRequest(url: url) 44 | urlRequest.httpMethod = "GET" 45 | for header in headers { 46 | urlRequest.addValue(header.value, forHTTPHeaderField: header.key) 47 | } 48 | 49 | urlSession.dataTask(with: urlRequest, completionHandler: { data, response, error in 50 | guard let data = data else { 51 | return completion("", TokenProviderError.error("[PushNotifications] - BeamsTokenProvider: Token is nil")) 52 | } 53 | guard let httpURLResponse = response as? HTTPURLResponse else { 54 | return completion("", TokenProviderError.error("[PushNotifications] - BeamsTokenProvider: Error while casting response object to `HTTPURLResponse`")) 55 | } 56 | let statusCode = httpURLResponse.statusCode 57 | guard statusCode >= 200 && statusCode < 300 else { 58 | return completion("", TokenProviderError.error("[PushNotifications] - BeamsTokenProvider: Received HTTP Status Code: \(statusCode)")) 59 | } 60 | guard error == nil else { 61 | return completion("", TokenProviderError.error("[PushNotifications] - BeamsTokenProvider: \(error.debugDescription)")) 62 | } 63 | 64 | guard let token = try? JSONDecoder().decode(Token.self, from: data).token else { 65 | return completion("", TokenProviderError.error("[PushNotifications] - BeamsTokenProvider: Error while parsing the token.")) 66 | } 67 | 68 | return completion(token, nil) 69 | }).resume() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/Services/EventTypeHandler.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | #endif 4 | import Foundation 5 | 6 | struct EventTypeHandler { 7 | // We have intentionally duplicated the code of the `getNotificationEventType` method in order to support Xcode 9 and Xcode 10. 8 | #if os(iOS) && swift(>=4.2) 9 | static func getNotificationEventType(userInfo: [AnyHashable: Any], applicationState: UIApplication.State) -> ReportEventType? { 10 | var eventType: ReportEventType 11 | let timestampSecs = UInt(Date().timeIntervalSince1970) 12 | let hasDisplayableContent = EventTypeHandler.hasDisplayableContent(userInfo) 13 | let hasData = EventTypeHandler.hasData(userInfo) 14 | 15 | guard 16 | let instanceId = InstanceId(userInfo: userInfo)?.id, 17 | let publishId = PublishId(userInfo: userInfo).id 18 | else { 19 | return nil 20 | } 21 | 22 | let deviceStateStore = InstanceDeviceStateStore(instanceId) 23 | guard let deviceId = deviceStateStore.getDeviceId() else { 24 | return nil 25 | } 26 | 27 | let userId = deviceStateStore.getUserId() 28 | 29 | switch applicationState { 30 | case .active: 31 | eventType = DeliveryEventType(instanceId: instanceId, publishId: publishId, deviceId: deviceId, userId: userId, timestampSecs: timestampSecs, appInBackground: false, hasDisplayableContent: hasDisplayableContent, hasData: hasData) 32 | 33 | case .background: 34 | eventType = DeliveryEventType(instanceId: instanceId, publishId: publishId, deviceId: deviceId, userId: userId, timestampSecs: timestampSecs, appInBackground: true, hasDisplayableContent: hasDisplayableContent, hasData: hasData) 35 | 36 | case .inactive: 37 | eventType = OpenEventType(instanceId: instanceId, publishId: publishId, deviceId: deviceId, userId: userId, timestampSecs: timestampSecs) 38 | } 39 | 40 | return eventType 41 | } 42 | #elseif os(iOS) && (swift(>=4.0) && !swift(>=4.2)) 43 | static func getNotificationEventType(userInfo: [AnyHashable: Any], applicationState: UIApplicationState) -> ReportEventType? { 44 | var eventType: ReportEventType 45 | let timestampSecs = UInt(Date().timeIntervalSince1970) 46 | let hasDisplayableContent = EventTypeHandler.hasDisplayableContent(userInfo) 47 | let hasData = EventTypeHandler.hasData(userInfo) 48 | 49 | guard 50 | let instanceId = InstanceId(userInfo: userInfo)?.id, 51 | let publishId = PublishId(userInfo: userInfo).id 52 | else { 53 | return nil 54 | } 55 | 56 | let deviceStateStore = InstanceDeviceStateStore(instanceId) 57 | guard let deviceId = deviceStateStore.getDeviceId() else { 58 | return nil 59 | } 60 | 61 | let userId = deviceStateStore.getUserId() 62 | 63 | switch applicationState { 64 | case .active: 65 | eventType = DeliveryEventType(instanceId: instanceId, publishId: publishId, deviceId: deviceId, userId: userId, timestampSecs: timestampSecs, appInBackground: false, hasDisplayableContent: hasDisplayableContent, hasData: hasData) 66 | 67 | case .background: 68 | eventType = DeliveryEventType(instanceId: instanceId, publishId: publishId, deviceId: deviceId, userId: userId, timestampSecs: timestampSecs, appInBackground: true, hasDisplayableContent: hasDisplayableContent, hasData: hasData) 69 | 70 | case .inactive: 71 | eventType = OpenEventType(instanceId: instanceId, publishId: publishId, deviceId: deviceId, userId: userId, timestampSecs: timestampSecs) 72 | } 73 | 74 | return eventType 75 | } 76 | #endif 77 | #if os(OSX) 78 | static func getNotificationEventType(userInfo: [AnyHashable: Any]) -> OpenEventType? { 79 | let timestampSecs = UInt(Date().timeIntervalSince1970) 80 | guard 81 | let instanceId = InstanceId(userInfo: userInfo)?.id, 82 | let publishId = PublishId(userInfo: userInfo).id 83 | else { 84 | return nil 85 | } 86 | 87 | let deviceStateStore = InstanceDeviceStateStore(instanceId) 88 | guard let deviceId = deviceStateStore.getDeviceId() else { 89 | return nil 90 | } 91 | 92 | let userId = deviceStateStore.getUserId() 93 | 94 | return OpenEventType(instanceId: instanceId, publishId: publishId, deviceId: deviceId, userId: userId, timestampSecs: timestampSecs) 95 | } 96 | #endif 97 | 98 | static func hasDisplayableContent(_ userInfo: [AnyHashable: Any]) -> Bool { 99 | guard let aps = userInfo["aps"] as? [String: Any] else { 100 | return false 101 | } 102 | 103 | return aps["alert"] != nil 104 | } 105 | 106 | // Example APNs payload: 107 | // 108 | // aps: { 109 | // alert: { 110 | // title: 'Hello', 111 | // body: 'Hello, world!' 112 | // }, 113 | // "content-available" : 1 114 | // }, 115 | // data: { 116 | // pusher: { 117 | // publishId: 'pubid-33f3f68e-b0c5-438f-b50f-fae93f6c48df' 118 | // } 119 | // } 120 | // 121 | static func hasData(_ userInfo: [AnyHashable: Any]) -> Bool { 122 | guard let data = userInfo["data"] as? [String: Any] else { 123 | return false 124 | } 125 | 126 | // `data` will always contain at least `pusher` object. 127 | // Returns `true` if there is any additional information provided. 128 | return data.count > 1 129 | } 130 | 131 | static func getRemoteNotificationType(_ userInfo: [AnyHashable: Any]) -> RemoteNotificationType { 132 | guard 133 | let data = userInfo["data"] as? [String: Any], 134 | let pusher = data["pusher"] as? [String: Any] 135 | else { 136 | return .ShouldProcess 137 | } 138 | 139 | #if os(iOS) && swift(>=4.0) 140 | let isForeground = UIApplication.shared.applicationState != .background 141 | #elseif os(OSX) 142 | let isForeground = true 143 | #endif 144 | 145 | let hasCustomerData = data.count > 1 // checks if there's anything other than the `pusher` key 146 | 147 | if hasCustomerData && isForeground { 148 | return .ShouldProcess 149 | } 150 | 151 | return pusher["userShouldIgnore"] != nil ? .ShouldIgnore : .ShouldProcess 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Sources/Services/ServerSyncEventHandler.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class ServerSyncEventHandler { 4 | 5 | static let serverSyncEventHandlersQueue = DispatchQueue(label: "com.pusher.beams.serverSyncEventHandlersQueue") 6 | static var serverSyncEventHandlers = [String: ServerSyncEventHandler]() 7 | 8 | static func obtain(instanceId: String) -> ServerSyncEventHandler { 9 | return serverSyncEventHandlersQueue.sync { 10 | if let handler = self.serverSyncEventHandlers[instanceId] { 11 | return handler 12 | } else { 13 | let handler = ServerSyncEventHandler() 14 | self.serverSyncEventHandlers[instanceId] = handler 15 | return handler 16 | } 17 | } 18 | } 19 | 20 | // used only for testing purposes 21 | static func destroy(instanceId: String) { 22 | _ = serverSyncEventHandlersQueue.sync { 23 | self.serverSyncEventHandlers.removeValue(forKey: instanceId) 24 | } 25 | } 26 | 27 | var userIdCallbacks = [String: [(Error?) -> Void]]() 28 | var stopCallbacks = [() -> Void]() 29 | 30 | private var interestsChangedDelegates = [() -> InterestsChangedDelegate?]() 31 | 32 | func handleEvent(event: ServerSyncEvent) { 33 | DispatchQueue.main.async { 34 | switch event { 35 | case .interestsChangedEvent(let interests): 36 | self.interestsChangedDelegates.forEach({ delegate in 37 | if let delegate = delegate() { 38 | delegate.interestsSetOnDeviceDidChange(interests: interests) 39 | } 40 | }) 41 | 42 | case .userIdSetEvent(let userId, let error): 43 | if !(self.userIdCallbacks[userId]?.isEmpty ?? true) { 44 | if let completion = self.userIdCallbacks[userId]?.removeFirst() { 45 | completion(error) 46 | } 47 | } 48 | 49 | case .stopEvent: 50 | if !(self.stopCallbacks.isEmpty) { 51 | let completion = self.stopCallbacks.removeFirst() 52 | completion() 53 | } 54 | } 55 | } 56 | } 57 | 58 | func registerInterestsChangedDelegate(_ interestsChangedDelegate: @escaping () -> InterestsChangedDelegate?) { 59 | self.interestsChangedDelegates.append(interestsChangedDelegate) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/Services/ServerSyncJobStore.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ServerSyncJobStore { 4 | private let instanceId: String 5 | private let syncJobStoreFileName: String 6 | private let fileManager = FileManager.default 7 | private var jobStoreArray: [ServerSyncJob] = [] 8 | private let syncJobStoreQueue = DispatchQueue(label: "com.pusher.beams.syncJobStoreQueue") 9 | 10 | init(instanceId: String) { 11 | self.instanceId = instanceId 12 | self.syncJobStoreFileName = "\(self.instanceId)-syncJobStore" 13 | 14 | self.jobStoreArray = self.loadOperations() 15 | } 16 | 17 | // https://stackoverflow.com/a/46369152 18 | private struct FailableDecodable: Decodable { 19 | let base: Base? 20 | 21 | init(from decoder: Decoder) throws { 22 | let container = try decoder.singleValueContainer() 23 | self.base = try? container.decode(Base.self) 24 | } 25 | } 26 | 27 | private func loadOperations() -> [ServerSyncJob] { 28 | guard let fileURL = try? fileManager.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false) else { 29 | return [] 30 | } 31 | 32 | let filePath = fileURL.appendingPathComponent(syncJobStoreFileName) 33 | 34 | guard let operations = NSData(contentsOfFile: filePath.relativePath) else { 35 | // Assuming a fresh installation here 36 | return [] 37 | } 38 | 39 | let jsonDecoder = JSONDecoder() 40 | guard let operationsArray = try? jsonDecoder.decode([FailableDecodable].self, from: (operations as Data)) else { 41 | print("[PushNotifications] - Failed to load previously stored operations, continuing without them.") 42 | return [] 43 | } 44 | 45 | return operationsArray.compactMap { $0.base } 46 | } 47 | 48 | private func persistOperations(_ jobStoreArray: [ServerSyncJob]) { 49 | let jsonEncoder = JSONEncoder() 50 | guard let data = try? jsonEncoder.encode(jobStoreArray) else { 51 | print("[PushNotifications] - Failed to encode operations, continuing without persisting them.") 52 | return 53 | } 54 | 55 | guard let fileURL = try? fileManager.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false) else { 56 | return 57 | } 58 | 59 | var filePath = fileURL.appendingPathComponent(syncJobStoreFileName) 60 | 61 | do { 62 | try (data as NSData).write(toFile: filePath.relativePath, options: .atomic) 63 | 64 | var resourceValues = URLResourceValues() 65 | resourceValues.isExcludedFromBackup = true 66 | try? filePath.setResourceValues(resourceValues) 67 | } catch { 68 | print("[PushNotifications] - Failed to persist operations, continuing ...") 69 | } 70 | } 71 | 72 | var isEmpty: Bool { 73 | return syncJobStoreQueue.sync { 74 | return self.jobStoreArray.isEmpty 75 | } 76 | } 77 | 78 | var first: ServerSyncJob? { 79 | return syncJobStoreQueue.sync { 80 | return jobStoreArray.first 81 | } 82 | } 83 | 84 | func toList() -> [ServerSyncJob] { 85 | return syncJobStoreQueue.sync { 86 | return self.jobStoreArray 87 | } 88 | } 89 | 90 | mutating func append(_ job: ServerSyncJob) { 91 | syncJobStoreQueue.sync { 92 | self.jobStoreArray.append(job) 93 | self.persistOperations(self.jobStoreArray) 94 | } 95 | } 96 | 97 | mutating func removeFirst() { 98 | syncJobStoreQueue.sync { 99 | if self.jobStoreArray.count > 0 { 100 | self.jobStoreArray.removeFirst() 101 | self.persistOperations(self.jobStoreArray) 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Tests/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - force_cast 3 | - line_length 4 | - type_body_length 5 | - file_length -------------------------------------------------------------------------------- /Tests/Extensions/Array+ContainsSameElements.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Array where Element: Comparable { 4 | func containsSameElements(as other: [Element]) -> Bool { 5 | return self.count == other.count && self.sorted() == other.sorted() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/Integration/ApplicationStartTests.swift: -------------------------------------------------------------------------------- 1 | import Nimble 2 | @testable import PushNotifications 3 | import XCTest 4 | 5 | class ApplicationStartTests: XCTestCase { 6 | // Real production instance. 7 | private let instanceId = "1b880590-6301-4bb5-b34f-45db1c5f5644" 8 | private let validToken = "notadevicetoken-apns-ApplicationStartTests".data(using: .utf8)! 9 | 10 | override func setUp() { 11 | super.setUp() 12 | TestHelper.clearEverything(instanceId: instanceId) 13 | } 14 | 15 | override func tearDown() { 16 | TestHelper.clearEverything(instanceId: instanceId) 17 | super.tearDown() 18 | } 19 | 20 | func testApplicationStartWillSyncInterests() { 21 | let pushNotifications = PushNotifications(instanceId: instanceId) 22 | pushNotifications.start() 23 | 24 | pushNotifications.registerDeviceToken(validToken) 25 | 26 | let deviceStateStore = InstanceDeviceStateStore(self.instanceId) 27 | expect(deviceStateStore.getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 28 | let deviceId = deviceStateStore.getDeviceId()! 29 | 30 | expect(TestAPIClientHelper().getDeviceInterests(instanceId: self.instanceId, deviceId: deviceId)) 31 | .toEventually(equal([]), timeout: .seconds(10)) 32 | 33 | _ = InstanceDeviceStateStore(self.instanceId).persistInterests(["cucas", "panda", "potato"]) 34 | pushNotifications.start() 35 | 36 | expect(TestAPIClientHelper().getDeviceInterests(instanceId: self.instanceId, deviceId: deviceId)) 37 | .toEventually(contain("cucas", "panda", "potato"), timeout: .seconds(10)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/Integration/ClearAllStateTest.swift: -------------------------------------------------------------------------------- 1 | import Nimble 2 | @testable import PushNotifications 3 | import XCTest 4 | 5 | class ClearAllStateTest: XCTestCase { 6 | // Real production instance. 7 | private let instanceId = "1b880590-6301-4bb5-b34f-45db1c5f5644" 8 | private let validToken = "notadevicetoken-apns-ClearAllStateTest".data(using: .utf8)! 9 | 10 | override func setUp() { 11 | super.setUp() 12 | TestHelper.clearEverything(instanceId: instanceId) 13 | } 14 | 15 | override func tearDown() { 16 | TestHelper.clearEverything(instanceId: instanceId) 17 | super.tearDown() 18 | } 19 | 20 | func testItShouldClearAllState() { 21 | let pushNotifications = PushNotifications(instanceId: instanceId) 22 | pushNotifications.start() 23 | pushNotifications.registerDeviceToken(validToken) 24 | 25 | let deviceStateStore = InstanceDeviceStateStore(self.instanceId) 26 | expect(deviceStateStore.getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 27 | let deviceId = deviceStateStore.getDeviceId() 28 | 29 | XCTAssertNoThrow(try pushNotifications.addDeviceInterest(interest: "a")) 30 | XCTAssertEqual(pushNotifications.getDeviceInterests(), ["a"]) 31 | let exp = expectation(description: "Clear all state completion handler must be called") 32 | pushNotifications.clearAllState { 33 | exp.fulfill() 34 | } 35 | 36 | expect(TestAPIClientHelper().getDevice(instanceId: self.instanceId, deviceId: deviceId!)) 37 | .toEventually(beNil(), timeout: .seconds(10)) 38 | 39 | XCTAssertEqual(pushNotifications.getDeviceInterests(), []) 40 | expect(InstanceDeviceStateStore(self.instanceId).getDeviceId()).toEventuallyNot(equal(deviceId!), timeout: .seconds(10)) 41 | 42 | waitForExpectations(timeout: 1) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/Integration/DeviceRegistrationTests.swift: -------------------------------------------------------------------------------- 1 | import Nimble 2 | @testable import PushNotifications 3 | import XCTest 4 | 5 | class DeviceRegistrationTests: XCTestCase { 6 | // Real production instance. 7 | private let instanceId = "1b880590-6301-4bb5-b34f-45db1c5f5644" 8 | private let validToken = "notadevicetoken-apns-DeviceRegistrationTests".data(using: .utf8)! 9 | 10 | override func setUp() { 11 | super.setUp() 12 | TestHelper.clearEverything(instanceId: instanceId) 13 | } 14 | 15 | override func tearDown() { 16 | TestHelper.clearEverything(instanceId: instanceId) 17 | super.tearDown() 18 | } 19 | 20 | func testStartRegisterDeviceTokenResultsInDeviceIdBeingStored() { 21 | let pushNotifications = PushNotifications(instanceId: instanceId) 22 | pushNotifications.start() 23 | 24 | pushNotifications.registerDeviceToken(validToken) 25 | 26 | expect(InstanceDeviceStateStore(self.instanceId).getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/Integration/MultipleClassInstanceSupportTest.swift: -------------------------------------------------------------------------------- 1 | import Nimble 2 | @testable import PushNotifications 3 | import XCTest 4 | 5 | class MultipleClassInstanceSupportTest: XCTestCase { 6 | private let validToken = "notadevicetoken-apns-MultipleClassInstanceSupportTest".data(using: .utf8)! 7 | private let deviceStateStore = InstanceDeviceStateStore(TestHelper.instanceId) 8 | private let validCucasJWTToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjQ3MDc5OTIzMDIsImlzcyI6Imh0dHBzOi8vMWI4ODA1OTAtNjMwMS00YmI1LWIzNGYtNDVkYjFjNWY1NjQ0LnB1c2hub3RpZmljYXRpb25zLnB1c2hlci5jb20iLCJzdWIiOiJjdWNhcyJ9.CTtrDXh7vae3rSSKBKf5X0y4RQpFg7YvIlirmBQqJn4" 9 | 10 | override func setUp() { 11 | super.setUp() 12 | TestHelper.clearEverything(instanceId: TestHelper.instanceId) 13 | } 14 | 15 | override func tearDown() { 16 | TestHelper.clearEverything(instanceId: TestHelper.instanceId) 17 | super.tearDown() 18 | } 19 | 20 | func testStopCallbacksShouldCallOnTheRightCallback() { 21 | let pushNotifications1 = PushNotifications(instanceId: TestHelper.instanceId) 22 | let pushNotifications2 = PushNotifications(instanceId: TestHelper.instanceId) 23 | 24 | pushNotifications1.start() 25 | pushNotifications1.registerDeviceToken(validToken) 26 | 27 | expect(self.deviceStateStore.getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 28 | let deviceId = self.deviceStateStore.getDeviceId()! 29 | 30 | let exp = expectation(description: "Stop completion handler must be called") 31 | pushNotifications2.stop { 32 | exp.fulfill() 33 | } 34 | 35 | expect(TestAPIClientHelper().getDevice(instanceId: TestHelper.instanceId, deviceId: deviceId)) 36 | .toEventually(beNil(), timeout: .seconds(10)) 37 | 38 | waitForExpectations(timeout: 1) 39 | } 40 | 41 | func testSetUserIdsCalledOnCorrectCallback () { 42 | let pushNotifications1 = PushNotifications(instanceId: TestHelper.instanceId) 43 | let pushNotifications2 = PushNotifications(instanceId: TestHelper.instanceId) 44 | 45 | pushNotifications1.start() 46 | pushNotifications2.start() 47 | pushNotifications1.registerDeviceToken(validToken) 48 | 49 | let tokenProvider = StubTokenProvider(jwt: validCucasJWTToken, error: nil) 50 | let exp = expectation(description: "It should not return an error") 51 | pushNotifications2.setUserId("cucas", tokenProvider: tokenProvider) { error in 52 | XCTAssertNil(error) 53 | exp.fulfill() 54 | } 55 | 56 | waitForExpectations(timeout: 10) 57 | } 58 | 59 | func testInterestsChangedDelegateCalledOnCorrectCallback() throws { 60 | let pushNotifications1 = PushNotifications(instanceId: TestHelper.instanceId) 61 | let pushNotifications2 = PushNotifications(instanceId: TestHelper.instanceId) 62 | 63 | class StubInterestsChanged: InterestsChangedDelegate { 64 | let completion: ([String]) -> Void 65 | init(completion: @escaping ([String]) -> Void) { 66 | self.completion = completion 67 | } 68 | 69 | func interestsSetOnDeviceDidChange(interests: [String]) { 70 | completion(interests) 71 | } 72 | } 73 | let exp = expectation(description: "Interests changed called with ['panda']") 74 | let stubInterestsChanged = StubInterestsChanged(completion: { interests in 75 | XCTAssertTrue(interests.containsSameElements(as: ["panda"])) 76 | exp.fulfill() 77 | }) 78 | pushNotifications1.delegate = stubInterestsChanged 79 | 80 | XCTAssertNoThrow(try pushNotifications2.addDeviceInterest(interest: "panda")) 81 | 82 | waitForExpectations(timeout: 1) 83 | } 84 | 85 | func testClearAllOnSameInstanceWorks() { 86 | let pushNotifications1 = PushNotifications(instanceId: TestHelper.instanceId) 87 | let pushNotifications2 = PushNotifications(instanceId: TestHelper.instanceId) 88 | 89 | pushNotifications1.start() 90 | pushNotifications1.registerDeviceToken(validToken) 91 | 92 | expect(self.deviceStateStore.getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 93 | let deviceId = self.deviceStateStore.getDeviceId()! 94 | 95 | pushNotifications2.clearAllState { } 96 | 97 | expect(self.deviceStateStore.getDeviceId()).toEventuallyNot(be(deviceId), timeout: .seconds(10)) 98 | } 99 | 100 | private class StubTokenProvider: TokenProvider { 101 | private let jwt: String 102 | private let error: Error? 103 | 104 | init(jwt: String, error: Error?) { 105 | self.jwt = jwt 106 | self.error = error 107 | } 108 | 109 | func fetchToken(userId: String, completionHandler completion: @escaping (String, Error?) -> Void) throws { 110 | completion(jwt, error) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Tests/Integration/ReportEventTypeTests.swift: -------------------------------------------------------------------------------- 1 | import Nimble 2 | @testable import PushNotifications 3 | import XCTest 4 | 5 | class ReportEventTypeTests: XCTestCase { 6 | // Real production instance. 7 | private let instanceId = "1b880590-6301-4bb5-b34f-45db1c5f5644" 8 | private let validToken = "notadevicetoken-apns-ReportEventTypeTests".data(using: .utf8)! 9 | 10 | override func setUp() { 11 | super.setUp() 12 | TestHelper.clearEverything(instanceId: instanceId) 13 | } 14 | 15 | override func tearDown() { 16 | TestHelper.clearEverything(instanceId: instanceId) 17 | super.tearDown() 18 | } 19 | 20 | func testHandleNotification() { 21 | let pushNotifications = PushNotifications(instanceId: instanceId) 22 | pushNotifications.start() 23 | 24 | pushNotifications.registerDeviceToken(validToken) 25 | 26 | expect(InstanceDeviceStateStore(self.instanceId).getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 27 | 28 | let userInfo = ["aps": ["alert": ["title": "Hello", "body": "Hello, world!"], "content-available": 1], "data": ["pusher": ["publishId": "pubid-33f3f68e-b0c5-438f-b50f-fae93f6c48df"]]] 29 | 30 | let eventType = pushNotifications.handleNotification(userInfo: userInfo) 31 | XCTAssertEqual(eventType, .ShouldProcess) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/Integration/SetUserIdTest.swift: -------------------------------------------------------------------------------- 1 | import Nimble 2 | @testable import PushNotifications 3 | import XCTest 4 | 5 | class SetUserIdTest: XCTestCase { 6 | // Real production instance. 7 | private let instanceId = "1b880590-6301-4bb5-b34f-45db1c5f5644" 8 | private let validToken = "notadevicetoken-apns-SetUserIdTest".data(using: .utf8)! 9 | private let validCucasJWTToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjQ3MDc5OTIzMDIsImlzcyI6Imh0dHBzOi8vMWI4ODA1OTAtNjMwMS00YmI1LWIzNGYtNDVkYjFjNWY1NjQ0LnB1c2hub3RpZmljYXRpb25zLnB1c2hlci5jb20iLCJzdWIiOiJjdWNhcyJ9.CTtrDXh7vae3rSSKBKf5X0y4RQpFg7YvIlirmBQqJn4" 10 | 11 | override func setUp() { 12 | super.setUp() 13 | TestHelper.clearEverything(instanceId: instanceId) 14 | } 15 | 16 | override func tearDown() { 17 | TestHelper.clearEverything(instanceId: instanceId) 18 | super.tearDown() 19 | } 20 | 21 | func testSetUserIdShouldAssociateThisDeviceWithUserOnTheServer() { 22 | let pushNotifications = PushNotifications(instanceId: instanceId) 23 | pushNotifications.start() 24 | pushNotifications.registerDeviceToken(validToken) 25 | 26 | expect(InstanceDeviceStateStore(self.instanceId).getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 27 | let deviceId = InstanceDeviceStateStore(self.instanceId).getDeviceId()! 28 | 29 | let tokenProvider = StubTokenProvider(jwt: validCucasJWTToken, error: nil) 30 | pushNotifications.setUserId("cucas", tokenProvider: tokenProvider) { _ in } 31 | 32 | expect(TestAPIClientHelper().getDevice(instanceId: self.instanceId, deviceId: deviceId)?.userId) 33 | .toEventually(equal("cucas"), timeout: .seconds(30)) 34 | } 35 | 36 | func testSetUserIdShouldThrowExceptionIfUserIdIsReassigned() { 37 | let pushNotifications = PushNotifications(instanceId: instanceId) 38 | pushNotifications.start() 39 | pushNotifications.registerDeviceToken(validToken) 40 | let expCucas = expectation(description: "Set user id for cucas should succeed") 41 | let expPotato = expectation(description: "Set user id for potato should fail") 42 | 43 | let tokenProvider = StubTokenProvider(jwt: validCucasJWTToken, error: nil) 44 | pushNotifications.setUserId("cucas", tokenProvider: tokenProvider) { error in 45 | XCTAssertNil(error) 46 | expCucas.fulfill() 47 | } 48 | 49 | pushNotifications.setUserId("potato", tokenProvider: tokenProvider) { error in 50 | XCTAssertNotNil(error) 51 | expPotato.fulfill() 52 | } 53 | 54 | waitForExpectations(timeout: 10) 55 | } 56 | 57 | func testSetUserIdCallStopAndSettingADifferentUserIdSucceeds() { 58 | let pushNotifications = PushNotifications(instanceId: instanceId) 59 | pushNotifications.start() 60 | pushNotifications.registerDeviceToken(validToken) 61 | 62 | let tokenProvider = StubTokenProvider(jwt: validCucasJWTToken, error: nil) 63 | pushNotifications.setUserId("cucas", tokenProvider: tokenProvider) { error in 64 | XCTAssertNil(error) 65 | } 66 | 67 | pushNotifications.clearAllState { } 68 | 69 | let exp = expectation(description: "It should not return an error") 70 | pushNotifications.setUserId("potato", tokenProvider: tokenProvider) { error in 71 | XCTAssertNil(error) 72 | exp.fulfill() 73 | } 74 | 75 | waitForExpectations(timeout: 10) 76 | } 77 | 78 | func testSetUserIdShouldReturnErrorIfStartHasNotBeenCalled() { 79 | let pushNotifications = PushNotifications(instanceId: instanceId) 80 | let exp = expectation(description: "It should return an error") 81 | let tokenProvider = StubTokenProvider(jwt: validCucasJWTToken, error: nil) 82 | pushNotifications.setUserId("cucas", tokenProvider: tokenProvider) { error in 83 | XCTAssertNotNil(error) 84 | exp.fulfill() 85 | } 86 | 87 | waitForExpectations(timeout: 1) 88 | } 89 | 90 | private class StubTokenProvider: TokenProvider { 91 | private let jwt: String 92 | private let error: Error? 93 | 94 | init(jwt: String, error: Error?) { 95 | self.jwt = jwt 96 | self.error = error 97 | } 98 | 99 | func fetchToken(userId: String, completionHandler completion: @escaping (String, Error?) -> Void) throws { 100 | completion(jwt, error) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Tests/Integration/StopTests.swift: -------------------------------------------------------------------------------- 1 | import Nimble 2 | @testable import PushNotifications 3 | import XCTest 4 | 5 | class StopTests: XCTestCase { 6 | // Real production instance. 7 | private let instanceId = "1b880590-6301-4bb5-b34f-45db1c5f5644" 8 | private let validToken = "notadevicetoken-apns-StopTests".data(using: .utf8)! 9 | private let deviceStateStore = InstanceDeviceStateStore("1b880590-6301-4bb5-b34f-45db1c5f5644") 10 | 11 | override func setUp() { 12 | super.setUp() 13 | TestHelper.clearEverything(instanceId: instanceId) 14 | } 15 | 16 | override func tearDown() { 17 | TestHelper.clearEverything(instanceId: instanceId) 18 | super.tearDown() 19 | } 20 | 21 | func testStopShouldDeleteDeviceOnTheServer() { 22 | let pushNotifications = PushNotifications(instanceId: instanceId) 23 | pushNotifications.start() 24 | pushNotifications.registerDeviceToken(validToken) 25 | 26 | expect(self.deviceStateStore.getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 27 | let deviceId = self.deviceStateStore.getDeviceId()! 28 | 29 | let exp = expectation(description: "Stop completion handler must be called") 30 | pushNotifications.stop { 31 | exp.fulfill() 32 | } 33 | 34 | expect(TestAPIClientHelper().getDevice(instanceId: self.instanceId, deviceId: deviceId)) 35 | .toEventually(beNil(), timeout: .seconds(10)) 36 | 37 | waitForExpectations(timeout: 1) 38 | } 39 | 40 | func testShouldDeleteLocalInterests() { 41 | let pushNotifications = PushNotifications(instanceId: instanceId) 42 | pushNotifications.start() 43 | pushNotifications.registerDeviceToken(validToken) 44 | 45 | expect(self.deviceStateStore.getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 46 | 47 | pushNotifications.stop { } 48 | 49 | XCTAssertEqual(pushNotifications.getDeviceInterests(), []) 50 | } 51 | 52 | func testAfterStopStartingAgainShouldBePossible() { 53 | let pushNotifications = PushNotifications(instanceId: instanceId) 54 | pushNotifications.start() 55 | pushNotifications.registerDeviceToken(validToken) 56 | 57 | expect(self.deviceStateStore.getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 58 | 59 | pushNotifications.stop { } 60 | 61 | expect(self.deviceStateStore.getDeviceId()).toEventually(beNil(), timeout: .seconds(10)) 62 | 63 | pushNotifications.start() 64 | pushNotifications.registerDeviceToken(validToken) 65 | 66 | expect(self.deviceStateStore.getDeviceId()).toEventuallyNot(beNil(), timeout: .seconds(10)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tests/Integration/TestAPIClientHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import PushNotifications 3 | 4 | struct Interests: Codable { 5 | let interests: [String] 6 | } 7 | 8 | struct TestDevice: Codable { 9 | let id: String 10 | let userId: String 11 | let metadata: Metadata 12 | } 13 | 14 | struct TestAPIClientHelper { 15 | func getDeviceInterests(instanceId: String, deviceId: String) -> [String]? { 16 | let session = URLSession(configuration: .ephemeral) 17 | let semaphore = DispatchSemaphore(value: 0) 18 | var interests: Interests? 19 | 20 | let request = setRequest(url: URL.PushNotifications.interests(instanceId: instanceId, 21 | deviceId: deviceId)!, 22 | httpMethod: .GET) 23 | session.dataTask(with: request) { data, _, _ in 24 | interests = try? JSONDecoder().decode(Interests.self, from: data!) 25 | semaphore.signal() 26 | }.resume() 27 | 28 | semaphore.wait() 29 | return interests?.interests 30 | } 31 | 32 | func deleteDevice(instanceId: String, deviceId: String) { 33 | let session = URLSession(configuration: .ephemeral) 34 | let semaphore = DispatchSemaphore(value: 0) 35 | 36 | let request = setRequest(url: URL.PushNotifications.device(instanceId: instanceId, 37 | deviceId: deviceId)!, 38 | httpMethod: .DELETE) 39 | session.dataTask(with: request) { _, _, _ in 40 | semaphore.signal() 41 | }.resume() 42 | 43 | semaphore.wait() 44 | } 45 | 46 | func getDevice(instanceId: String, deviceId: String) -> TestDevice? { 47 | let session = URLSession(configuration: .ephemeral) 48 | let semaphore = DispatchSemaphore(value: 0) 49 | var device: TestDevice? 50 | 51 | let request = setRequest(url: URL.PushNotifications.device(instanceId: instanceId, 52 | deviceId: deviceId)!, 53 | httpMethod: .GET) 54 | session.dataTask(with: request) { data, _, _ in 55 | device = try? JSONDecoder().decode(TestDevice.self, from: data!) 56 | semaphore.signal() 57 | }.resume() 58 | 59 | semaphore.wait() 60 | return device 61 | } 62 | 63 | func setRequest(url: URL, httpMethod: HTTPMethod, body: Data? = nil) -> URLRequest { 64 | var request = URLRequest(url: url) 65 | request.addValue("application/json", forHTTPHeaderField: "Content-Type") 66 | request.httpMethod = httpMethod.rawValue 67 | request.httpBody = body 68 | 69 | return request 70 | } 71 | 72 | enum HTTPMethod: String { 73 | case DELETE 74 | case GET 75 | case POST 76 | case PUT 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/Integration/TestHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import PushNotifications 3 | 4 | struct TestHelper { 5 | 6 | static let instanceId = "1b880590-6301-4bb5-b34f-45db1c5f5644" 7 | static let instanceId2 = "8ba76dac-b2de-472f-bcf2-74cca438ea13" 8 | 9 | static func clearEverything(instanceId: String) { 10 | if let deviceId = InstanceDeviceStateStore(instanceId).getDeviceId() { 11 | TestAPIClientHelper().deleteDevice(instanceId: instanceId, deviceId: deviceId) 12 | } 13 | 14 | // swiftlint:disable:next force_try 15 | let url = try! FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false) 16 | let filePath = url.appendingPathComponent("\(instanceId)-syncJobStore") 17 | try? FileManager.default.removeItem(atPath: filePath.relativePath) 18 | 19 | InstanceDeviceStateStore(instanceId).clear() 20 | DeviceStateStore().removeAllInstanceIds() 21 | ServerSyncProcessHandler.destroy(instanceId: instanceId) 22 | ServerSyncEventHandler.destroy(instanceId: instanceId) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/Unit/Extensions/ArrayContainsSameElementsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class ArrayContainsSameElementsTests: XCTestCase { 5 | func testArrayContainsSameElements1() { 6 | let arrayOne = ["a", "b", "c", "d", "e"] 7 | let arrayTwo = ["b", "d", "a", "e", "c"] 8 | XCTAssertTrue(arrayOne.containsSameElements(as: arrayTwo)) 9 | } 10 | 11 | func testArrayContainsSameElements2() { 12 | let arrayOne = ["a", "b", "c", "d"] 13 | let arrayTwo = ["b", "d", "a", "e", "c"] 14 | XCTAssertFalse(arrayOne.containsSameElements(as: arrayTwo)) 15 | } 16 | 17 | func testArrayContainsSameElements3() { 18 | let arrayOne = ["a", "b", "c", "d", "2"] 19 | let arrayTwo = ["b", "d", "a", "e", "c"] 20 | XCTAssertFalse(arrayOne.containsSameElements(as: arrayTwo)) 21 | } 22 | 23 | func testArrayContainsSameElements4() { 24 | let arrayOne = ["1", "1"] 25 | let arrayTwo = ["1", "2"] 26 | XCTAssertFalse(arrayOne.containsSameElements(as: arrayTwo)) 27 | } 28 | 29 | func testArrayContainsSameElements5() { 30 | let arrayOne = ["-", "adffevs", "2332", ""] 31 | let arrayTwo = ["adffevs", "", "", "2332"] 32 | XCTAssertFalse(arrayOne.containsSameElements(as: arrayTwo)) 33 | } 34 | 35 | func testArrayContainsSameElements6() { 36 | let arrayOne = ["com.pusher.sdk:123", "com.pusher.sdk:12321", "123"] 37 | let arrayTwo = ["123", "com.pusher.sdk:123", "com.pusher.sdk:12321"] 38 | XCTAssertTrue(arrayOne.containsSameElements(as: arrayTwo)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/Unit/Helpers/DeviceTokenHelperTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class DeviceTokenHelperTests: XCTestCase { 5 | 6 | func testConvertToString() { 7 | let deviceTokenString = "551c547e8dba7f13b69b00cead88e9c2adcc0a68a8659214d03de71f4b9357e2" 8 | let deviceTokenData = deviceTokenString.toData()! // Convert to `Data` from hexadecimal representation. 9 | let toDeviceTokenString = deviceTokenData.hexadecimalRepresentation() // Convert back to hexadecimal representation. 10 | XCTAssert(deviceTokenString == toDeviceTokenString) 11 | } 12 | 13 | func testConversionToDataIsNil() { 14 | let deviceTokenString = "" 15 | XCTAssert(deviceTokenString.toData() == nil) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/Unit/Helpers/InterestsMD5HashTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class InterestsMD5HashTests: XCTestCase { 5 | func testCalculatedHashIsCorrect() { 6 | let interestsMD5Hash1 = ["a", "b", "c", "d", "e"].calculateMD5Hash() 7 | XCTAssertEqual(interestsMD5Hash1, "FB2AE5DB06EFD8297195270BDC4FB60B") 8 | 9 | let interestsMD5Hash2 = ["vegan-pizza", "donuts", "zzz", "aaa", "a"].calculateMD5Hash() 10 | XCTAssertEqual(interestsMD5Hash2, "749E2830366BA863F796CDF5E281662F") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/Unit/Models/AuthDataTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class AuthDataTests: XCTestCase { 5 | 6 | private var authData: AuthData! 7 | 8 | override func setUp() { 9 | super.setUp() 10 | self.authData = AuthData(headers: ["A": "B"], queryParams: ["1": "2"]) 11 | } 12 | 13 | override func tearDown() { 14 | self.authData = nil 15 | super.tearDown() 16 | } 17 | 18 | func testAuthData() { 19 | XCTAssertNotNil(self.authData) 20 | XCTAssertEqual(self.authData.headers, ["A": "B"]) 21 | XCTAssertEqual(self.authData.queryParams, ["1": "2"]) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/Unit/Models/ConstantsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class ConstantsTests: XCTestCase { 5 | func testUserDefaultsConstants() { 6 | XCTAssertEqual(PersistenceConstants.UserDefaults.suiteName(instanceId: nil), "PushNotifications") 7 | XCTAssertEqual(PersistenceConstants.UserDefaults.suiteName( 8 | instanceId: "1b880590-6301-4bb5-b34f-45db1c5f5644"), 9 | "PushNotifications.1b880590-6301-4bb5-b34f-45db1c5f5644") 10 | XCTAssertEqual(PersistenceConstants.UserDefaults.metadataSDKVersion, "com.pusher.sdk.metadata.sdkVersion") 11 | XCTAssertEqual(PersistenceConstants.UserDefaults.metadataiOSVersion, "com.pusher.sdk.metadata.iosVersion") 12 | XCTAssertEqual(PersistenceConstants.UserDefaults.metadataMacOSVersion, "com.pusher.sdk.metadata.macosVersion") 13 | XCTAssertEqual(PersistenceConstants.UserDefaults.deviceId, "com.pusher.sdk.deviceId") 14 | XCTAssertEqual(PersistenceConstants.UserDefaults.instanceId, "com.pusher.sdk.instanceId") 15 | } 16 | 17 | func testPersistanceServiceConstants() { 18 | XCTAssertEqual(PersistenceConstants.PersistenceService.prefix, "com.pusher.sdk.interests") 19 | XCTAssertEqual(PersistenceConstants.PersistenceService.hashKey, "interestsHash") 20 | } 21 | 22 | func testDispatchQueueConstants() { 23 | XCTAssertEqual(Constants.DispatchQueue.preIISOperationQueue, "com.pusher.pushnotifications.pre.iis.operation.queue") 24 | XCTAssertEqual(Constants.DispatchQueue.persistenceStorageOperationQueue, "com.pusher.pushnotifications.persistence.storage.operation.queue") 25 | } 26 | 27 | func testReportEventTypeConstants() { 28 | XCTAssertEqual(Constants.ReportEventType.open, "Open") 29 | XCTAssertEqual(Constants.ReportEventType.delivery, "Delivery") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/Unit/Models/FeatureFlagsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class FeatureFlagsTests: XCTestCase { 5 | func testFeatureFlagDeliveryTrackingEnabledIsSetToTrue() { 6 | XCTAssertEqual(FeatureFlags.DeliveryTrackingEnabled, true) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/Unit/Models/InterestsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class InterestsTests: XCTestCase { 5 | private let interests = Interests(interests: ["a", "b", "c"]) 6 | 7 | func testContainsInterests() { 8 | XCTAssertNotNil(self.interests) 9 | } 10 | 11 | func testInterestsEncoded() throws { 12 | let interestskEncoded = try self.interests.encode() 13 | XCTAssertNotNil(interestskEncoded) 14 | let interestsJSONExpected = "{\"interests\":[\"a\",\"b\",\"c\"]}" 15 | let interestsJSON = String(data: interestskEncoded, encoding: .utf8)! 16 | XCTAssertEqual(interestsJSONExpected, interestsJSON) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/Unit/Models/PublishIdTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class PublishIdTests: XCTestCase { 5 | private var userInfo: [AnyHashable: Any]! 6 | 7 | override func setUp() { 8 | self.userInfo = self.constructUserInfo() 9 | super.setUp() 10 | } 11 | 12 | override func tearDown() { 13 | self.userInfo = nil 14 | super.tearDown() 15 | } 16 | 17 | func testReturnsId() { 18 | let parsedId = PublishId(userInfo: self.userInfo).id 19 | XCTAssertNotNil(parsedId) 20 | XCTAssertEqual(parsedId, "123") 21 | } 22 | 23 | private func constructUserInfo() -> [AnyHashable: Any] { 24 | let publishId = ["publishId": "123"] 25 | let pusher = ["pusher": publishId] 26 | let data = ["data": pusher] 27 | 28 | return data 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/Unit/Models/ReasonTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class ReasonTests: XCTestCase { 5 | func testReasonModel() { 6 | let reason = Reason(description: "abc") 7 | XCTAssertNotNil(reason) 8 | XCTAssertEqual(reason.description, "abc") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Tests/Unit/Models/RegisterTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class RegisterTests: XCTestCase { 5 | #if os(iOS) 6 | private let register = Register(token: "123", bundleIdentifier: "com.pusher", metadata: Metadata(sdkVersion: "0.4.0", iosVersion: "11.2.0", macosVersion: nil)) 7 | #elseif os(OSX) 8 | private let register = Register(token: "123", bundleIdentifier: "com.pusher", metadata: Metadata(sdkVersion: "0.4.0", iosVersion: nil, macosVersion: "10.9")) 9 | #endif 10 | 11 | #if os(iOS) 12 | func testRegisterModel() { 13 | let register = self.register 14 | XCTAssertNotNil(register) 15 | XCTAssertEqual(register.token, "123") 16 | XCTAssertEqual(register.bundleIdentifier, "com.pusher") 17 | XCTAssertEqual(register.metadata.sdkVersion, "0.4.0") 18 | XCTAssertEqual(register.metadata.iosVersion, "11.2.0") 19 | XCTAssertEqual(register.metadata.macosVersion, nil) 20 | } 21 | 22 | func testRegisterEncoded() throws { 23 | let registerEncoded = try self.register.encode() 24 | XCTAssertNotNil(registerEncoded) 25 | let registerJSON = String(data: registerEncoded, encoding: .utf8)! 26 | 27 | let registerDecoded = try JSONDecoder().decode(Register.self, from: registerJSON.data(using: .utf8)!) 28 | 29 | XCTAssertNotNil(registerDecoded) 30 | XCTAssertEqual(registerDecoded.token, "123") 31 | XCTAssertEqual(registerDecoded.bundleIdentifier, "com.pusher") 32 | XCTAssertEqual(registerDecoded.metadata.sdkVersion, "0.4.0") 33 | XCTAssertEqual(registerDecoded.metadata.iosVersion, "11.2.0") 34 | XCTAssertEqual(registerDecoded.metadata.macosVersion, nil) 35 | } 36 | #elseif os(OSX) 37 | func testRegisterModel() { 38 | let register = self.register 39 | XCTAssertNotNil(register) 40 | XCTAssertEqual(register.token, "123") 41 | XCTAssertEqual(register.bundleIdentifier, "com.pusher") 42 | XCTAssertEqual(register.metadata.sdkVersion, "0.4.0") 43 | XCTAssertEqual(register.metadata.iosVersion, nil) 44 | XCTAssertEqual(register.metadata.macosVersion, "10.9") 45 | } 46 | 47 | func testRegisterEncoded() throws { 48 | let registerEncoded = try self.register.encode() 49 | XCTAssertNotNil(registerEncoded) 50 | let registerJSON = String(data: registerEncoded, encoding: .utf8)! 51 | 52 | let registerDecoded = try JSONDecoder().decode(Register.self, from: registerJSON.data(using: .utf8)!) 53 | 54 | XCTAssertNotNil(registerDecoded) 55 | XCTAssertEqual(registerDecoded.token, "123") 56 | XCTAssertEqual(registerDecoded.bundleIdentifier, "com.pusher") 57 | XCTAssertEqual(registerDecoded.metadata.sdkVersion, "0.4.0") 58 | XCTAssertEqual(registerDecoded.metadata.iosVersion, nil) 59 | XCTAssertEqual(registerDecoded.metadata.macosVersion, "10.9") 60 | } 61 | #endif 62 | } 63 | -------------------------------------------------------------------------------- /Tests/Unit/Models/SDKTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class SDKTests: XCTestCase { 5 | func testVersionModel() { 6 | let version = SDK.version 7 | XCTAssertNotNil(version) 8 | XCTAssertEqual(version, "4.0.0") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Tests/Unit/Models/SystemVersionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class SystemVersionTests: XCTestCase { 5 | func testSystemVersion() { 6 | XCTAssertNotNil(SystemVersion.version) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/Unit/Services/BeamsTokenProviderTests.swift: -------------------------------------------------------------------------------- 1 | import OHHTTPStubs 2 | import XCTest 3 | #if canImport(OHHTTPStubsSwift) 4 | import OHHTTPStubsSwift 5 | #endif 6 | @testable import PushNotifications 7 | 8 | class BeamsTokenProviderTests: XCTestCase { 9 | 10 | private let instanceId = "8a070eaa-033f-46d6-bb90-f4c15acc47e1" 11 | private let deviceId = "apns-8792dc3f-45ce-4fd9-ab6d-3bf731f813c6" 12 | private var beamsTokenProvider: BeamsTokenProvider! 13 | 14 | override func setUp() { 15 | super.setUp() 16 | self.beamsTokenProvider = BeamsTokenProvider(authURL: "localhost:8080", getAuthData: { () -> (AuthData) in 17 | let sessionToken = "SESSION-TOKEN" 18 | return AuthData(headers: ["Authorization": "Bearer \(sessionToken)"], queryParams: [:]) 19 | }) 20 | } 21 | 22 | override func tearDown() { 23 | self.beamsTokenProvider = nil 24 | HTTPStubs.removeAllStubs() 25 | super.tearDown() 26 | } 27 | 28 | func testBeamsTokenProvider() { 29 | XCTAssertNotNil(self.beamsTokenProvider.authURL) 30 | XCTAssertEqual(self.beamsTokenProvider.authURL, "localhost:8080") 31 | 32 | let url = self.authURL() 33 | let jsonObject: [String: Any] = [ 34 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDA1NjA" 35 | ] 36 | stub(condition: isAbsoluteURLString(url.absoluteString)) { _ in 37 | return HTTPStubsResponse(jsonObject: jsonObject, statusCode: 200, headers: nil) 38 | } 39 | 40 | let exp = expectation(description: "It should successfully fetch the token") 41 | 42 | do { 43 | try self.beamsTokenProvider.fetchToken(userId: "Johnny Cash") { token, error in 44 | guard error == nil else { 45 | XCTFail("Calling 'fetchToken(userId:)' should result in no error") 46 | return exp.fulfill() 47 | } 48 | 49 | XCTAssertNotNil(token) 50 | XCTAssertEqual(token, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDA1NjA") 51 | exp.fulfill() 52 | } 53 | } catch { 54 | XCTFail("Calling 'fetchToken(userId:)' should not fail") 55 | exp.fulfill() 56 | } 57 | 58 | waitForExpectations(timeout: 10) 59 | } 60 | 61 | func testBeamsTokenProviderInvalidToken() { 62 | let url = self.authURL() 63 | let responseToken = "" 64 | let stubData = responseToken.data(using: .utf8) 65 | stub(condition: isAbsoluteURLString(url.absoluteString)) { _ in 66 | return HTTPStubsResponse(data: stubData!, statusCode: 500, headers: nil) 67 | } 68 | 69 | let exp = expectation(description: "It should return an error.") 70 | 71 | do { 72 | try self.beamsTokenProvider.fetchToken(userId: "Johnny Cash") { _, error in 73 | guard case TokenProviderError.error(let errorMessage) = error! else { 74 | XCTFail("Calling 'fetchToken(userId:)' should result in a 'TokenProviderError'") 75 | return exp.fulfill() 76 | } 77 | 78 | let expectedErrorMessage = "[PushNotifications] - BeamsTokenProvider: Received HTTP Status Code: 500" 79 | 80 | XCTAssertNotNil(errorMessage) 81 | XCTAssertEqual(errorMessage, expectedErrorMessage) 82 | exp.fulfill() 83 | } 84 | } catch { 85 | XCTFail("Calling 'fetchToken(userId:)' should not fail") 86 | exp.fulfill() 87 | } 88 | 89 | waitForExpectations(timeout: 10) 90 | } 91 | 92 | func testBeamsTokenIsNil() { 93 | let url = self.authURL() 94 | stub(condition: isAbsoluteURLString(url.absoluteString)) { _ in 95 | return HTTPStubsResponse(error: PushNotificationsError.error("[PushNotifications] - BeamsTokenProvider: Token is nil")) 96 | } 97 | 98 | let exp = expectation(description: "It should return an error.") 99 | 100 | do { 101 | try self.beamsTokenProvider.fetchToken(userId: "Johnny Cash") { _, error in 102 | guard case TokenProviderError.error(let errorMessage) = error! else { 103 | XCTFail("Calling 'fetchToken(userId:)' should result in a 'TokenProviderError'") 104 | return exp.fulfill() 105 | } 106 | 107 | let expectedErrorMessage = "[PushNotifications] - BeamsTokenProvider: Token is nil" 108 | 109 | XCTAssertNotNil(errorMessage) 110 | XCTAssertEqual(errorMessage, expectedErrorMessage) 111 | exp.fulfill() 112 | } 113 | } catch { 114 | XCTFail("Calling 'fetchToken(userId:)' should not fail") 115 | exp.fulfill() 116 | } 117 | 118 | waitForExpectations(timeout: 10) 119 | } 120 | 121 | private func authURL() -> URL { 122 | let testURLString = "localhost:8080" 123 | var components = URLComponents(string: testURLString)! 124 | let userIdQueryItem = URLQueryItem(name: "user_id", value: "Johnny Cash") 125 | components.queryItems = [userIdQueryItem] 126 | 127 | return components.url! 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Tests/Unit/Services/DeviceStateStoreTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class DeviceStateStoreTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | TestHelper.clearEverything(instanceId: TestHelper.instanceId) 8 | } 9 | 10 | override func tearDown() { 11 | super.tearDown() 12 | TestHelper.clearEverything(instanceId: TestHelper.instanceId) 13 | } 14 | 15 | func testInstanceIdsShouldRetrieveAndStoreInstancesCorrectly() { 16 | let deviceStateStore = DeviceStateStore() 17 | XCTAssertEqual(deviceStateStore.getInstanceIds(), []) 18 | 19 | deviceStateStore.persistInstanceId(TestHelper.instanceId) 20 | XCTAssertTrue(deviceStateStore.getInstanceIds().containsSameElements(as: [TestHelper.instanceId])) 21 | XCTAssertEqual(deviceStateStore.getInstanceIds().count, 1) 22 | 23 | // does not have duplicates 24 | deviceStateStore.persistInstanceId(TestHelper.instanceId) 25 | XCTAssertTrue(deviceStateStore.getInstanceIds().containsSameElements(as: [TestHelper.instanceId])) 26 | XCTAssertEqual(deviceStateStore.getInstanceIds().count, 1) 27 | 28 | // add another instance id 29 | deviceStateStore.persistInstanceId(TestHelper.instanceId2) 30 | XCTAssertTrue(deviceStateStore.getInstanceIds().containsSameElements(as: [TestHelper.instanceId, TestHelper.instanceId2])) 31 | XCTAssertEqual(deviceStateStore.getInstanceIds().count, 2) 32 | 33 | // remove first instance id 34 | deviceStateStore.removeInstanceId(instanceId: TestHelper.instanceId) 35 | XCTAssertTrue(deviceStateStore.getInstanceIds().containsSameElements(as: [TestHelper.instanceId2])) 36 | XCTAssertEqual(deviceStateStore.getInstanceIds().count, 1) 37 | 38 | // clear all instances 39 | deviceStateStore.removeAllInstanceIds() 40 | XCTAssertEqual(deviceStateStore.getInstanceIds(), []) 41 | } 42 | 43 | func testInstnaceIdsShouldMigrateCorrectly() { 44 | let oldInstanceService = UserDefaults(suiteName: PersistenceConstants.UserDefaults.suiteName(instanceId: nil))! 45 | oldInstanceService.set(TestHelper.instanceId, forKey: PersistenceConstants.UserDefaults.instanceId) 46 | 47 | // save things to the old storage 48 | let oldInstanceStorage = InstanceDeviceStateStore(nil) 49 | _ = oldInstanceStorage.persistInterests(["lemon", "pomelo", "grapefruit"]) 50 | _ = oldInstanceStorage.persistUserId(userId: "danielle") 51 | oldInstanceStorage.persistServerConfirmedInterestsHash("hash12345") 52 | oldInstanceStorage.persistStartJobHasBeenEnqueued(flag: true) 53 | oldInstanceStorage.persistUserIdHasBeenCalledWith(userId: "danielleHasBeenCalled") 54 | oldInstanceStorage.persistDeviceId("daniellesDeviceId") 55 | oldInstanceStorage.persistAPNsToken(token: "daniellesAPNsToken") 56 | oldInstanceStorage.persistMetadata(metadata: Metadata(sdkVersion: "123", iosVersion: "10.0", macosVersion: nil)) 57 | 58 | // get from the new device state store which should handle the migration for us 59 | let deviceStateStore = DeviceStateStore() 60 | XCTAssertTrue(deviceStateStore.getInstanceIds().containsSameElements(as: [TestHelper.instanceId])) 61 | XCTAssertEqual(deviceStateStore.getInstanceIds().count, 1) 62 | 63 | // assert that the instance storage migrated correctly 64 | let newInstanceStorage = InstanceDeviceStateStore(TestHelper.instanceId) 65 | XCTAssertTrue(newInstanceStorage.getInterests()!.containsSameElements(as: ["lemon", "pomelo", "grapefruit"])) 66 | XCTAssertEqual(newInstanceStorage.getInterests()!.count, 3) 67 | XCTAssertEqual(newInstanceStorage.getUserId(), "danielle") 68 | XCTAssertEqual(newInstanceStorage.getServerConfirmedInterestsHash(), "hash12345") 69 | XCTAssertEqual(newInstanceStorage.getStartJobHasBeenEnqueued(), true) 70 | XCTAssertEqual(newInstanceStorage.getUserIdHasBeenCalledWith(), "danielleHasBeenCalled") 71 | XCTAssertEqual(newInstanceStorage.getDeviceId(), "daniellesDeviceId") 72 | XCTAssertEqual(newInstanceStorage.getAPNsToken(), "daniellesAPNsToken") 73 | XCTAssertEqual(newInstanceStorage.getMetadata().sdkVersion, "123") 74 | XCTAssertEqual(newInstanceStorage.getMetadata().iosVersion, "10.0") 75 | XCTAssertEqual(newInstanceStorage.getMetadata().macosVersion, nil) 76 | 77 | // assert that old reference is gone 78 | XCTAssertEqual(oldInstanceService.string(forKey: PersistenceConstants.UserDefaults.instanceId), nil) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Tests/Unit/Services/DeviceTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class DeviceTests: XCTestCase { 5 | func testPersist() { 6 | let deviceStateStore = InstanceDeviceStateStore(TestHelper.instanceId) 7 | deviceStateStore.persistDeviceId("abcd") 8 | let deviceId = deviceStateStore.getDeviceId() 9 | 10 | XCTAssertNotNil(deviceId) 11 | XCTAssert("abcd" == deviceId) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/Unit/Services/InstanceDeviceStateStoreTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class InstanceDeviceStateStoreTests: XCTestCase { 5 | 6 | private let instanceDeviceStateStore1 = InstanceDeviceStateStore("instanceId1") 7 | private let instanceDeviceStateStore2 = InstanceDeviceStateStore("instanceId2") 8 | 9 | override func setUp() { 10 | super.setUp() 11 | instanceDeviceStateStore1.clear() 12 | instanceDeviceStateStore2.clear() 13 | } 14 | 15 | override func tearDown() { 16 | instanceDeviceStateStore1.clear() 17 | instanceDeviceStateStore2.clear() 18 | super.tearDown() 19 | } 20 | 21 | func testTwoInstancesDoNotIntefere() { 22 | // set 1 instance up with some interests and a device 23 | _ = instanceDeviceStateStore1.persistInterests(["cat", "dog"]) 24 | instanceDeviceStateStore1.persistDeviceId("deviceId1") 25 | 26 | // set 2nd instance up with different interests and a device 27 | _ = instanceDeviceStateStore2.persistInterests(["banana", "apple", "pear"]) 28 | instanceDeviceStateStore2.persistDeviceId("deviceId2") 29 | 30 | // check they're saved 31 | XCTAssertTrue((instanceDeviceStateStore1.getInterests()?.containsSameElements(as: ["cat", "dog"]))!) 32 | XCTAssertTrue((instanceDeviceStateStore2.getInterests()?.containsSameElements(as: ["banana", "apple", "pear"]))!) 33 | 34 | XCTAssertEqual(instanceDeviceStateStore1.getDeviceId(), "deviceId1") 35 | XCTAssertEqual(instanceDeviceStateStore2.getDeviceId(), "deviceId2") 36 | 37 | // clear instance 1 38 | instanceDeviceStateStore1.clear() 39 | 40 | // check instance 1 is cleared and instance 2 is still okay 41 | XCTAssertEqual(instanceDeviceStateStore1.getInterests(), []) 42 | XCTAssertTrue((instanceDeviceStateStore2.getInterests()?.containsSameElements(as: ["banana", "apple", "pear"]))!) 43 | 44 | XCTAssertEqual(instanceDeviceStateStore1.getDeviceId(), nil) 45 | XCTAssertEqual(instanceDeviceStateStore2.getDeviceId(), "deviceId2") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/Unit/Services/InterestPersistableTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class InterestPersistableTests: XCTestCase { 5 | 6 | private var deviceStateStore: InstanceDeviceStateStore! 7 | 8 | override func setUp() { 9 | super.setUp() 10 | UserDefaults.standard.removePersistentDomain(forName: PersistenceConstants.UserDefaults.suiteName(instanceId: TestHelper.instanceId)) 11 | self.deviceStateStore = InstanceDeviceStateStore(TestHelper.instanceId) 12 | } 13 | 14 | override func tearDown() { 15 | self.deviceStateStore = nil 16 | UserDefaults.standard.removePersistentDomain(forName: PersistenceConstants.UserDefaults.suiteName(instanceId: TestHelper.instanceId)) 17 | super.tearDown() 18 | } 19 | 20 | func testPersistInterestThatWasNotSavedYet() { 21 | let persistenceOperation = self.deviceStateStore.persistInterest("tech") 22 | XCTAssertTrue(persistenceOperation) 23 | } 24 | 25 | func testPersistInterestThatIsAlreadySaved() { 26 | _ = self.deviceStateStore.persistInterest("tech") 27 | let persistenceOperation = self.deviceStateStore.persistInterest("tech") 28 | XCTAssertFalse(persistenceOperation) 29 | } 30 | 31 | func testPersistInterestsThatWereNotSavedYet() { 32 | let interests = ["a", "b", "c", "d", "e"] 33 | _ = self.deviceStateStore.persistInterests(interests) 34 | let storedInterests = self.deviceStateStore.getInterests() 35 | XCTAssertNotNil(storedInterests!) 36 | XCTAssertTrue(interests.containsSameElements(as: storedInterests!)) 37 | } 38 | 39 | func testRemoveInterestFromTheStorage() { 40 | let removeOperation = self.deviceStateStore.removeInterest(interest: "tech") 41 | XCTAssertFalse(removeOperation) 42 | } 43 | 44 | func testRemoveExistingInterestFromTheStorage() { 45 | _ = self.deviceStateStore.persistInterest("tech") 46 | let removeOperation = self.deviceStateStore.removeInterest(interest: "tech") 47 | XCTAssertTrue(removeOperation) 48 | } 49 | 50 | func testRemoveAllInterests() { 51 | _ = self.deviceStateStore.persistInterests(["a", "b", "c", "d", "e"]) 52 | self.deviceStateStore.removeAllInterests() 53 | let interests = self.deviceStateStore.getInterests() 54 | XCTAssertEqual(interests!, []) 55 | } 56 | 57 | func testPersistInterestAndBatchSaveInterests() { 58 | let persistenceOperation = self.deviceStateStore.persistInterest("interest") 59 | XCTAssertTrue(persistenceOperation) 60 | let interests = ["a", "b", "c", "d", "e"] 61 | _ = self.deviceStateStore.persistInterests(interests) 62 | let storedInterests = self.deviceStateStore.getInterests() 63 | XCTAssertNotNil(storedInterests!) 64 | XCTAssert(storedInterests?.count == 5) 65 | XCTAssertTrue(interests.containsSameElements(as: storedInterests!)) 66 | } 67 | 68 | func testBatchSaveInterestsAndPersistAnotherInterest() { 69 | let interests = ["a", "b", "c", "d", "e"] 70 | _ = self.deviceStateStore.persistInterests(interests) 71 | let persistenceOperation = self.deviceStateStore.persistInterest("interest") 72 | let storedInterests = self.deviceStateStore.getInterests() 73 | XCTAssertNotNil(storedInterests!) 74 | XCTAssertTrue(persistenceOperation) 75 | XCTAssert(storedInterests?.count == 6) 76 | XCTAssertTrue(storedInterests!.containsSameElements(as: ["a", "b", "c", "d", "e", "interest"])) 77 | } 78 | 79 | func testBatchSaveInterestsAndSaveExistingInterest() { 80 | let interests = ["a", "b", "c"] 81 | _ = self.deviceStateStore.persistInterests(interests) 82 | let persistenceOperation = self.deviceStateStore.persistInterest("a") 83 | XCTAssertFalse(persistenceOperation) 84 | let storedInterests = self.deviceStateStore.getInterests() 85 | XCTAssert(storedInterests?.count == 3) 86 | XCTAssertTrue(storedInterests!.containsSameElements(as: interests)) 87 | } 88 | 89 | func testBatchSaveSameInterestsTwice() { 90 | let interests = ["a", "b", "c"] 91 | let persistenceOperation = self.deviceStateStore.persistInterests(interests) 92 | XCTAssertTrue(persistenceOperation) 93 | let persistSameInterestSetAgain = self.deviceStateStore.persistInterests(interests) 94 | XCTAssertFalse(persistSameInterestSetAgain) 95 | } 96 | 97 | func testPersistInterestWithALongName() { 98 | let interestWithALongName = "cs3pbizT,UjWwYXfguIm@y=l730QOOJvPfWV@W0_h2_IO8mkEzeS1JXwC@nHJDuZwbrgtsCVXAA3=9CIkQW69,.4d6Cs5Ny_gRALxAj3YlXEk674SGiqWgX:T74M6yQAqWfSGSJT.unKgg3J0ZqiQng__2V8ladmfVNw" 99 | let persistenceOperation = self.deviceStateStore.persistInterest(interestWithALongName) 100 | XCTAssertTrue(persistenceOperation) 101 | let storedInterests = self.deviceStateStore.getInterests() 102 | XCTAssertNotNil(storedInterests!) 103 | XCTAssertTrue(storedInterests?.count == 1) 104 | XCTAssertTrue(storedInterests!.containsSameElements(as: [interestWithALongName])) 105 | } 106 | 107 | func testInterestsHash() { 108 | self.deviceStateStore.persistServerConfirmedInterestsHash("749e2830366ba863f796cdf5e281662f") 109 | 110 | XCTAssertEqual(self.deviceStateStore.getServerConfirmedInterestsHash(), "749e2830366ba863f796cdf5e281662f") 111 | } 112 | 113 | func testNilInterestsHash() { 114 | XCTAssertEqual(self.deviceStateStore.getServerConfirmedInterestsHash(), "") 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Tests/Unit/Services/ServerSyncJobStoreTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class ServerSyncJobStoreTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | TestHelper.clearEverything(instanceId: TestHelper.instanceId) 8 | } 9 | 10 | override func tearDown() { 11 | super.tearDown() 12 | TestHelper.clearEverything(instanceId: TestHelper.instanceId) 13 | } 14 | 15 | func testBasicOperations() { 16 | var jobstore = ServerSyncJobStore(instanceId: TestHelper.instanceId) 17 | 18 | XCTAssertTrue(jobstore.isEmpty) 19 | 20 | jobstore.append(.setUserIdJob(userId: "danielle")) 21 | 22 | XCTAssertFalse(jobstore.isEmpty) 23 | 24 | let list = jobstore.toList() 25 | XCTAssertEqual(list.count, 1) 26 | 27 | if case .setUserIdJob(let userId) = list[0] { 28 | XCTAssertEqual(userId, "danielle") 29 | } else { 30 | XCTFail("The job should be of type '.SetUserIdJob'") 31 | } 32 | 33 | if let firstElement = jobstore.first, 34 | case .setUserIdJob(let userId) = firstElement { 35 | XCTAssertEqual(userId, "danielle") 36 | } else { 37 | XCTFail("The job should be of type '.SetUserIdJob'") 38 | } 39 | 40 | jobstore.removeFirst() 41 | 42 | XCTAssertTrue(jobstore.isEmpty) 43 | XCTAssertEqual(jobstore.toList().count, 0) 44 | } 45 | 46 | func testCorruptedFileShouldReturnEmptyOperations() throws { 47 | // create the file manually 48 | let url = try FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false) 49 | let filePath = url.appendingPathComponent("syncJobStore") 50 | let contents = "[invalid_json // lol]" 51 | FileManager.default.createFile(atPath: filePath.relativePath, contents: contents.toData()!) 52 | 53 | let jobstore = ServerSyncJobStore(instanceId: TestHelper.instanceId) 54 | 55 | XCTAssertTrue(jobstore.isEmpty) 56 | XCTAssertEqual(jobstore.toList().count, 0) 57 | } 58 | 59 | func testCorruptedEventShouldNotDropAllRequests() throws { 60 | let url = try FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false) 61 | let filePath = url.appendingPathComponent("\(TestHelper.instanceId)-syncJobStore") 62 | let contents = "[{\"userIdKey\":\"danielle\",\"discriminator\":6},{\"discriminator\":7000}]" 63 | FileManager.default.createFile(atPath: filePath.relativePath, contents: contents.data(using: .utf8)!) 64 | 65 | let jobstore = ServerSyncJobStore(instanceId: TestHelper.instanceId) 66 | XCTAssertFalse(jobstore.isEmpty) 67 | XCTAssertEqual(jobstore.toList().count, 1) 68 | } 69 | 70 | func testReportMissingInstanceIdEventShouldNotDropAllRequests() throws { 71 | let url = try FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false) 72 | let filePath = url.appendingPathComponent("\(TestHelper.instanceId)-syncJobStore") 73 | let contents = "[{\"userIdKey\":\"danielle\",\"discriminator\":6},{\"openEventTypeKey\":{\"deviceId\":\"192031231\",\"timestampSecs\":12,\"event\":\"Open\",\"publishId\":\"13u190231\"},\"discriminator\":7}]" 74 | FileManager.default.createFile(atPath: filePath.relativePath, contents: contents.data(using: .utf8)!) 75 | 76 | let jobstore = ServerSyncJobStore(instanceId: TestHelper.instanceId) 77 | XCTAssertFalse(jobstore.isEmpty) 78 | XCTAssertEqual(jobstore.toList().count, 1) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Tests/Unit/Services/UserPersistableTests.swift: -------------------------------------------------------------------------------- 1 | @testable import PushNotifications 2 | import XCTest 3 | 4 | class UserPersistableTests: XCTestCase { 5 | 6 | private var deviceStateStore: InstanceDeviceStateStore! 7 | 8 | override func setUp() { 9 | super.setUp() 10 | UserDefaults.standard.removePersistentDomain(forName: PersistenceConstants.UserDefaults.suiteName(instanceId: TestHelper.instanceId)) 11 | self.deviceStateStore = InstanceDeviceStateStore(TestHelper.instanceId) 12 | } 13 | 14 | override func tearDown() { 15 | UserDefaults.standard.removePersistentDomain(forName: PersistenceConstants.UserDefaults.suiteName(instanceId: TestHelper.instanceId)) 16 | super.tearDown() 17 | } 18 | 19 | func testPersistUserThatWasNotSavedYet() { 20 | let userIdNotSetYet = self.deviceStateStore.getUserId() 21 | XCTAssertNil(userIdNotSetYet) 22 | let persistenceOperation = self.deviceStateStore.persistUserId(userId: "Johnny Cash") 23 | XCTAssertTrue(persistenceOperation) 24 | let userId = self.deviceStateStore.getUserId() 25 | XCTAssertNotNil(userId) 26 | XCTAssertEqual(userId, "Johnny Cash") 27 | } 28 | 29 | func testPersistUserThatIsAlreadySaved() { 30 | _ = self.deviceStateStore.persistUserId(userId: "Johnny Cash") 31 | let persistenceOperation = self.deviceStateStore.persistUserId(userId: "Johnny Cash") 32 | XCTAssertFalse(persistenceOperation) 33 | } 34 | 35 | func testPersistUserAndRemoveUser() { 36 | let persistenceOperation = self.deviceStateStore.persistUserId(userId: "Johnny Cash") 37 | XCTAssertTrue(persistenceOperation) 38 | let userId = self.deviceStateStore.getUserId() 39 | XCTAssertNotNil(userId) 40 | XCTAssertEqual(userId, "Johnny Cash") 41 | self.deviceStateStore.removeUserId() 42 | XCTAssertNil(self.deviceStateStore.getUserId()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /docs/Enums/PushNotificationsError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PushNotificationsError Enumeration Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |

PushNotifications 4.0.0 Docs (100% documented)

21 |

View on GitHub

22 |

23 |

24 | 25 |
26 |

27 |
28 |
29 |
30 | 35 |
36 |
37 | 92 |
93 |
94 |
95 |

PushNotificationsError

96 |
97 |
98 | 99 |
public enum PushNotificationsError : Error
100 | 101 |
102 |
103 |

Error thrown by PushNotifications.

104 | 105 |

Values

106 | 107 |

error General error message.

108 | 109 |
110 | Show on GitHub 111 |
112 |
113 |
114 |
115 |
    116 |
  • 117 |
    118 | 119 | 120 | 121 | error(_:) 122 | 123 |
    124 |
    125 |
    126 |
    127 |
    128 |
    129 |

    General error.

    130 | 131 |
    132 |
    133 |

    Declaration

    134 |
    135 |

    Swift

    136 |
    case error(String)
    137 | 138 |
    139 |
    140 |
    141 | Show on GitHub 142 |
    143 |
    144 |
    145 |
  • 146 |
147 |
148 |
149 |
150 | 154 |
155 |
156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /docs/Enums/TokenProviderError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TokenProviderError Enumeration Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |

PushNotifications 4.0.0 Docs (100% documented)

21 |

View on GitHub

22 |

23 |

24 | 25 |
26 |

27 |
28 |
29 |
30 | 35 |
36 |
37 | 92 |
93 |
94 |
95 |

TokenProviderError

96 |
97 |
98 | 99 |
public enum TokenProviderError : Error
100 | 101 |
102 |
103 |

TokenProviderError.

104 | 105 |

Values

106 | 107 |

error Token provider error message.

108 | 109 |
110 | Show on GitHub 111 |
112 |
113 |
114 |
115 |
    116 |
  • 117 |
    118 | 119 | 120 | 121 | error(_:) 122 | 123 |
    124 |
    125 |
    126 |
    127 |
    128 |
    129 |

    Token provider error.

    130 | 131 |
    132 |
    133 |

    Declaration

    134 |
    135 |

    Swift

    136 |
    case error(String)
    137 | 138 |
    139 |
    140 |
    141 | Show on GitHub 142 |
    143 |
    144 |
    145 |
  • 146 |
147 |
148 |
149 |
150 | 154 |
155 |
156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 100% 23 | 24 | 25 | 100% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.pushnotifications 7 | CFBundleName 8 | PushNotifications 9 | DocSetPlatformFamily 10 | pushnotifications 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/Enums/PushNotificationsError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PushNotificationsError Enumeration Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |

PushNotifications 4.0.0 Docs (100% documented)

21 |

View on GitHub

22 |

23 |

24 | 25 |
26 |

27 |
28 |
29 |
30 | 35 |
36 |
37 | 92 |
93 |
94 |
95 |

PushNotificationsError

96 |
97 |
98 | 99 |
public enum PushNotificationsError : Error
100 | 101 |
102 |
103 |

Error thrown by PushNotifications.

104 | 105 |

Values

106 | 107 |

error General error message.

108 | 109 |
110 | Show on GitHub 111 |
112 |
113 |
114 |
115 |
    116 |
  • 117 |
    118 | 119 | 120 | 121 | error(_:) 122 | 123 |
    124 |
    125 |
    126 |
    127 |
    128 |
    129 |

    General error.

    130 | 131 |
    132 |
    133 |

    Declaration

    134 |
    135 |

    Swift

    136 |
    case error(String)
    137 | 138 |
    139 |
    140 |
    141 | Show on GitHub 142 |
    143 |
    144 |
    145 |
  • 146 |
147 |
148 |
149 |
150 | 154 |
155 |
156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/Enums/TokenProviderError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TokenProviderError Enumeration Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |

PushNotifications 4.0.0 Docs (100% documented)

21 |

View on GitHub

22 |

23 |

24 | 25 |
26 |

27 |
28 |
29 |
30 | 35 |
36 |
37 | 92 |
93 |
94 |
95 |

TokenProviderError

96 |
97 |
98 | 99 |
public enum TokenProviderError : Error
100 | 101 |
102 |
103 |

TokenProviderError.

104 | 105 |

Values

106 | 107 |

error Token provider error message.

108 | 109 |
110 | Show on GitHub 111 |
112 |
113 |
114 |
115 |
    116 |
  • 117 |
    118 | 119 | 120 | 121 | error(_:) 122 | 123 |
    124 |
    125 |
    126 |
    127 |
    128 |
    129 |

    Token provider error.

    130 | 131 |
    132 |
    133 |

    Declaration

    134 |
    135 |

    Swift

    136 |
    case error(String)
    137 | 138 |
    139 |
    140 |
    141 | Show on GitHub 142 |
    143 |
    144 |
    145 |
  • 146 |
147 |
148 |
149 |
150 | 154 |
155 |
156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 100% 23 | 24 | 25 | 100% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/docsets/PushNotifications.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/docsets/PushNotifications.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/docsets/PushNotifications.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/docsets/PushNotifications.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | 61 | // KaTeX rendering 62 | if ("katex" in window) { 63 | $($('.math').each( (_, element) => { 64 | katex.render(element.textContent, element, { 65 | displayMode: $(element).hasClass('m-block'), 66 | throwOnError: false, 67 | trust: true 68 | }); 69 | })) 70 | } 71 | -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $typeahead = $('[data-typeahead]'); 3 | var $form = $typeahead.parents('form'); 4 | var searchURL = $form.attr('action'); 5 | 6 | function displayTemplate(result) { 7 | return result.name; 8 | } 9 | 10 | function suggestionTemplate(result) { 11 | var t = '
'; 12 | t += '' + result.name + ''; 13 | if (result.parent_name) { 14 | t += '' + result.parent_name + ''; 15 | } 16 | t += '
'; 17 | return t; 18 | } 19 | 20 | $typeahead.one('focus', function() { 21 | $form.addClass('loading'); 22 | 23 | $.getJSON(searchURL).then(function(searchData) { 24 | const searchIndex = lunr(function() { 25 | this.ref('url'); 26 | this.field('name'); 27 | this.field('abstract'); 28 | for (const [url, doc] of Object.entries(searchData)) { 29 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 30 | } 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3, 37 | autoselect: true 38 | }, 39 | { 40 | limit: 10, 41 | display: displayTemplate, 42 | templates: { suggestion: suggestionTemplate }, 43 | source: function(query, sync) { 44 | const lcSearch = query.toLowerCase(); 45 | const results = searchIndex.query(function(q) { 46 | q.term(lcSearch, { boost: 100 }); 47 | q.term(lcSearch, { 48 | boost: 10, 49 | wildcard: lunr.Query.wildcard.TRAILING 50 | }); 51 | }).map(function(result) { 52 | var doc = searchData[result.ref]; 53 | doc.url = result.ref; 54 | return doc; 55 | }); 56 | sync(results); 57 | } 58 | } 59 | ); 60 | $form.removeClass('loading'); 61 | $typeahead.trigger('focus'); 62 | }); 63 | }); 64 | 65 | var baseURL = searchURL.slice(0, -"search.json".length); 66 | 67 | $typeahead.on('typeahead:select', function(e, result) { 68 | window.location = baseURL + result.url; 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/Documents/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | 4 | ], 5 | "source_directory": "/Users/danielbrowne/Documents/Work/push-notifications-swift" 6 | } -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/docsets/PushNotifications.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/PushNotifications.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/docsets/PushNotifications.tgz -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pusher/push-notifications-swift/441a6dede9a1a59f01ae4dd85b4ee3fbc9730ba4/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | 61 | // KaTeX rendering 62 | if ("katex" in window) { 63 | $($('.math').each( (_, element) => { 64 | katex.render(element.textContent, element, { 65 | displayMode: $(element).hasClass('m-block'), 66 | throwOnError: false, 67 | trust: true 68 | }); 69 | })) 70 | } 71 | -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $typeahead = $('[data-typeahead]'); 3 | var $form = $typeahead.parents('form'); 4 | var searchURL = $form.attr('action'); 5 | 6 | function displayTemplate(result) { 7 | return result.name; 8 | } 9 | 10 | function suggestionTemplate(result) { 11 | var t = '
'; 12 | t += '' + result.name + ''; 13 | if (result.parent_name) { 14 | t += '' + result.parent_name + ''; 15 | } 16 | t += '
'; 17 | return t; 18 | } 19 | 20 | $typeahead.one('focus', function() { 21 | $form.addClass('loading'); 22 | 23 | $.getJSON(searchURL).then(function(searchData) { 24 | const searchIndex = lunr(function() { 25 | this.ref('url'); 26 | this.field('name'); 27 | this.field('abstract'); 28 | for (const [url, doc] of Object.entries(searchData)) { 29 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 30 | } 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3, 37 | autoselect: true 38 | }, 39 | { 40 | limit: 10, 41 | display: displayTemplate, 42 | templates: { suggestion: suggestionTemplate }, 43 | source: function(query, sync) { 44 | const lcSearch = query.toLowerCase(); 45 | const results = searchIndex.query(function(q) { 46 | q.term(lcSearch, { boost: 100 }); 47 | q.term(lcSearch, { 48 | boost: 10, 49 | wildcard: lunr.Query.wildcard.TRAILING 50 | }); 51 | }).map(function(result) { 52 | var doc = searchData[result.ref]; 53 | doc.url = result.ref; 54 | return doc; 55 | }); 56 | sync(results); 57 | } 58 | } 59 | ); 60 | $form.removeClass('loading'); 61 | $typeahead.trigger('focus'); 62 | }); 63 | }); 64 | 65 | var baseURL = searchURL.slice(0, -"search.json".length); 66 | 67 | $typeahead.on('typeahead:select', function(e, result) { 68 | window.location = baseURL + result.url; 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /docs/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | 4 | ], 5 | "source_directory": "/Users/danielbrowne/Documents/Work/push-notifications-swift" 6 | } -------------------------------------------------------------------------------- /push-notifications-ios/push-notifications-ios/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import PushNotifications 2 | import UIKit 3 | 4 | // swiftlint:disable force_try 5 | 6 | @UIApplicationMain 7 | class AppDelegate: UIResponder, UIApplicationDelegate { 8 | var window: UIWindow? 9 | let pushNotifications = PushNotifications.shared 10 | 11 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 12 | self.pushNotifications.start(instanceId: "YOUR_INSTANCE_ID") // Can be found here: https://dash.pusher.com 13 | try! self.pushNotifications.addDeviceInterest(interest: "debug-test") 14 | self.pushNotifications.registerForRemoteNotifications() 15 | 16 | return true 17 | } 18 | 19 | func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 20 | self.pushNotifications.registerDeviceToken(deviceToken) 21 | } 22 | 23 | func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { 24 | self.pushNotifications.handleNotification(userInfo: userInfo) 25 | print(userInfo) 26 | } 27 | 28 | func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { 29 | print("Remote notification support is unavailable due to error: \(error.localizedDescription)") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /push-notifications-ios/push-notifications-ios/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /push-notifications-ios/push-notifications-ios/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /push-notifications-ios/push-notifications-ios/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /push-notifications-ios/push-notifications-ios/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIBackgroundModes 24 | 25 | remote-notification 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /push-notifications-ios/push-notifications-ios/ViewController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class ViewController: UIViewController { 5 | 6 | override func viewDidLoad() { 7 | super.viewDidLoad() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /push-notifications-ios/push-notifications-ios/push-notifications-ios.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /push-notifications-mac-objc/push-notifications-mac-objc/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AppDelegate : NSObject 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /push-notifications-mac-objc/push-notifications-mac-objc/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | @import PushNotifications; 3 | 4 | @interface AppDelegate () 5 | 6 | @end 7 | 8 | @implementation AppDelegate 9 | 10 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 11 | [[PushNotifications shared] startWithInstanceId:@"YOUR_INSTANCE_ID"]; // Can be found here: https://dash.pusher.com 12 | [[PushNotifications shared] registerForRemoteNotifications]; 13 | 14 | NSError *anyError; 15 | [[PushNotifications shared] addDeviceInterestWithInterest:@"debug-test" error:&anyError]; 16 | } 17 | 18 | - (void)application:(NSApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 19 | [[PushNotifications shared] registerDeviceToken:deviceToken]; 20 | } 21 | 22 | - (void)application:(NSApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { 23 | [[PushNotifications shared] handleNotificationWithUserInfo:userInfo]; 24 | NSLog(@"%@", userInfo); 25 | } 26 | 27 | - (void)application:(NSApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { 28 | NSLog(@"Remote notification support is unavailable due to error: %@", error.localizedDescription); 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /push-notifications-mac-objc/push-notifications-mac-objc/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /push-notifications-mac-objc/push-notifications-mac-objc/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2018 Pusher. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /push-notifications-mac-objc/push-notifications-mac-objc/ViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ViewController : NSViewController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /push-notifications-mac-objc/push-notifications-mac-objc/ViewController.m: -------------------------------------------------------------------------------- 1 | #import "ViewController.h" 2 | 3 | @implementation ViewController 4 | 5 | - (void)viewDidLoad { 6 | [super viewDidLoad]; 7 | } 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /push-notifications-mac-objc/push-notifications-mac-objc/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | int main(int argc, const char * argv[]) { 4 | return NSApplicationMain(argc, argv); 5 | } 6 | -------------------------------------------------------------------------------- /push-notifications-mac-objc/push-notifications-mac-objc/push-notifications-mac-objc.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /push-notifications-mac-objc/push-notifications-mac-objc/push_notifications_mac_objc.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /push-notifications-mac/push-notifications-mac/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import PushNotifications 3 | 4 | @NSApplicationMain 5 | class AppDelegate: NSObject, NSApplicationDelegate { 6 | 7 | let pushNotifications = PushNotifications.shared 8 | 9 | func applicationDidFinishLaunching(_ aNotification: Notification) { 10 | self.pushNotifications.start(instanceId: "YOUR_INSTANCE_ID") // Can be found here: https://dash.pusher.com 11 | self.pushNotifications.registerForRemoteNotifications() 12 | try? self.pushNotifications.addDeviceInterest(interest: "debug-test") 13 | } 14 | 15 | func application(_ application: NSApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 16 | self.pushNotifications.registerDeviceToken(deviceToken) 17 | } 18 | 19 | func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String: Any]) { 20 | self.pushNotifications.handleNotification(userInfo: userInfo) 21 | print(userInfo) 22 | } 23 | 24 | func application(_ application: NSApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { 25 | print("Remote notification support is unavailable due to error: \(error.localizedDescription)") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /push-notifications-mac/push-notifications-mac/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /push-notifications-mac/push-notifications-mac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2018 Luka Bratos. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /push-notifications-mac/push-notifications-mac/ViewController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class ViewController: NSViewController { 4 | 5 | override func viewDidLoad() { 6 | super.viewDidLoad() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /push-notifications-mac/push-notifications-mac/push_notifications_mac.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.aps-environment 6 | development 7 | com.apple.security.app-sandbox 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AppDelegate : UIResponder 4 | 5 | @property (strong, nonatomic) UIWindow *window; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | @import PushNotifications; 3 | 4 | @interface AppDelegate () 5 | 6 | @end 7 | 8 | @implementation AppDelegate 9 | 10 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 11 | 12 | // if using one instance id throughout the whole app do the following: 13 | [[PushNotifications shared] startWithInstanceId:@"YOUR_INSTANCE_ID"]; // Can be found here: https://dash.pusher.com 14 | [[PushNotifications shared] registerForRemoteNotifications]; 15 | 16 | NSError *anyError; 17 | [[PushNotifications shared] addDeviceInterestWithInterest:@"debug-test" error:&anyError]; 18 | 19 | // if using multiple instances do the following: 20 | // PushNotifications *pn1 = [[PushNotifications alloc] initWithInstanceId: @"YOUR_INSTANCE_ID_1"]; 21 | // NSError *anyError; 22 | // [pn1 addDeviceInterestWithInterest:@"debug-potatoes" error:&anyError] 23 | // 24 | // PushNotifications *pn2 = [[PushNotifications alloc] initWithInstanceId: @"YOUR_INSTANCE_ID_2"]; 25 | // NSError *anyError; 26 | // [pn2 addDeviceInterestWithInterest:@"debug-carrots" error:&anyError] 27 | 28 | 29 | return YES; 30 | } 31 | 32 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 33 | [[PushNotifications shared] registerDeviceToken:deviceToken]; 34 | } 35 | 36 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { 37 | [[PushNotifications shared] handleNotificationWithUserInfo:userInfo]; 38 | NSLog(@"%@", userInfo); 39 | } 40 | 41 | -(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { 42 | NSLog(@"Remote notification support is unavailable due to error: %@", error.localizedDescription); 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIBackgroundModes 24 | 25 | remote-notification 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/ViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ViewController : UIViewController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/ViewController.m: -------------------------------------------------------------------------------- 1 | #import "ViewController.h" 2 | 3 | @interface ViewController () 4 | 5 | @end 6 | 7 | @implementation ViewController 8 | 9 | - (void)viewDidLoad { 10 | [super viewDidLoad]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "AppDelegate.h" 3 | 4 | int main(int argc, char * argv[]) { 5 | @autoreleasepool { 6 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /push-notifications-objc/push-notifications-objc/push-notifications-objc.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | --------------------------------------------------------------------------------