├── fastlane ├── build_info.yml ├── Pluginfile └── README.md ├── safesafe ├── Resources │ ├── de.lproj │ │ └── LaunchScreen.strings │ ├── pl.lproj │ │ └── LaunchScreen.strings │ ├── uk-UA.lproj │ │ └── LaunchScreen.strings │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ └── Contents.json │ │ ├── AppIcon-Dev.appiconset │ │ │ ├── 100.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── 1024.png │ │ ├── bug_icon.imageset │ │ │ ├── bug_icon.pdf │ │ │ └── Contents.json │ │ ├── AppIcon-Stage.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ └── 87.png │ │ ├── watermark.imageset │ │ │ ├── watermark.pdf │ │ │ └── Contents.json │ │ ├── close_icon.imageset │ │ │ ├── close_icon.pdf │ │ │ └── Contents.json │ │ ├── govtech_white.imageset │ │ │ ├── govtech_white.pdf │ │ │ └── Contents.json │ │ ├── Logo-govpl-WHITE.imageset │ │ │ ├── Logo-govpl-WHITE.pdf │ │ │ └── Contents.json │ │ ├── SafeSafe_white-1.imageset │ │ │ ├── SafeSafe_white-1.pdf │ │ │ └── Contents.json │ │ ├── gradient-iphoneX-72p.imageset │ │ │ ├── gradient-iphoneX-72p.pdf │ │ │ └── Contents.json │ │ ├── protegosafe_logo_white-red.imageset │ │ │ ├── protegosafe_logo_white-red.pdf │ │ │ └── Contents.json │ │ ├── protegosafe_logo_white-red_old.imageset │ │ │ ├── protegosafe_logo_white-red.pdf │ │ │ └── Contents.json │ │ └── protegosafe_logo_white-yellow.imageset │ │ │ ├── protegosafe_logo_white-yellow.pdf │ │ │ └── Contents.json │ ├── Extensions │ │ ├── apns-mutablecontent.xcconfig │ │ ├── apns-mutablecontent-dev.xcconfig │ │ ├── apns-stage-mutablecontent.xcconfig │ │ ├── apns-stage-mutablecontent-dev.xcconfig │ │ └── apns-stage-adhoc-mutablecontent.xcconfig │ ├── Live │ │ ├── live.xcconfig │ │ ├── liveDebug.xcconfig │ │ └── Config-live.plist │ ├── Stage │ │ ├── stage.xcconfig │ │ ├── stageDebug.xcconfig │ │ └── stageAdhoc.xcconfig │ ├── safesafe Debug.entitlements │ └── safesafe Release.entitlements ├── App │ ├── safesafe-Bridging-Header.h │ └── SceneDelegate.swift ├── Common │ ├── Extensions │ │ ├── String │ │ │ ├── String+utils.swift │ │ │ ├── String+SHA256.swift │ │ │ ├── String+json.swift │ │ │ └── String+localized.swift │ │ ├── UIApplication+version.swift │ │ ├── UIWindow+key.swift │ │ ├── URL+utils.swift │ │ ├── Result+Void.swift │ │ ├── UINavigationController+statusBar.swift │ │ └── Moya+PromiseKit.swift │ ├── Error │ │ ├── PromiseCancel.swift │ │ └── InternalError.swift │ ├── Base │ │ ├── NavigationController.swift │ │ └── ViewController.swift │ ├── Helpers │ │ ├── Fatal.swift │ │ ├── Assertion.swift │ │ ├── Notifications │ │ │ └── Models │ │ │ │ ├── LocalizedNotificationModel.swift │ │ │ │ ├── PushNotificationCovidStatsModel.swift │ │ │ │ ├── PushNotificationHistoryModel.swift │ │ │ │ └── RouteModel.swift │ │ ├── URLAction.swift │ │ ├── Protobuf │ │ │ └── ProtobufWorker.swift │ │ ├── NetworkMonitoring.swift │ │ ├── WebCacheCleaner.swift │ │ ├── HiderController.swift │ │ ├── Logger.swift │ │ ├── Networking │ │ │ ├── NetworkingAlertManager.swift │ │ │ └── RenewableRequest.swift │ │ ├── Validation │ │ │ └── Upload │ │ │ │ └── UploadValidationAlertManager.swift │ │ └── HistoricalDataWorker.swift │ ├── Protocols │ │ ├── DCDeviceProtocol.swift │ │ ├── AlertManager │ │ │ ├── AlertType.swift │ │ │ ├── AlertManager.swift │ │ │ └── AlertAction.swift │ │ ├── ViewControllerType.swift │ │ ├── CoordinatorType.swift │ │ ├── JSONReprestable.swift │ │ ├── RquestHeader.swift │ │ └── ViewModelType.swift │ └── Files │ │ └── File.swift ├── Services │ ├── JavaScript Bridge │ │ ├── Models │ │ │ ├── AppReviewResponse.swift │ │ │ ├── SMSResponse.swift │ │ │ ├── SystemLanguageResponse.swift │ │ │ ├── ApplicationVersionResponse.swift │ │ │ ├── CovidStatsRequest.swift │ │ │ ├── CovidStatsResponse.swift │ │ │ ├── SurveyFinishedResponse.swift │ │ │ ├── DeleteHistoricalDataRequest.swift │ │ │ ├── UploadTemporaryExposureKeysResponse.swift │ │ │ ├── RejectedServiceResponse.swift │ │ │ ├── UploadTemporaryExposureKeysStatusResult.swift │ │ │ ├── EnableServicesResponse.swift │ │ │ ├── AppLifeCycleResponse.swift │ │ │ ├── ServiceStatusResponse.swift │ │ │ ├── ExposureHistoryRiskCheckAgregatedResponse.swift │ │ │ └── AppReviewMockAlertManager.swift │ │ └── JSBridge+BridgeDataType.swift │ ├── Free test │ │ └── Model │ │ │ ├── JS │ │ │ ├── FreeTestPinCodeResponse.swift │ │ │ ├── FreeTestUploadPinRequest.swift │ │ │ ├── FreeTestPinUploadResponse.swift │ │ │ └── FreeTestSubscriptionInfoResponse.swift │ │ │ ├── Networking │ │ │ ├── FreeTestGetSubscriptionRequestModel.swift │ │ │ ├── FreeTestCreateSubscriptionResponseModel.swift │ │ │ ├── FreeTestGetSubscriptionResponseModel.swift │ │ │ ├── FreeTestCreateSubscriptionRequestModel.swift │ │ │ └── FreeTestRequestHeader.swift │ │ │ ├── FreeTestSubscriptionState.swift │ │ │ └── Database │ │ │ └── DeviceGUIDModel.swift │ ├── ExposureNotification │ │ ├── Models │ │ │ ├── DeviceVerification.swift │ │ │ ├── TemporaryExposureKeys │ │ │ │ ├── TemporaryExposureKeysData.swift │ │ │ │ ├── TemporaryExposureKeys.swift │ │ │ │ └── TemporaryExposureSingleKey.swift │ │ │ ├── Auth │ │ │ │ ├── TemporaryExposureKeysAuthResponse.swift │ │ │ │ └── TemporaryExposureKeysAuthData.swift │ │ │ ├── RiskLevel.swift │ │ │ ├── Exposure.swift │ │ │ ├── ExposureSummary.swift │ │ │ └── Historical data │ │ │ │ ├── ExposureHistoryAnalyzeCheck.swift │ │ │ │ └── ExposureHistoryRiskCheck.swift │ │ ├── Download │ │ │ └── Model │ │ │ │ └── DiagnosisKeysDownloadInfoModel.swift │ │ └── ExposureServiceHistoricalData.swift │ ├── Permissions │ │ ├── Protocols │ │ │ └── PermissionType.swift │ │ ├── ExposureNotificationPermission.swift │ │ └── NotificationsPermission.swift │ ├── AppStatus │ │ ├── ExposureNotificationStatusProtocol.swift │ │ ├── ExposureNotificationStatus.swift │ │ └── ServiceStatusManager.swift │ ├── District │ │ └── Models │ │ │ ├── DistrictObservedManageModel.swift │ │ │ ├── ObservedDistrictsPWAResponseModel.swift │ │ │ ├── LocalStorage │ │ │ ├── ObservedDistrictStorageModel.swift │ │ │ ├── VoivodeshipStorageModel.swift │ │ │ └── DistrictStorageModel.swift │ │ │ ├── DistrictResponseModel.swift │ │ │ └── DistrictsPWAResponseModel.swift │ ├── AppManager.swift │ ├── KeychainService.swift │ ├── DeviceCheckService.swift │ ├── StoredDefaults.swift │ ├── OpenerService.swift │ ├── JailbreakService.swift │ ├── Notifications │ │ └── NotificationsAlertManager.swift │ ├── Local Storage │ │ └── FileStorage.swift │ ├── DetailsWorker.swift │ └── TimestampsWorker.swift ├── Components │ ├── Debug │ │ ├── DebugStackViewItem.swift │ │ └── SQLite │ │ │ └── SQLiteManager.swift │ └── Hider │ │ └── HiderViewController.swift ├── Models │ ├── DashboardStatsResponse.swift │ ├── Timestamps.swift │ └── DashboardStatsAPIResponse.swift ├── Networking │ ├── RemoteConfig │ │ └── Models │ │ │ ├── SubscriptionConfiguration.swift │ │ │ ├── DiagnosisKeyDownloadConfiguration.swift │ │ │ ├── RemoteConfigurationResponse.swift │ │ │ └── ExposureConfiguration.swift │ ├── Plugin │ │ └── CachePolicyPlugin.swift │ ├── CustomSessionDelegate.swift │ ├── CustomSession.swift │ ├── InfoTarget.swift │ ├── FreeTestTarget.swift │ └── ExposureKeysTarget.swift ├── DependencyContainer │ └── Factories │ │ ├── Components │ │ ├── PWA │ │ │ └── PWAViewControllerFactory.swift │ │ └── Debug │ │ │ └── DebugViewControllerFactory.swift │ │ └── Services │ │ └── ExposureNotificationJSBridgeFactory.swift └── Language │ └── LanguageController.swift ├── Documentation ├── RemovingHistoricalData.swift ├── ReportingRiskLevel.md ├── ReceivingExposuresInformation.md ├── DownloadingDiagnosisKeys.md ├── ControllingExposureNotification.md ├── ProvidingDiagnosisKeys.md └── UploadingTemporaryExposureKeys.md ├── DISCLAIMER.pdf ├── ghImages └── logo.png ├── doc └── images │ └── logo.png ├── Scripts ├── RealmStripFrameworks.sh ├── CopyGooglePlist.sh └── CopyConfigPlist.sh ├── .github ├── PULL_REQUEST_TEMPLATE │ ├── pull_request_template.md │ └── README.md ├── CONTRIBUTING_en.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── CONTRIBUTING.md ├── workflows │ ├── nodejs.yml │ └── stage-nodejs.yml ├── SUPPORT.md └── SECURITY.md ├── .whitesource ├── Gemfile ├── PushMutableContent ├── PushMutableContent.entitlements ├── PushMutableContent-dev.entitlements ├── Info.plist └── NotificationService.swift ├── XcodeGenConfig └── dev.yml ├── upload-pl-locale.sh ├── rebuild.sh ├── Podfile ├── sdasdasdas ├── Info.plist └── NotificationService.swift └── .gitignore /fastlane/build_info.yml: -------------------------------------------------------------------------------- 1 | --- 2 | build_number: 471 3 | -------------------------------------------------------------------------------- /safesafe/Resources/de.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /safesafe/Resources/pl.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Documentation/RemovingHistoricalData.swift: -------------------------------------------------------------------------------- 1 | To be provided -------------------------------------------------------------------------------- /safesafe/Resources/uk-UA.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /DISCLAIMER.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/DISCLAIMER.pdf -------------------------------------------------------------------------------- /ghImages/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/ghImages/logo.png -------------------------------------------------------------------------------- /doc/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/doc/images/logo.png -------------------------------------------------------------------------------- /Scripts/RealmStripFrameworks.sh: -------------------------------------------------------------------------------- 1 | bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh" -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Which issue(s) this PR fixes: 2 | 3 | Fixes # 4 | 5 | @gh-username 6 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /safesafe/App/safesafe-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // safesafe-Bridging-Header.h 3 | // safesafe 4 | // 5 | 6 | #import 7 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/100.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/114.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/120.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/144.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/152.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/167.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/180.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/20.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/29.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/40.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/50.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/57.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/58.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/60.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/72.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/76.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/80.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/87.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/bug_icon.imageset/bug_icon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/bug_icon.imageset/bug_icon.pdf -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "checkRunSettings": { 3 | "vulnerableCheckRunConclusionLevel": "success" 4 | }, 5 | "issueSettings": { 6 | "minSeverityLevel": "MEDIUM" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Dev.appiconset/1024.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/100.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/1024.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/114.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/120.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/144.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/152.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/167.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/180.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/20.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/29.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/40.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/50.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/57.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/58.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/60.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/72.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/76.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/80.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/AppIcon-Stage.appiconset/87.png -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/watermark.imageset/watermark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/watermark.imageset/watermark.pdf -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/close_icon.imageset/close_icon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/close_icon.imageset/close_icon.pdf -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/govtech_white.imageset/govtech_white.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/govtech_white.imageset/govtech_white.pdf -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-xcodegen' 6 | gem 'fastlane-plugin-firebase_app_distribution' 7 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/Logo-govpl-WHITE.imageset/Logo-govpl-WHITE.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/Logo-govpl-WHITE.imageset/Logo-govpl-WHITE.pdf -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/SafeSafe_white-1.imageset/SafeSafe_white-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/SafeSafe_white-1.imageset/SafeSafe_white-1.pdf -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/gradient-iphoneX-72p.imageset/gradient-iphoneX-72p.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/gradient-iphoneX-72p.imageset/gradient-iphoneX-72p.pdf -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/protegosafe_logo_white-red.imageset/protegosafe_logo_white-red.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/protegosafe_logo_white-red.imageset/protegosafe_logo_white-red.pdf -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/protegosafe_logo_white-red_old.imageset/protegosafe_logo_white-red.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/protegosafe_logo_white-red_old.imageset/protegosafe_logo_white-red.pdf -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/protegosafe_logo_white-yellow.imageset/protegosafe_logo_white-yellow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProteGO-Safe/ios/HEAD/safesafe/Resources/Assets.xcassets/protegosafe_logo_white-yellow.imageset/protegosafe_logo_white-yellow.pdf -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/watermark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "watermark.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /safesafe/Common/Extensions/String/String+utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+utils.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 04/08/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | static var empty: String { "" } 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/govtech_white.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "govtech_white.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/Logo-govpl-WHITE.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Logo-govpl-WHITE.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/SafeSafe_white-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "SafeSafe_white-1.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/gradient-iphoneX-72p.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "gradient-iphoneX-72p.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /safesafe/Common/Error/PromiseCancel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PromiseCancel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 23/10/2020. 6 | // 7 | 8 | import PromiseKit 9 | 10 | struct PromiseCancel: PromiseKit.CancellableError { 11 | let isCancelled: Bool = true 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/AppReviewResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppReviewResponse.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 23/11/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct AppReviewResponse: Codable { 11 | let appReview: Bool 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Components/Debug/DebugStackViewItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DebugStackViewItem.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 18/08/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | final class DebugStackViewItem: UIButton { 11 | var action: DebugAction = .none 12 | } 13 | 14 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/SMSResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmsResponse.swift 3 | // safesafe 4 | // 5 | // Created by Adam Tokarczyk on 23/06/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SMSResponse: Codable { 11 | let number: String 12 | let text: String 13 | } 14 | -------------------------------------------------------------------------------- /safesafe/Models/DashboardStatsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DashboardStatsResponse.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 11/12/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DashboardStatsResponse: Codable, JSONRepresentable { 11 | let covidStats: Data 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/JS/FreeTestPinCodeResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestPinCodeResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 23/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FreeTestPinCodeResponse: Codable { 11 | let code: String? 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/protegosafe_logo_white-red.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "protegosafe_logo_white-red.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/protegosafe_logo_white-yellow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "protegosafe_logo_white-yellow.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/JS/FreeTestUploadPinRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestUploadPinRequest.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FreeTestUploadPinRequest: Codable { 11 | let pin: String 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Models/Timestamps.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Timestamps.swift 3 | // safesafe 4 | // 5 | // Created by Namedix on 22/02/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TimestampsResponse: Decodable { 11 | let nextUpdate: Int 12 | let dashboardUpdated: Int 13 | let detailsUpdated: Int 14 | } 15 | -------------------------------------------------------------------------------- /safesafe/Networking/RemoteConfig/Models/SubscriptionConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SubscriptionConfiguration.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SubscriptionConfiguration: Codable { 11 | let interval: Int 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/SystemLanguageResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemLanguageResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 04/08/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SystemLanguageResponse: Codable { 11 | let language: String 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Components/Hider/HiderViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HiderViewController.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 06/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | final class HiderViewController: UIViewController { 11 | static let identifier = "HiderViewController" 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/ApplicationVersionResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationVersionResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 28/07/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ApplicationVersionResponse: Codable { 11 | let appVersion: String 12 | } 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | source "https://rubygems.org" 6 | 7 | gem 'fastlane' 8 | gem 'cocoapods' 9 | 10 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 11 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 12 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/DeviceVerification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceVerification.swift 3 | // safesafe 4 | // 5 | 6 | import Foundation 7 | 8 | struct DeviceVerification: Encodable { 9 | 10 | let deviceToken: String 11 | let transactionId: String 12 | let timestamp: Int 13 | 14 | } 15 | -------------------------------------------------------------------------------- /safesafe/Common/Base/NavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationController.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 21/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NavigationController: UINavigationController { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Fatal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fatal.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 14/07/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | final class Fatal { 11 | class func execute(_ message: String) -> Never { 12 | console(message) 13 | return abort() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/CovidStatsRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CovidStatsRequest.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 13/12/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct CovidStatsRequest: Codable, JSONRepresentable { 11 | let isCovidStatsNotificationEnabled: Bool 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/CovidStatsResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CovidStatsResponse.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 13/12/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct CovidStatsResponse: Codable, JSONRepresentable { 11 | let isCovidStatsNotificationEnabled: Bool 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/Networking/FreeTestGetSubscriptionRequestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestGetSubscriptionRequestModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FreeTestGetSubscriptionRequestModel: Codable { 11 | let guid: String 12 | } 13 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING_en.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | Our guidelines are mostly located around [.github](/.github/) directory of the project. 3 | 4 | ### ToC for contributors 5 | - [Code of Conduct](CODE_OF_CONDUCT.md) 6 | - [Issues & Support](SUPPORT.md) 7 | - [License](/LICENSE) 8 | - [Security](SECURITY.md) 9 | - [Pull request](PULL_REQUEST_TEMPLATE/README.md) 10 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/bug_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "bug_icon.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/close_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "close_icon.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/Networking/FreeTestCreateSubscriptionResponseModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestCreateSubscriptionResponseModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FreeTestCreateSubscriptionResponseModel: Codable { 11 | let token: String 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/TemporaryExposureKeys/TemporaryExposureKeysData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TemporaryExposureKeysData.swift 3 | // safesafe 4 | // 5 | 6 | import Foundation 7 | 8 | @available(iOS 13.5, *) 9 | struct TemporaryExposureKeysData: Encodable { 10 | let data: TemporaryExposureKeys 11 | let isInteroperabilityEnabled: Bool 12 | } 13 | -------------------------------------------------------------------------------- /safesafe/Common/Protocols/DCDeviceProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DCDeviceProtocol.swift 3 | // safesafe 4 | 5 | import DeviceCheck 6 | 7 | protocol DCDeviceProtocol { 8 | 9 | var isSupported: Bool { get } 10 | func generateToken(completionHandler completion: @escaping (Data?, Error?) -> Void) 11 | 12 | } 13 | 14 | extension DCDevice: DCDeviceProtocol { } 15 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/Networking/FreeTestGetSubscriptionResponseModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestGetSubscriptionResponseModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FreeTestGetSubscriptionResponseModel: Codable { 11 | let guid: String 12 | let status: Int 13 | } 14 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Assertion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Assertion.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 14/07/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | final class Assertion { 11 | class func failure(_ message: String) { 12 | console(message) 13 | #if !LIVE && !STAGE 14 | abort() 15 | #endif 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/Networking/FreeTestCreateSubscriptionRequestModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestCreateSubscriptionRequestModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FreeTestCreateSubscriptionRequestModel: Codable { 11 | let code: String 12 | let guid: String 13 | } 14 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/SurveyFinishedResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SurveyFinishedResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 23/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SurveyFinishedResponse: Codable { 12 | let timestamp: TimeInterval 13 | } 14 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Notifications/Models/LocalizedNotificationModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalizedNotificationModel.swift 3 | // PushMutableContent 4 | // 5 | // Created by Łukasz Szyszkowski on 02/12/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct LocalizedNotificationModel: Codable { 11 | let title: String 12 | let content: String 13 | let laguageISO: String 14 | } 15 | -------------------------------------------------------------------------------- /safesafe/Common/Extensions/UIApplication+version.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+version.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 28/07/2020. 6 | // 7 | import UIKit 8 | 9 | extension UIApplication { 10 | static var appVersion: String? { 11 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/protegosafe_logo_white-red_old.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "protegosafe_logo_white-red.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/DeleteHistoricalDataRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteHistoricalDataRequest.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 07/12/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DeleteHistoricalDataRequest: Codable { 11 | let notifications: [String] 12 | let riskChecks: [String] 13 | let exposures: [String] 14 | } 15 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/UploadTemporaryExposureKeysResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UploadTemporaryExposureKeysResponse.swift 3 | // safesafe 4 | // 5 | // Created by Rafał Małczyński on 06/05/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UploadTemporaryExposureKeysResponse: Codable { 11 | 12 | let pin: String 13 | let isInteroperabilityEnabled: Bool 14 | } 15 | -------------------------------------------------------------------------------- /safesafe/Services/Permissions/Protocols/PermissionType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionType.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 26/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PromiseKit 11 | 12 | protocol PermissionType { 13 | func state(shouldAsk: Bool) -> Promise 14 | } 15 | -------------------------------------------------------------------------------- /safesafe/Networking/RemoteConfig/Models/DiagnosisKeyDownloadConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiagnosisKeyDownloadConfiguration.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 21/05/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DiagnosisKeyDownloadConfiguration: Decodable { 11 | let timeoutMobileSeconds: Int 12 | let timeoutWifiSeconds: Int 13 | let retryCount: Int 14 | } 15 | -------------------------------------------------------------------------------- /safesafe/Common/Protocols/AlertManager/AlertType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlertType.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 01/07/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | enum AlertType { 11 | case noInternet 12 | case uploadGeneral 13 | case keysAtLeast 14 | case keysMax 15 | case keysPerDayMax 16 | case pushNotificationSettings 17 | case appReviewMock 18 | } 19 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/FreeTestSubscriptionState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestSubscriptionState.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | enum FreeTestSubscriptionState: Int, Codable { 11 | case unverified = 0 12 | case verified = 1 13 | case signedForTest = 2 14 | case utilized = 3 15 | case unknown = 999 16 | } 17 | -------------------------------------------------------------------------------- /safesafe/Resources/Extensions/apns-mutablecontent.xcconfig: -------------------------------------------------------------------------------- 1 | INFOPLIST_FILE = PushMutableContent/Info.plist 2 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe.apns-mutablecontent 3 | DEVELOPMENT_TEAM = 85GAG994R5 4 | CODE_SIGN_ENTITLEMENTS = PushMutableContent/PushMutableContent.entitlements 5 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Live Mutable Content 6 | CODE_SIGN_IDENTITY = Apple Distribution: Ministerstwo Cyfryzacji (85GAG994R5) 7 | -------------------------------------------------------------------------------- /safesafe/Resources/Extensions/apns-mutablecontent-dev.xcconfig: -------------------------------------------------------------------------------- 1 | INFOPLIST_FILE = PushMutableContent/Info.plist 2 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe.apns-mutablecontent 3 | DEVELOPMENT_TEAM = 85GAG994R5 4 | CODE_SIGN_ENTITLEMENTS = PushMutableContent/PushMutableContent-dev.entitlements 5 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Live Debug Mutable Content 6 | CODE_SIGN_IDENTITY = Apple Development: Protego Protego (Y78W4PU766) 7 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/Auth/TemporaryExposureKeysAuthResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TemporaryExposureKeysAuthResponse.swift 3 | // safesafe 4 | // 5 | 6 | import Foundation 7 | 8 | struct TemporaryExposureKeysAuthResponse: Decodable { 9 | 10 | struct TemporaryExposureKeysToken: Decodable { 11 | let accessToken: String 12 | } 13 | 14 | let result: TemporaryExposureKeysToken 15 | 16 | } 17 | -------------------------------------------------------------------------------- /safesafe/Common/Protocols/ViewControllerType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerType.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 09/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ViewControllerType: UIViewController { 12 | associatedtype T: ViewModelType 13 | var viewModel: T { get } 14 | func start() 15 | init(viewModel: T) 16 | } 17 | -------------------------------------------------------------------------------- /safesafe/Networking/RemoteConfig/Models/RemoteConfigurationResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteConfigurationResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 21/05/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RemoteConfigurationResponse: Decodable { 11 | let diagnosis: DiagnosisKeyDownloadConfiguration 12 | let exposure: ExposureConfiguration 13 | let subscription: SubscriptionConfiguration 14 | } 15 | -------------------------------------------------------------------------------- /safesafe/Resources/Extensions/apns-stage-mutablecontent.xcconfig: -------------------------------------------------------------------------------- 1 | INFOPLIST_FILE = PushMutableContent/Info.plist 2 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe-staging.apns-mutablecontent 3 | DEVELOPMENT_TEAM = 85GAG994R5 4 | CODE_SIGN_ENTITLEMENTS = PushMutableContent/PushMutableContent.entitlements 5 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Stage Mutable Content 6 | CODE_SIGN_IDENTITY = Apple Distribution: Ministerstwo Cyfryzacji (85GAG994R5) 7 | -------------------------------------------------------------------------------- /PushMutableContent/PushMutableContent.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | production 7 | com.apple.security.application-groups 8 | 9 | group.pl.gov.mc.protegosafe 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /safesafe/Resources/Extensions/apns-stage-mutablecontent-dev.xcconfig: -------------------------------------------------------------------------------- 1 | INFOPLIST_FILE = PushMutableContent/Info.plist 2 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe-staging.apns-mutablecontent 3 | DEVELOPMENT_TEAM = 85GAG994R5 4 | CODE_SIGN_ENTITLEMENTS = PushMutableContent/PushMutableContent-dev.entitlements 5 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Stage Debug Mutable Content 6 | CODE_SIGN_IDENTITY = Apple Development: Protego Protego (Y78W4PU766) 7 | -------------------------------------------------------------------------------- /PushMutableContent/PushMutableContent-dev.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.security.application-groups 8 | 9 | group.pl.gov.mc.protegosafe 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /safesafe/Resources/Extensions/apns-stage-adhoc-mutablecontent.xcconfig: -------------------------------------------------------------------------------- 1 | INFOPLIST_FILE = PushMutableContent/Info.plist 2 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe-staging.apns-mutablecontent 3 | DEVELOPMENT_TEAM = 85GAG994R5 4 | CODE_SIGN_ENTITLEMENTS = PushMutableContent/PushMutableContent.entitlements 5 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Stage Ad-Hoc Mutable Content 6 | CODE_SIGN_IDENTITY = Apple Distribution: Ministerstwo Cyfryzacji (85GAG994R5) 7 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/JS/FreeTestPinUploadResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestPinUploadResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FreeTestPinUploadResponse: Codable { 11 | 12 | enum Status: Int, Codable { 13 | case success = 1 14 | case failed = 2 15 | case canceled = 3 16 | } 17 | 18 | let result: Status 19 | } 20 | -------------------------------------------------------------------------------- /safesafe/Common/Extensions/UIWindow+key.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIWindow+key.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 30/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIWindow { 11 | static var key: UIWindow? { 12 | if #available(iOS 13, *) { 13 | return UIApplication.shared.windows.first { $0.isKeyWindow } 14 | } else { 15 | return UIApplication.shared.keyWindow 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /safesafe/Services/AppStatus/ExposureNotificationStatusProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureNotificationStatusProtocol.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 25/05/2020. 6 | // 7 | 8 | import Foundation 9 | import PromiseKit 10 | 11 | protocol ExposureNotificationStatusProtocol: class { 12 | var status: Promise { get } 13 | func isBluetoothOn(delay: TimeInterval) -> Promise 14 | } 15 | -------------------------------------------------------------------------------- /safesafe/Services/District/Models/DistrictObservedManageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DistrictObservedManageModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 11/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DistrictObservedManageModel: Codable { 11 | 12 | enum OperationType: Int, Codable { 13 | case add = 1 14 | case delete = 2 15 | } 16 | 17 | let districtId: Int 18 | let type: OperationType 19 | } 20 | -------------------------------------------------------------------------------- /XcodeGenConfig/dev.yml: -------------------------------------------------------------------------------- 1 | targets: 2 | safesafe Dev: 3 | settings: 4 | INFOPLIST_FILE: "safesafe/Info-dev.plist" 5 | PRODUCT_BUNDLE_IDENTIFIER: "se.sigmaconnectivity.protegosafe" 6 | ASSETCATALOG_COMPILER_APPICON_NAME: "AppIcon-Dev" 7 | DEVELOPMENT_TEAM: "HU9FKSEQXD" 8 | CODE_SIGN_ENTITLEMENTS: "safesafe Dev.entitlements" 9 | PROVISIONING_PROFILE_SPECIFIER: "protegosafe-development" 10 | OTHER_SWIFT_FLAGS: "$(inherited) -D COCOAPODS -D DEV" 11 | -------------------------------------------------------------------------------- /safesafe/Services/District/Models/ObservedDistrictsPWAResponseModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObservedDistrictsPWAResponseModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 12/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ObservedDistrictsPWAResponseModel: Codable { 11 | 12 | let districts: [District] 13 | 14 | struct District: Codable { 15 | let id: Int 16 | let name: String 17 | let state: Int 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /safesafe/Networking/Plugin/CachePolicyPlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CachePolicyPlugin.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 07/10/2020. 6 | // 7 | 8 | import Moya 9 | 10 | final class CachePolicyPlugin: PluginType { 11 | func prepare(_ request: URLRequest, target: TargetType) -> URLRequest { 12 | var mutableRequest = request 13 | mutableRequest.cachePolicy = .reloadIgnoringLocalCacheData 14 | 15 | return mutableRequest 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/RejectedServiceResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RejectedServiceResponse.swift 3 | // safesafe 4 | // 5 | // Created by Rafał Małczyński on 23/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum RejectedService: String, Codable { 12 | 13 | case notification 14 | 15 | } 16 | 17 | struct RejectedServiceResponse: Codable { 18 | 19 | var rejectedService: RejectedService 20 | 21 | } 22 | -------------------------------------------------------------------------------- /safesafe/Common/Protocols/AlertManager/AlertManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlertManager.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 30/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol AlertManager { 11 | var viewController: UIViewController? { get } 12 | func register(viewController: UIViewController) 13 | func show(type: AlertType, result: @escaping (AlertAction) -> ()) 14 | } 15 | 16 | extension AlertManager { 17 | func register(viewController: UIViewController) {} 18 | } 19 | -------------------------------------------------------------------------------- /safesafe/Common/Protocols/CoordinatorType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoordinatorType.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 09/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol CoordinatorType { 12 | init(parent: T?) 13 | func start() 14 | } 15 | 16 | extension CoordinatorType { 17 | init(parent: T? = nil) { 18 | self.init(parent: parent) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/Auth/TemporaryExposureKeysAuthData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TemporaryExposureKeysAuthData.swift 3 | // safesafe 4 | // 5 | 6 | import Foundation 7 | 8 | struct TemporaryExposureKeysAuthData: Encodable { 9 | 10 | struct TemporaryExposureKeysAuthCode: Encodable { 11 | let code: String 12 | } 13 | 14 | let data: TemporaryExposureKeysAuthCode 15 | 16 | init(code: String) { 17 | data = TemporaryExposureKeysAuthCode(code: code) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /upload-pl-locale.sh: -------------------------------------------------------------------------------- 1 | # run sh locales/scripts/upload.sh -i PROJECT_ID -t PROJECT_TOKEN 2 | while getopts i:t:l: flag 3 | do 4 | case "${flag}" in 5 | i) id=${OPTARG};; 6 | t) token=${OPTARG};; 7 | esac 8 | done 9 | 10 | curl -X POST https://api.poeditor.com/v2/projects/upload \ 11 | -F api_token="$token" \ 12 | -F id="$id" \ 13 | -F language="pl" \ 14 | -F updating="terms_translations" \ 15 | -F file=@"`dirname "$0"`/safesafe/Resources/translations/pl.strings" \ 16 | -F fuzzy_trigger="1" \ 17 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/UploadTemporaryExposureKeysStatusResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UploadTemporaryExposureKeysStatusResult.swift 3 | // safesafe 4 | // 5 | 6 | import Foundation 7 | 8 | enum UploadTemporaryExposureKeysStatus: Int, Encodable { 9 | case success = 1 10 | case failure = 2 11 | case canceled = 3 12 | case noInternet = 4 13 | case accessDenied = 5 14 | } 15 | 16 | struct UploadTemporaryExposureKeysStatusResult: Encodable { 17 | 18 | let result: UploadTemporaryExposureKeysStatus 19 | 20 | } 21 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/URLAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLAction.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 20/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import UIKit.UIApplication 10 | 11 | enum URLAction: String, CaseIterable { 12 | case tel 13 | case sms 14 | case mailto 15 | case facetime 16 | case facetimeAudio = "facetime-audio" 17 | 18 | func call(url: URL) { 19 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /safesafe/Resources/Live/live.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-safesafe/Pods-safesafe.live.xcconfig" 2 | 3 | INFOPLIST_FILE = safesafe/Info-live.plist 4 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe 5 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon 6 | DEVELOPMENT_TEAM = 85GAG994R5 7 | CODE_SIGN_ENTITLEMENTS = safesafe/Resources/safesafe Release.entitlements 8 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Live 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -D LIVE 10 | CODE_SIGN_IDENTITY = Apple Distribution: Ministerstwo Cyfryzacji (85GAG994R5) 11 | -------------------------------------------------------------------------------- /safesafe/Resources/Live/liveDebug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-safesafe/Pods-safesafe.livedebug.xcconfig" 2 | 3 | INFOPLIST_FILE = safesafe/Info-live.plist 4 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe 5 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon 6 | DEVELOPMENT_TEAM = 85GAG994R5 7 | CODE_SIGN_ENTITLEMENTS = safesafe/Resources/safesafe Debug.entitlements 8 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Live Debug 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -D LIVE_DEBUG 10 | CODE_SIGN_IDENTITY = Apple Development: Protego Protego (Y78W4PU766) 11 | -------------------------------------------------------------------------------- /safesafe/Common/Protocols/JSONReprestable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONReprestable.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 19/05/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol JSONRepresentable: Codable { 11 | var jsonString: String? { get } 12 | } 13 | 14 | extension JSONRepresentable { 15 | var jsonString: String? { 16 | guard 17 | let data = try? JSONEncoder().encode(self), 18 | let json = String(data: data, encoding: .utf8) 19 | else { return nil } 20 | 21 | return json 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /safesafe/Resources/Stage/stage.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-safesafe/Pods-safesafe.stage.xcconfig" 2 | 3 | INFOPLIST_FILE = safesafe/Info-stage.plist 4 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe-staging 5 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Stage 6 | DEVELOPMENT_TEAM = 85GAG994R5 7 | CODE_SIGN_ENTITLEMENTS = safesafe/Resources/safesafe Release.entitlements 8 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Stage 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -D STAGE 10 | CODE_SIGN_IDENTITY = Apple Distribution: Ministerstwo Cyfryzacji (85GAG994R5) 11 | -------------------------------------------------------------------------------- /safesafe/Resources/Stage/stageDebug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-safesafe/Pods-safesafe.stagedebug.xcconfig" 2 | 3 | INFOPLIST_FILE = safesafe/Info-stage.plist 4 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe-staging 5 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Stage 6 | DEVELOPMENT_TEAM = 85GAG994R5 7 | CODE_SIGN_ENTITLEMENTS = safesafe/Resources/safesafe Debug.entitlements 8 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Stage Debug 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -D STAGE_DEBUG 10 | CODE_SIGN_IDENTITY = Apple Development: Protego Protego (Y78W4PU766) 11 | -------------------------------------------------------------------------------- /safesafe/Resources/Stage/stageAdhoc.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-safesafe/Pods-safesafe.liveadhoc.xcconfig" 2 | 3 | INFOPLIST_FILE = safesafe/Info-live.plist 4 | PRODUCT_BUNDLE_IDENTIFIER = pl.gov.mc.protegosafe-staging 5 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon 6 | DEVELOPMENT_TEAM = 85GAG994R5 7 | CODE_SIGN_ENTITLEMENTS = safesafe/Resources/safesafe Release.entitlements 8 | PROVISIONING_PROFILE_SPECIFIER = Protego Safe Stage Ad-Hoc 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -D STAGE_ADHOC 10 | CODE_SIGN_IDENTITY = Apple Distribution: Ministerstwo Cyfryzacji (85GAG994R5) 11 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/EnableServicesResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnableServicesResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 17/05/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct EnableServicesResponse: Codable { 11 | 12 | enum CodingKeys: String, CodingKey { 13 | case enableBluetooth = "enableBt" 14 | case enableExposureNotificationService 15 | case enableNotification 16 | } 17 | 18 | let enableExposureNotificationService: Bool? 19 | let enableBluetooth: Bool? 20 | let enableNotification: Bool? 21 | } 22 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/AppLifeCycleResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppLifeCycleResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 19/05/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ApplicationLifecycleResponse: Codable, JSONRepresentable { 11 | 12 | enum CodingKeys: String, CodingKey { 13 | case appicationState = "appState" 14 | } 15 | 16 | let appicationState: LifecycleState 17 | 18 | enum LifecycleState: Int, Codable { 19 | case willEnterForeground = 1 20 | case didEnterBackground = 2 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Download/Model/DiagnosisKeysDownloadInfoModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiagnosisKeysDownloadInfoModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 21/10/2020. 6 | // 7 | 8 | import RealmSwift 9 | import Foundation 10 | 11 | class DiagnosisKeysDownloadInfoModel: Object, LocalStorable { 12 | 13 | static let identifier: Int = 21102020 14 | 15 | @objc dynamic var id: Int = DiagnosisKeysDownloadInfoModel.identifier 16 | @objc dynamic var lastPackageTimestamp: Int = .zero 17 | 18 | override class func primaryKey() -> String? { "id" } 19 | } 20 | -------------------------------------------------------------------------------- /safesafe/Services/District/Models/LocalStorage/ObservedDistrictStorageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObservedDistrictStorageModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 11/10/2020. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | final class ObservedDistrictStorageModel: Object, LocalStorable { 12 | 13 | @objc dynamic var districtId: Int = -1 14 | @objc dynamic var name: String = .empty 15 | @objc dynamic var createdAt = Date() 16 | @objc dynamic var order: Int = .zero // for future use maybe 17 | 18 | override class func primaryKey() -> String? { "districtId" } 19 | } 20 | -------------------------------------------------------------------------------- /safesafe/Common/Extensions/String/String+SHA256.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+SHA256.swift 3 | // safesafe 4 | // 5 | 6 | import Foundation 7 | 8 | extension String { 9 | 10 | func sha256() -> String? { 11 | guard let data = self.data(using: .utf8) else { return nil } 12 | 13 | var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 14 | data.withUnsafeBytes { 15 | _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) 16 | } 17 | 18 | return hash 19 | .map { String(format: "%02x", $0) } 20 | .joined() 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /safesafe/Services/AppManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppManager.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 09/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class AppManager { 12 | static let instance = AppManager() 13 | 14 | private let defaults = StoredDefaults.standard 15 | 16 | var isFirstRun: Bool { 17 | return defaults.get(key: .isFirstRun) ?? false 18 | } 19 | 20 | private init() {} 21 | } 22 | 23 | extension StoredDefaults.Key { 24 | static let isFirstRun = StoredDefaults.Key("isFirstRun") 25 | } 26 | -------------------------------------------------------------------------------- /Scripts/CopyGooglePlist.sh: -------------------------------------------------------------------------------- 1 | DEV_PATH=${PROJECT_DIR}/${TARGET_NAME}/Resources/Dev/GoogleService-Info-Dev.plist 2 | STAGE_PATH=${PROJECT_DIR}/${TARGET_NAME}/Resources/Stage/GoogleService-Info-Stage.plist 3 | LIVE_PATH=${PROJECT_DIR}/${TARGET_NAME}/Resources/Live/GoogleService-Info-Live.plist 4 | 5 | DESTINATION=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist 6 | 7 | case "${CONFIGURATION}" in 8 | "Dev") cp -r "${DEV_PATH}" "${DESTINATION}";; 9 | "Stage"|"StageDebug"|"StageScreencast") cp -r "${STAGE_PATH}" "${DESTINATION}";; 10 | "Live"|"LiveDebug") cp -r "${LIVE_PATH}" "${DESTINATION}";; 11 | *) ;; 12 | esac -------------------------------------------------------------------------------- /safesafe/Services/District/Models/DistrictResponseModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DistrictResponseModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 11/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DistrictResponseModel: Codable { 11 | 12 | let updated: Int 13 | let voivodeships: [Voivodeship] 14 | 15 | struct Voivodeship: Codable { 16 | let id: Int 17 | let name: String 18 | let districts: [District] 19 | 20 | struct District: Codable { 21 | let id: Int 22 | let name: String 23 | let state: Int 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Scripts/CopyConfigPlist.sh: -------------------------------------------------------------------------------- 1 | DEV_PATH=${PROJECT_DIR}/${TARGET_NAME}/Resources/Dev/Config-dev.plist 2 | STAGE_PATH=${PROJECT_DIR}/${TARGET_NAME}/Resources/Stage/Config-stage.plist 3 | LIVE_PATH=${PROJECT_DIR}/${TARGET_NAME}/Resources/Live/Config-live.plist 4 | 5 | case "${CONFIGURATION}" in 6 | "Dev") cp -r "${DEV_PATH}" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Config-dev.plist";; 7 | "Stage"|"StageDebug"|"StageScreencast") cp -r "${STAGE_PATH}" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Config-stage.plist";; 8 | "Live"|"LiveDebug") cp -r "${LIVE_PATH}" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Config-live.plist";; 9 | *) ;; 10 | esac -------------------------------------------------------------------------------- /safesafe/Common/Protocols/RquestHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RquestHeader.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol RequestHeader: Codable { 11 | var asDictionary: [String: String] { get } 12 | } 13 | 14 | extension RequestHeader { 15 | var asDictionary: [String: String] { 16 | guard 17 | let data = try? JSONEncoder().encode(self), 18 | let dictionary = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: String] 19 | else { return [:] } 20 | 21 | return dictionary 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /safesafe/Networking/CustomSessionDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlamofireSessionManagerBuilder.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 04/06/2020. 6 | // 7 | 8 | import Foundation 9 | import Moya 10 | import Alamofire 11 | import Security 12 | import TrustKit 13 | 14 | class CustomSessionDelegate: SessionDelegate { 15 | 16 | func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { 17 | TrustKit.sharedInstance().pinningValidator.handle(challenge, completionHandler: completionHandler) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /safesafe/Networking/RemoteConfig/Models/ExposureConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureConfiguration.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 21/05/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ExposureConfiguration: Decodable { 11 | let minimumRiskScore: Int 12 | let attenuationWeigh: Int 13 | let daysSinceLastExposureWeight: Int 14 | let durationWeight: Int 15 | let transmissionRiskWeight: Int 16 | let attenuationScores: [Int] 17 | let daysSinceLastExposureScores: [Int] 18 | let durationScores: [Int] 19 | let transmissionRiskScores: [Int] 20 | let durationAtAttenuationThresholds: [Int] 21 | } 22 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/RiskLevel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RiskLevel.swift 3 | // safesafe 4 | // 5 | // Created by Rafał Małczyński on 25/05/2020. 6 | // 7 | 8 | enum RiskLevel: Int, Encodable { 9 | case none = 0 10 | case low = 1 11 | case medium = 2 12 | case high = 3 13 | 14 | init(fromFullRangeScore score: Int) { 15 | switch score { 16 | case 1...1499: 17 | self = .low 18 | 19 | case 1500...2999: 20 | self = .medium 21 | 22 | case 3000...: 23 | self = .high 24 | 25 | default: 26 | self = .none 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/TemporaryExposureKeys/TemporaryExposureKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TemporaryExposureKeys.swift 3 | // safesafe 4 | // 5 | 6 | import Foundation 7 | 8 | @available(iOS 13.5, *) 9 | struct TemporaryExposureKeys: Encodable { 10 | 11 | enum Default { 12 | static let regions = ["PL"] 13 | static let platform = "ios" 14 | static let appPackageName = Bundle.main.bundleIdentifier! 15 | } 16 | 17 | let temporaryExposureKeys: [TemporaryExposureSingleKey] 18 | let regions = Default.regions 19 | let platform = Default.platform 20 | let appPackageName = Default.appPackageName 21 | let verificationPayload: String 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Współpraca nad projektem 2 | 3 | Zapraszamy wszystkich do współpracy nad projektem: 4 | 5 | 1. [Zasady ogólne.](CODE_OF_CONDUCT.md) 6 | 2. [Chcę zgłosić pomysł dotyczący aplikacji.](SUPPORT.md) 7 | 3. [Chcę zgłosić błąd dotyczący bezpieczeństwa.](SECURITY.md) 8 | 4. [Programuję, jak mogę pomóc?](#Programuję-jak-mogę-pomóc) 9 | 10 | ## Programuję, jak mogę pomóc? 11 | 12 | Jest mnóstwo sposobów w jaki możesz nam pomóc: 13 | * Rób razem z nami audyt kodu. 14 | * Dodawaj swoje `Pull Request`y. 15 | * Przeglądaj istniejące `Pull Request`y. 16 | * Pomagaj innym użytkownikom. 17 | 18 | Komunikuj się z nami przez GitHub i Stackoverflow. 19 | Więcej informacji w temacie komunikacji znajdziesz [tutaj](SUPPORT.md) 20 | -------------------------------------------------------------------------------- /rebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | xcproj=$(find . -name *.xcodeproj -print -quit) 3 | rm -Rf $xcproj 4 | osascript -e 'quit app "XCODE"' 5 | xcodegen 6 | pod install 7 | 8 | xcworkspace=$(find . -name *.xcworkspace -print -quit) 9 | locations=$(ls -d /Applications/Xcode*) 10 | declare -a options 11 | 12 | while IFS=' ' read -ra XCODES; do 13 | for i in "${XCODES[@]}"; do 14 | options+=($i) 15 | done 16 | done <<< $locations 17 | 18 | echo 19 | echo "-----------------------------------------------" 20 | echo "Please select Xcode app to open: ${xcworkspace}" 21 | echo 22 | PS3='your choice: ' 23 | select opt in "${options[@]}" 24 | do 25 | case $opt in 26 | *) open $xcworkspace -a $opt 27 | break 28 | ;; 29 | esac 30 | done -------------------------------------------------------------------------------- /safesafe/Networking/CustomSession.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomSession.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 04/06/2020. 6 | // 7 | 8 | import Foundation 9 | import Alamofire 10 | import Moya 11 | 12 | class CustomSession { 13 | final class func defaultSession() -> Session { 14 | let configuration = URLSessionConfiguration.default 15 | configuration.headers = .default 16 | configuration.requestCachePolicy = .reloadIgnoringLocalCacheData 17 | configuration.urlCache = .init(memoryCapacity: .zero, diskCapacity: .zero, diskPath: nil) 18 | 19 | return Session(configuration: configuration, delegate: CustomSessionDelegate(), startRequestsImmediately: false) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /safesafe/Common/Extensions/String/String+json.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+json.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 23/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | func jsonDecode(decoder: JSONDecoder? = nil) -> T? { 14 | let decoder = decoder ?? JSONDecoder() 15 | guard let data = self.data(using: .utf8) else { 16 | return nil 17 | } 18 | 19 | do { 20 | let model = try decoder.decode(T.self, from: data) 21 | return model 22 | } catch { 23 | console(error) 24 | return nil 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /safesafe/Common/Extensions/URL+utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+utils.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | 13 | static let emptyFilePath = URL(string: "file://")! 14 | 15 | static func build(scheme: String, host: String, port: Int? = nil) -> URL? { 16 | var components = URLComponents() 17 | components.host = host 18 | components.scheme = scheme 19 | components.port = port 20 | 21 | return components.url 22 | } 23 | 24 | func isHostEqual(to otherHost: URL) -> Bool { 25 | return self.path.hasPrefix(otherHost.path) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/TemporaryExposureKeys/TemporaryExposureSingleKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TemporaryExposureKey.swift 3 | // safesafe 4 | // 5 | 6 | import ExposureNotification 7 | 8 | @available(iOS 13.5, *) 9 | struct TemporaryExposureSingleKey: Encodable { 10 | 11 | let key: Data 12 | let rollingPeriod: ENIntervalNumber 13 | let rollingStartNumber: ENIntervalNumber 14 | let transmissionRisk: ENRiskLevel 15 | 16 | init(_ key: ENTemporaryExposureKey) { 17 | self.key = key.keyData 18 | rollingPeriod = key.rollingPeriod 19 | rollingStartNumber = key.rollingStartNumber 20 | 21 | // As requested, it should be `8` for now 22 | transmissionRisk = 8//key.transmissionRiskLevel 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /safesafe/Common/Extensions/Result+Void.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result+Void.swift 3 | // safesafe Live 4 | // 5 | // Created by Rafał Małczyński on 22/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PromiseKit 11 | 12 | extension Swift.Result where Success == Void { 13 | static var success: Swift.Result { 14 | return .success(()) 15 | } 16 | } 17 | 18 | 19 | extension Swift.Result { 20 | func toPromise() -> Promise { 21 | Promise { seal in 22 | switch self { 23 | case .success(let success): 24 | seal.fulfill(success) 25 | case .failure(let error): 26 | seal.reject(error) 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /safesafe/Common/Protocols/AlertManager/AlertAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlertAction.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 01/07/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | enum AlertAction { 11 | case cancel 12 | case ok 13 | case retry 14 | case settings 15 | case more 16 | 17 | var title: String { 18 | switch self { 19 | case .cancel: 20 | return "ALERT_CANCEL_BUTTON_TITLE".localized() 21 | case .ok: 22 | return "ALERT_OK_BUTTON_TITLE".localized() 23 | case .retry: 24 | return "ALERT_RETRY_BUTTON_TITLE".localized() 25 | case .settings: 26 | return "ALERT_SETTINGS_BUTTON_TITLE".localized() 27 | case .more: 28 | return "More" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/Networking/FreeTestRequestHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestHeader.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FreeTestRequestHeader: Codable, RequestHeader { 11 | 12 | // MARK: - Properties 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case userAgent = "User-Agent" 16 | case accessToken = "Authorization" 17 | } 18 | 19 | let userAgent = "ios" 20 | let accessToken: String? 21 | 22 | // MARK: - Initialization 23 | 24 | init(accessToken: String? = nil) { 25 | if let accessToken = accessToken { 26 | self.accessToken = "Bearer \(accessToken)" 27 | } else { 28 | self.accessToken = nil 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /safesafe/Services/District/Models/DistrictsPWAResponseModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DistrictsPWAResponseModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 11/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DistrictsPWAResponseModel: Codable { 11 | 12 | enum ResultType: Int, Codable { 13 | case success = 1 14 | case failed = 2 15 | } 16 | 17 | let result: ResultType 18 | let updated: Int 19 | let voivodeships: [Voivodeship] 20 | 21 | struct Voivodeship: Codable { 22 | let id: Int 23 | let name: String 24 | let districts: [District] 25 | 26 | struct District: Codable { 27 | let id: Int 28 | let name: String 29 | let state: Int 30 | let isSubscribed: Bool 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /safesafe/Common/Extensions/UINavigationController+statusBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+statusBar.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 21/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UINavigationController { 12 | 13 | func setStatusBar(backgroundColor: UIColor) { 14 | let statusBarFrame: CGRect 15 | if #available(iOS 13.0, *) { 16 | statusBarFrame = view.window?.windowScene?.statusBarManager?.statusBarFrame ?? CGRect.zero 17 | } else { 18 | statusBarFrame = UIApplication.shared.statusBarFrame 19 | } 20 | let statusBarView = UIView(frame: statusBarFrame) 21 | statusBarView.backgroundColor = backgroundColor 22 | view.addSubview(statusBarView) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | **Current behavior** 19 | A clear and concise description of what is happening right now. 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | **Smartphone (please complete the following information):** 25 | - Device model: [e.g. iPhone 11 SE] 26 | - OS: [e.g. iOS 13.6] 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/Exposure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Exposure.swift 3 | // safesafe 4 | // 5 | // Created by Rafał Małczyński on 24/05/2020. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | final class Exposure: Object, LocalStorable { 12 | 13 | @objc dynamic var id = UUID().uuidString 14 | 15 | /// Total calculated risk, range is 0-4096 16 | @objc dynamic var risk: Int = .zero 17 | 18 | /// Exposure duration in seconds 19 | @objc dynamic var duration: Double = .zero 20 | 21 | /// Date of exposure 22 | @objc dynamic var date: Date = Date() 23 | 24 | convenience init( 25 | risk: Int, 26 | duration: Double, 27 | date: Date 28 | ) { 29 | self.init() 30 | self.risk = risk 31 | self.duration = duration 32 | self.date = date 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /safesafe/DependencyContainer/Factories/Components/PWA/PWAViewControllerFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PWAViewControllerFactory.swift 3 | // safesafe 4 | // 5 | 6 | import Foundation 7 | 8 | protocol PWAViewControllerFactory { 9 | func makePWAViewController() -> PWAViewController 10 | } 11 | 12 | extension DependencyContainer: PWAViewControllerFactory { 13 | 14 | func makePWAViewController() -> PWAViewController { 15 | let viewModel = PWAViewModel(with: jsBridge) 16 | let viewController = PWAViewController(viewModel: viewModel) 17 | viewModel.registerDebug { [weak self, viewController, viewModel] in 18 | guard let self = self else { return } 19 | 20 | viewController.present(self.makeDebugViewController(closeCallback: viewModel.didCloseDebugView), animated: true) 21 | } 22 | return viewController 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/JS/FreeTestSubscriptionInfoResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestSubscriptionInfoResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FreeTestSubscriptionInfoResponse: Codable, JSONRepresentable { 11 | 12 | static let empty = FreeTestSubscriptionInfoResponse() 13 | 14 | let subscription: Subscription? 15 | 16 | struct Subscription: Codable { 17 | let guid: String 18 | let status: FreeTestSubscriptionState 19 | let updated: Int 20 | } 21 | 22 | init () { 23 | self.subscription = nil 24 | } 25 | 26 | init(with guid: DeviceGUIDModel) { 27 | self.subscription = .init( 28 | guid: guid.uuid, 29 | status: guid.stateEnum, 30 | updated: guid.update 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /safesafe/Common/Extensions/Moya+PromiseKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Moya+PromiseKit.swift 3 | // safesafe 4 | // 5 | // Created by Namedix on 22/02/2021. 6 | // 7 | 8 | import Moya 9 | import PromiseKit 10 | 11 | extension MoyaProvider { 12 | func request( 13 | _ target: Target, 14 | callbackQueue: DispatchQueue? = .none, 15 | progress: ProgressBlock? = .none 16 | ) -> Promise { 17 | Promise { seal in 18 | request( 19 | target, 20 | callbackQueue: callbackQueue, 21 | progress: progress 22 | ) { result in 23 | switch result { 24 | case .success(let response): 25 | seal.fulfill(response) 26 | case .failure(let error): 27 | seal.reject(error) 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /safesafe/Services/Free test/Model/Database/DeviceGUIDModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceGUIDModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 21/10/2020. 6 | // 7 | 8 | import RealmSwift 9 | import Foundation 10 | 11 | class DeviceGUIDModel: Object, LocalStorable { 12 | 13 | static let identifier: Int = 22102020 14 | 15 | @objc dynamic var id: Int = DeviceGUIDModel.identifier 16 | @objc dynamic var uuid: String = .empty 17 | @objc dynamic var state: Int = .zero 18 | @objc dynamic var update: Int = .zero 19 | @objc dynamic var token: String? 20 | @objc dynamic var pinCode: String? 21 | 22 | override class func primaryKey() -> String? { "id" } 23 | 24 | var stateEnum: FreeTestSubscriptionState { 25 | guard let state = FreeTestSubscriptionState(rawValue: self.state) else { return .unknown } 26 | 27 | return state 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /safesafe/Common/Protocols/ViewModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModelType.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 09/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ViewModelType { 12 | // Initialize viewmodel here, it's called on View Controller init() 13 | func start() 14 | 15 | // View Controller Life Cycle Events 16 | func onViewDidLoad(setupFinished: Bool) 17 | func onViewWillAppear(layoutFinished: Bool) 18 | func onViewDidAppear() 19 | func onViewWillDisappear() 20 | func onViewDidDisappear() 21 | } 22 | 23 | extension ViewModelType { 24 | func onViewDidLoad(setupFinished: Bool) {} 25 | func onViewWillAppear(layoutFinished: Bool) {} 26 | func onViewDidAppear() {} 27 | func onViewWillDisappear() {} 28 | func onViewDidDisappear() {} 29 | func start() {} 30 | } 31 | -------------------------------------------------------------------------------- /safesafe/Resources/Live/Config-live.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BASE_URL 6 | https://storage.googleapis.com/cdn.safesafe.app/2 7 | EXPOSURE_NOTIFICATION 8 | 9 | EN_STORAGE_URL 10 | https://exp.safesafe.app/ 11 | EN_GAT_BASE_URL 12 | https://gat.safesafe.app/ 13 | EN_UDK_BASE_URL 14 | https://udk.safesafe.app/ 15 | 16 | FREE_TEST 17 | 18 | BASE_URL 19 | https://mb.safesafe.app 20 | 21 | PWA 22 | 23 | SCHEME 24 | https 25 | HOST 26 | v4.safesafe.app 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /safesafe/DependencyContainer/Factories/Services/ExposureNotificationJSBridgeFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureNotificationJSBridgeFactory.swift 3 | // safesafe 4 | // 5 | 6 | import UIKit 7 | 8 | @available(iOS 13.5, *) 9 | protocol ExposureNotificationJSBridgeFactory { 10 | 11 | func makeExposureNotificationJSBridge(with viewController: UIViewController) -> ExposureNotificationJSProtocol 12 | 13 | } 14 | 15 | @available(iOS 13.5, *) 16 | extension DependencyContainer: ExposureNotificationJSBridgeFactory { 17 | 18 | func makeExposureNotificationJSBridge(with viewController: UIViewController) -> ExposureNotificationJSProtocol { 19 | return ExposureNotificationJSBridge( 20 | exposureService: exposureService, 21 | exposureSummaryService: exposureSummaryService, 22 | exposureStatus: exposureService, 23 | viewController: viewController 24 | ) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/ExposureSummary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureSummary.swift 3 | // safesafe 4 | // 5 | // Created by Rafał Małczyński on 24/05/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ExposureSummary: Encodable { 11 | 12 | /** 13 | Risk score normalized to range of 1-3. 14 | 15 | Value is calculated based on **totalRiskScoreFullRange** from `ENExposureInfo`. 16 | Risk score equals: 17 | - 1, when `totalRiskScoreFullRange` is in `1-1499` 18 | - 2, when `totalRiskScoreFullRange` is in `1500-2999` 19 | - 3, when `totalRiskScoreFullRange` is in `3000-4096` 20 | */ 21 | let riskLevel: RiskLevel 22 | 23 | } 24 | 25 | extension ExposureSummary { 26 | 27 | /// Creates `ExposureSummary` based on values of `totalRiskScoreFullRange` from range 0-4096 28 | init(fromFullRangeScore score: Int) { 29 | self.riskLevel = .init(fromFullRangeScore: score) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /safesafe/Services/District/Models/LocalStorage/VoivodeshipStorageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VoivodeshipStorageModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 11/10/2020. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | final class VoivodeshipStorageModel: Object, LocalStorable { 12 | 13 | @objc dynamic var id: Int = .zero 14 | @objc dynamic var name: String = .empty 15 | @objc dynamic var updatedAt: Int = .zero 16 | @objc dynamic var order: Int = .zero 17 | let districts = LinkingObjects(fromType: DistrictStorageModel.self, property: "voivodeship") 18 | 19 | override class func primaryKey() -> String? { "id" } 20 | 21 | convenience init(with voivodeshipAPIModel: DistrictResponseModel.Voivodeship, index: Int, updatedAt: Int) { 22 | self.init() 23 | 24 | self.id = voivodeshipAPIModel.id 25 | self.name = voivodeshipAPIModel.name 26 | self.updatedAt = updatedAt 27 | self.order = index 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/ServiceStatusResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceStatusResponse.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 15/05/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ServicesResponse: Codable, JSONRepresentable { 11 | 12 | enum CodingKeys: String, CodingKey { 13 | case status = "servicesStatus" 14 | } 15 | 16 | let status: Status 17 | 18 | struct Status: Codable { 19 | enum CodingKeys: String, CodingKey { 20 | case isBluetoothOn = "isBtOn" 21 | case exposureNotificationStatus 22 | case isNotificationEnabled 23 | } 24 | 25 | let exposureNotificationStatus: ExposureNotificationStatus 26 | let isBluetoothOn: Bool 27 | let isNotificationEnabled: Bool 28 | 29 | enum ExposureNotificationStatus: Int, Codable { 30 | case on = 1 31 | case off 32 | case restricted 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '10.0' 3 | 4 | def pods_definition 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | pod 'SnapKit', '5.0.1' 9 | pod 'Firebase/Functions' 10 | pod 'Firebase/Messaging' 11 | pod 'Firebase/Auth' 12 | pod 'Firebase/RemoteConfig' 13 | pod 'PromiseKit', '~> 6.13' 14 | pod 'Moya', '~> 14.0' 15 | pod 'ZIPFoundation', '~> 0.9' 16 | pod 'KeychainAccess', '~> 4.2.0' 17 | pod 'TrustKit', '~> 1.6.5' 18 | pod 'Siren', '~> 5.4.0' 19 | pod 'RealmSwift', '~> 5.0.0' 20 | pod 'SwiftProtobuf', '~> 1.0' 21 | 22 | pod 'DBDebugToolkit', :configurations => ['Stage', 'StageDebug', 'LiveDebug'] 23 | 24 | end 25 | 26 | target 'safesafe' do 27 | pods_definition 28 | end 29 | 30 | post_install do |installer| 31 | installer.pods_project.build_configurations.each do |config| 32 | config.build_settings['CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER'] = 'NO' 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Notifications/Models/PushNotificationCovidStatsModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushNotificationCovidStatsModel.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 13/12/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct PushNotificationCovidStatsModel: Codable { 11 | let updated: Int 12 | let newCases: Int? 13 | let totalCases: Int? 14 | let newDeaths: Int? 15 | let totalDeaths: Int? 16 | let newRecovered: Int? 17 | let totalRecovered: Int? 18 | let newVaccinations: Int 19 | let totalVaccinations: Int 20 | let newVaccinationsDose1: Int 21 | let totalVaccinationsDose1: Int 22 | let newVaccinationsDose2: Int 23 | let totalVaccinationsDose2: Int 24 | 25 | var dictionary: [String: Any]? { 26 | guard let data = try? JSONEncoder().encode(self) else { 27 | return nil 28 | } 29 | return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { 30 | $0 as? [String: Any] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Documentation/ReportingRiskLevel.md: -------------------------------------------------------------------------------- 1 | # Reporting Risk Level 2 | 3 | Pass the highest risk level of user's exposure from last 14 days detected by Exposure Notification Framework to PWA. 4 | 5 | Steps: 6 | 7 | - PWA request reporting risk level 8 | - Service method: [JSBridge.onBridgeData(type:body:completion:)](../safesafe/Services/JavaScript%20Bridge/JSBridge.swift) 9 | - Data Type: [JSBridge.BridgeDataType.exposureList](../safesafe/Services/JavaScript%20Bridge/JSBridge.swift) 10 | - Get all data about exposures from local database 11 | - Service method: [ExposureSummaryService.getExposureSummary()](../safesafe/Services/ExposureNotification/ExposureSummaryService.swift) 12 | - Calc risk level from risk score (max **riskScore** = 4096): 13 | - Used model: [ExposureSummary](../safesafe/Services/ExposureNotification/Models/ExposureSummary.swift) 14 | - Pass calculated risk level to PWA by returning json with result model back through JSBridge 15 | - Service method: [JSBridge.exposureListGetBridgeDataResponse(requestID:)](../safesafe/Services/JavaScript%20Bridge/JSBridge.swift) -------------------------------------------------------------------------------- /safesafe/Resources/safesafe Debug.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.associated-domains 8 | 9 | applinks:safesafe.app 10 | applinks:*.safesafe.app 11 | applinks:www.gov.pl 12 | applinks:*www.gov.pl 13 | activitycontinuation:www.gov.pl 14 | activitycontinuation:*www.gov.pl 15 | applinks:gov.pl 16 | applinks:*gov.pl 17 | activitycontinuation:gov.pl 18 | activitycontinuation:*gov.pl 19 | 20 | com.apple.developer.exposure-notification 21 | 22 | com.apple.security.application-groups 23 | 24 | group.pl.gov.mc.protegosafe 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /safesafe/Resources/safesafe Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | production 7 | com.apple.developer.associated-domains 8 | 9 | applinks:safesafe.app 10 | applinks:*.safesafe.app 11 | applinks:www.gov.pl 12 | applinks:*www.gov.pl 13 | activitycontinuation:www.gov.pl 14 | activitycontinuation:*www.gov.pl 15 | applinks:gov.pl 16 | applinks:*gov.pl 17 | activitycontinuation:gov.pl 18 | activitycontinuation:*gov.pl 19 | 20 | com.apple.developer.exposure-notification 21 | 22 | com.apple.security.application-groups 23 | 24 | group.pl.gov.mc.protegosafe 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /safesafe/Common/Extensions/String/String+localized.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+localized.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 04/08/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | 12 | private enum Constants { 13 | static let bundleNotFoundPrefix = "BNF_" 14 | static var localDirectoryName = "translations" 15 | } 16 | 17 | func localized(_ languageCode: String = LanguageController.selected, comment: String = .empty) -> String { 18 | guard let path = Bundle.main.path(forResource: Constants.localDirectoryName, ofType: nil), 19 | let bundle = Bundle(path: path) 20 | else { return "\(Constants.bundleNotFoundPrefix)\(languageCode)_\(self)" } 21 | 22 | let localized = NSLocalizedString(self, tableName: languageCode.lowercased(), bundle: bundle, value: .empty, comment: comment) 23 | if localized == self { 24 | return NSLocalizedString(self, tableName: LanguageController.default, bundle: bundle, value: .empty, comment: comment) 25 | } 26 | 27 | return localized 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Protobuf/ProtobufWorker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProtobufWorker.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 06/12/2020. 6 | // 7 | 8 | import Foundation 9 | import SwiftProtobuf 10 | 11 | final class ProtobufWorker { 12 | 13 | private enum Constants { 14 | static let minDataLengthBytes: Int = 16 15 | } 16 | 17 | func countAllKeys(urls: [URL]) -> Int { 18 | var allKeysCount: Int = .zero 19 | let binURLs = urls.filter { $0.pathExtension == "bin" } 20 | 21 | for url in binURLs { 22 | allKeysCount += countKeys(url: url) 23 | } 24 | 25 | return allKeysCount 26 | } 27 | 28 | private func countKeys(url: URL) -> Int { 29 | guard 30 | let data = try? Data(contentsOf: url), 31 | data.count > Constants.minDataLengthBytes, 32 | let decodedInfo = try? TemporaryExposureKeyExport(serializedData: data[Constants.minDataLengthBytes...]) 33 | else { 34 | return .zero 35 | } 36 | 37 | return decodedInfo.keys.count 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /safesafe/Services/KeychainService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyChain.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 24/05/2020. 6 | // 7 | 8 | import Foundation 9 | import KeychainAccess 10 | 11 | final class KeychainService { 12 | static let shared = KeychainService() 13 | 14 | private let keychain: Keychain 15 | 16 | private init() { 17 | self.keychain = Keychain(service: Bundle.main.bundleIdentifier ?? "pl.gov.mc.protegosafe") 18 | } 19 | 20 | func set(value: String, for key: Key) { 21 | try? keychain.set(value, key: key.name) 22 | } 23 | 24 | func set(data: Data, for key: Key) { 25 | try? keychain.set(data, key: key.name) 26 | } 27 | 28 | func getValue(for key: Key) -> String? { 29 | return try? keychain.get(key.name) 30 | } 31 | 32 | func getData(for key: Key) -> Data? { 33 | return try? keychain.getData(key.name) 34 | } 35 | } 36 | 37 | extension KeychainService { 38 | struct Key { 39 | let name: String 40 | 41 | init(_ name: String) { 42 | self.name = name 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sdasdasdas/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | sdasdasdas 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionPointIdentifier 26 | com.apple.usernotifications.service 27 | NSExtensionPrincipalClass 28 | $(PRODUCT_MODULE_NAME).NotificationService 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /safesafe/Networking/InfoTarget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoTarget.swift 3 | // safesafe 4 | // 5 | // Created by Namedix on 23/02/2021. 6 | // 7 | 8 | import Moya 9 | 10 | enum InfoTarget { 11 | case fetchTimestamps 12 | case fetchDashboard 13 | case fetchDetails 14 | case fetchDistricts 15 | } 16 | 17 | extension InfoTarget: TargetType { 18 | var baseURL: URL { URL(string: ConfigManager.default.baseURL)! } 19 | 20 | var path: String { 21 | switch self { 22 | case .fetchTimestamps: 23 | return "/timestamps.json" 24 | case .fetchDashboard: 25 | return "/dashboard.json" 26 | case .fetchDetails: 27 | return "/details.json" 28 | case .fetchDistricts: 29 | return "/districts.json" 30 | } 31 | } 32 | 33 | var method: Method { 34 | return .get 35 | } 36 | 37 | var sampleData: Data { .init() } 38 | 39 | var task: Task { 40 | return .requestParameters( 41 | parameters: ["randomSeed": "\(arc4random())"], 42 | encoding: URLEncoding.default 43 | ) 44 | } 45 | 46 | var headers: [String : String]? { nil } 47 | } 48 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/ExposureHistoryRiskCheckAgregatedResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureHistoryRiskCheckAgregatedResponse.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 13/12/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ExposureHistoryRiskCheckAgregatedResponse: Codable, JSONRepresentable { 11 | let riskCheck: Stats? 12 | 13 | init(with model: ExposureHistoryRiskCheckAgregated?) { 14 | if let model = model { 15 | self.riskCheck = .init(with: model) 16 | } else { 17 | self.riskCheck = nil 18 | } 19 | } 20 | 21 | struct Stats: Codable { 22 | let lastRiskCheckTimestamp: Int 23 | let todayKeysCount: Int 24 | let last7daysKeysCount: Int 25 | let totalKeysCount: Int 26 | 27 | init(with model: ExposureHistoryRiskCheckAgregated) { 28 | self.lastRiskCheckTimestamp = model.lastRiskCheckTimestamp 29 | self.todayKeysCount = model.todayKeysCount 30 | self.last7daysKeysCount = model.last7daysKeysCount 31 | self.totalKeysCount = model.totalKeysCount 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/Historical data/ExposureHistoryAnalyzeCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureRiskCheck.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 07/12/2020. 6 | // 7 | 8 | import RealmSwift 9 | import Foundation 10 | 11 | final class ExposureHistoryAnalyzeCheck: Object, LocalStorable { 12 | 13 | struct EncodableModel: Encodable { 14 | let id: String 15 | let timestamp: Int 16 | let exposures: Int 17 | let keys: Int 18 | } 19 | 20 | @objc dynamic var id: String = UUID().uuidString 21 | @objc dynamic var date = Date() 22 | @objc dynamic var matchedKeyCount: Int = .zero 23 | @objc dynamic var keysCount: Int = .zero 24 | 25 | override class func primaryKey() -> String? { "id" } 26 | 27 | convenience init(matchedKeyCount: Int, keysCount: Int) { 28 | self.init() 29 | 30 | self.matchedKeyCount = matchedKeyCount 31 | self.keysCount = keysCount 32 | } 33 | 34 | func asEncodable() -> EncodableModel { 35 | .init(id: id, timestamp: Int(date.timeIntervalSince1970), exposures: matchedKeyCount, keys: keysCount) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /safesafe/Models/DashboardStatsAPIResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DashboardStatsResponse.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 11/12/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DashboardStatsAPIResponse: Codable { 11 | let updated: Int 12 | let newCases: Int? 13 | let totalCases: Int? 14 | let newDeaths: Int? 15 | let totalDeaths: Int? 16 | let newRecovered: Int? 17 | let totalRecovered: Int? 18 | let newVaccinations: Int 19 | let totalVaccinations: Int 20 | let newVaccinationsDose1: Int 21 | let totalVaccinationsDose1: Int 22 | let newVaccinationsDose2: Int 23 | let totalVaccinationsDose2: Int 24 | let newTests: Int 25 | let newDeathsWithoutComorbidities: Int 26 | let newDeathsWithComorbidities: Int 27 | let newUndesirableReaction: Int 28 | let totalUndesirableReaction: Int 29 | 30 | var dictionary: [String: Any]? { 31 | guard let data = try? JSONEncoder().encode(self) else { 32 | return nil 33 | } 34 | return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { 35 | $0 as? [String: Any] 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Documentation/ReceivingExposuresInformation.md: -------------------------------------------------------------------------------- 1 | # Receiving Exposures Documentation 2 | 3 | When Exposure Notification Framework finishes comparing diagnosis keys with keys stored on the device and it detects some exposures when **ExposureService.detectExposures()** is called, then result of this method will contain potential exposures. In order to provide user a correct risk level that is shown in the PWA module application has to get detailed information about detected exposures and save them to the local, encrypted database. 4 | 5 | Steps: 6 | 7 | - Start by detecting exposures. 8 | - Service method: [ExposureService.detectExposures()](../safesafe/Services/ExposureNotification/ExposureService.swift) 9 | - The list of **Exposure** objects is provided, based on list of **ENExposureDetectionSummary** objects. 10 | - The app saves every exposure information to **encrypted** database with **only** the following data: 11 | - Day level resolution that the exposure occurred 12 | - Length of exposure (value is stored in seconds) 13 | - The total risk score calculated for the exposure 14 | - Service method: [ExposureSummaryService.getExposureSummary()](../safesafe/Services/ExposureNotification/ExposureSummaryService.swift) -------------------------------------------------------------------------------- /PushMutableContent/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | PushMutableContent 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSExtension 24 | 25 | NSExtensionPointIdentifier 26 | com.apple.usernotifications.service 27 | NSExtensionPrincipalClass 28 | $(PRODUCT_MODULE_NAME).NotificationService 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /safesafe/DependencyContainer/Factories/Components/Debug/DebugViewControllerFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DebugViewControllerFactory.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 18/08/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol DebugViewControllerFactory { 11 | func makeDebugViewController(closeCallback: @escaping () -> Void) -> DebugViewController 12 | } 13 | 14 | extension DependencyContainer: DebugViewControllerFactory { 15 | func makeDebugViewController(closeCallback: @escaping () -> Void) -> DebugViewController { 16 | let viewModel = DebugViewModel( 17 | districtService: districtsService, 18 | localStorage: realmLocalStorage, 19 | exposureService: exposureServiceDebug, 20 | fileStorage: fileStorage 21 | ) 22 | 23 | viewModel.onSimulateExposureRiskChange { [weak self] in 24 | if #available(iOS 13.5, *) { 25 | self?.jsBridge.debugSendExposureList() 26 | } 27 | } 28 | 29 | let viewController = DebugViewController(viewModel: viewModel) 30 | viewController.closeCallback = closeCallback 31 | 32 | return viewController 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /safesafe/Services/DeviceCheckService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceCheckService.swift 3 | // safesafe 4 | // 5 | 6 | import DeviceCheck 7 | import PromiseKit 8 | 9 | protocol DeviceCheckServiceProtocol { 10 | 11 | func generatePayload() -> Promise 12 | 13 | } 14 | 15 | final class DeviceCheckService: DeviceCheckServiceProtocol { 16 | 17 | // MARK: - Properties 18 | 19 | let dcDevice: DCDeviceProtocol 20 | 21 | // MARK: - Life Cycle 22 | 23 | init(with dcDevice: DCDeviceProtocol = DCDevice.current) { 24 | self.dcDevice = dcDevice 25 | } 26 | 27 | // MARK: - ExposureNotification payload generation 28 | 29 | func generatePayload() -> Promise { 30 | Promise { seal in 31 | dcDevice.generateToken { token, error in 32 | guard let token = token else { 33 | seal.reject(InternalError.deviceCheckTokenGenerationFailed) 34 | return 35 | } 36 | if let error = error { 37 | seal.reject(error) 38 | return 39 | } 40 | 41 | seal.fulfill(token.base64EncodedString()) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: build and deploy 4 | on: 5 | push: 6 | branches: 7 | - master 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [10.x] 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v2 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm ci 22 | - run: npm run build --if-present 23 | - run: cp firebase.json build/ 24 | - name: Archive Production Artifact 25 | uses: actions/upload-artifact@master 26 | with: 27 | name: build 28 | path: build 29 | deploy: 30 | needs: build 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout Repo 34 | uses: actions/checkout@v2 35 | - name: Download Artifact 36 | uses: actions/download-artifact@master 37 | with: 38 | name: build 39 | - name: Deploy to Production Firebase 40 | uses: w9jds/firebase-action@master 41 | with: 42 | args: deploy --only hosting 43 | env: 44 | GCP_SA_KEY: ${{ secrets.FIREBASE_HOSTING_SAFE }} 45 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/Models/Historical data/ExposureHistoryRiskCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureHistoryRiskCheck.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 07/12/2020. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | final class ExposureHistoryRiskCheck: Object, LocalStorable { 12 | 13 | struct EncodableModel: Encodable { 14 | let id: String 15 | let timestamp: Int 16 | let exposures: Int 17 | let riskLevel: Int 18 | } 19 | 20 | @objc dynamic var id: String = UUID().uuidString 21 | @objc dynamic var date = Date() 22 | @objc dynamic var matchedKeyCount: Int = .zero 23 | @objc dynamic var riskLevelFull: Int = .zero 24 | 25 | override class func primaryKey() -> String? { "id" } 26 | 27 | convenience init(matchedKeyCount: Int, riskLevelFull: Int) { 28 | self.init() 29 | self.matchedKeyCount = matchedKeyCount 30 | self.riskLevelFull = riskLevelFull 31 | } 32 | 33 | func asEncodable() -> EncodableModel { 34 | let level = RiskLevel(fromFullRangeScore: riskLevelFull) 35 | 36 | return .init(id: id, timestamp: Int(date.timeIntervalSince1970), exposures: matchedKeyCount, riskLevel: level.rawValue) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/stage-nodejs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: stage build and deploy 4 | on: 5 | push: 6 | tags: 7 | - release/*.*.* 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [10.x] 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v2 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm ci 22 | - run: npm run build --if-present 23 | - run: cp stage-firebase.json build/firebase.json 24 | - name: Archive Production Artifact 25 | uses: actions/upload-artifact@master 26 | with: 27 | name: build 28 | path: build 29 | deploy: 30 | needs: build 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout Repo 34 | uses: actions/checkout@v2 35 | - name: Download Artifact 36 | uses: actions/download-artifact@master 37 | with: 38 | name: build 39 | - name: Deploy to Stage Firebase 40 | uses: w9jds/firebase-action@master 41 | with: 42 | args: deploy --only hosting 43 | env: 44 | GCP_SA_KEY: ${{ secrets.FIREBASE_HOSTING_SAFE 45 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew install fastlane` 16 | 17 | # Available Actions 18 | ### uats 19 | ``` 20 | fastlane uats 21 | ``` 22 | 23 | ### adhocs 24 | ``` 25 | fastlane adhocs 26 | ``` 27 | 28 | ### live_appstore 29 | ``` 30 | fastlane live_appstore 31 | ``` 32 | 33 | ### stage_appstore 34 | ``` 35 | fastlane stage_appstore 36 | ``` 37 | 38 | ### stage_screencast_appstore 39 | ``` 40 | fastlane stage_screencast_appstore 41 | ``` 42 | 43 | ### stage_adhoc 44 | ``` 45 | fastlane stage_adhoc 46 | ``` 47 | 48 | ### stage_screencast_adhoc 49 | ``` 50 | fastlane stage_screencast_adhoc 51 | ``` 52 | 53 | ### live_adhoc 54 | ``` 55 | fastlane live_adhoc 56 | ``` 57 | 58 | ### dev_adhoc 59 | ``` 60 | fastlane dev_adhoc 61 | ``` 62 | 63 | 64 | ---- 65 | 66 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 67 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 68 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 69 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | ## Issues - Features, Bugs and Security: 2 | 3 | Please create new Issues in apropriate repository only using the correct issue template type: 4 | - [Pull Requests](PULL_REQUEST_TEMPLATE/README.md) 5 | - [Features](ISSUE_TEMPLATE/feature_request.md) 6 | - [Bugs](ISSUE_TEMPLATE/bug_report.md) 7 | - [Security](SECURITY.md) 8 | 9 | New GitHub issues may be automatically closed/deleted if not related or not compatible with the templates provided. 10 | 11 | ## New Issue templates: 12 | 13 | If you would like to provide a new Issue template please create a [Pull Request](PULL_REQUEST_TEMPLATE/README.md) relating to at least one Issue. 14 | 15 | ## Community support 16 | **Please do not use GitHub Issues for generic support questions or unreleated discussions. Instead please use Stack Overflow:** 17 | 18 | - https://stackoverflow.com/questions/tagged/ios 19 | - https://stackoverflow.com/questions/tagged/android 20 | - https://stackoverflow.com/questions/tagged/chrome 21 | - https://stackoverflow.com/questions/tagged/firefox 22 | - https://stackoverflow.com/questions/tagged/ProteGO-Safe 23 | - https://stackoverflow.com/questions/tagged/ProteGO-Safe-ios 24 | - https://stackoverflow.com/questions/tagged/ProteGO-Safe-android 25 | - https://stackoverflow.com/questions/tagged/ProteGO-Safe-web 26 | - https://stackoverflow.com/questions/tagged/ProteGO-Safe-backend 27 | -------------------------------------------------------------------------------- /safesafe/Services/StoredDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoredDefaults.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 09/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public final class StoredDefaults { 12 | 13 | private enum Constants { 14 | static let appGroupIdentifier = "group.pl.gov.mc.protegosafe" 15 | } 16 | 17 | static let standard = StoredDefaults() 18 | 19 | public struct Key { 20 | let name: String 21 | 22 | init(_ name: String) { 23 | self.name = name 24 | } 25 | } 26 | 27 | 28 | func set(value: Any, key: Key, useAppGroup: Bool = false) { 29 | defaults(useAppGroup).set(value, forKey: key.name) 30 | } 31 | 32 | func get(key: Key, useAppGroup: Bool = false) -> T? { 33 | defaults(useAppGroup).value(forKey: key.name) as? T 34 | } 35 | 36 | func delete(key: Key, useAppGroup: Bool = false) { 37 | defaults(useAppGroup).removeObject(forKey: key.name) 38 | } 39 | 40 | private func defaults(_ useAppGroup: Bool = false) -> UserDefaults { 41 | if useAppGroup, let appGroupDefaults = UserDefaults(suiteName: Constants.appGroupIdentifier) { 42 | return appGroupDefaults 43 | } 44 | 45 | return .standard 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /safesafe/Services/OpenerService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenerService.swift 3 | // safesafe 4 | // 5 | // Created by Adam Tokarczyk on 23/06/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol OpenerServiceType: AnyObject { 11 | func open(_ route: OpenerService.Route) 12 | } 13 | 14 | 15 | final class OpenerService: OpenerServiceType { 16 | 17 | // MARK: - Properties 18 | 19 | private let appSharedPreferences: UIApplication 20 | 21 | enum Route { 22 | case settingsUrl 23 | case smsUrl(number: String, message: String) 24 | 25 | var url: URL { 26 | switch self { 27 | case .settingsUrl: 28 | return URL(string: UIApplication.openSettingsURLString)! 29 | case .smsUrl(let number, let message): 30 | return URL(string: "sms:\(number)&body=\(message)")! 31 | } 32 | } 33 | } 34 | 35 | // MARK: - Life Cycle 36 | 37 | public init(appSharedPreferences: UIApplication = .shared) { 38 | self.appSharedPreferences = appSharedPreferences 39 | } 40 | 41 | // MARK: - Methods 42 | 43 | func open(_ route: Route) { 44 | if appSharedPreferences.canOpenURL(route.url) { 45 | appSharedPreferences.open(route.url, completionHandler: nil) 46 | } else { 47 | console("Can't open url: \(route.url)") 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/JSBridge+BridgeDataType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSBridge+BridgeDataType.swift 3 | // safesafe 4 | // 5 | // Created by Namedix on 17/02/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | extension JSBridge { 11 | enum BridgeDataType: Int { 12 | case dailyTopicUnsubscribe = 1 13 | case applicationLifecycle = 11 14 | case notificationsPermission = 35 15 | case serviceStatus = 51 16 | case setServices = 52 17 | case clearData = 37 18 | case uploadTemporaryExposureKeys = 43 19 | 20 | case exposureList = 61 21 | case appVersion = 62 22 | case systemLanguage = 63 23 | case clearExposureRisk = 66 24 | case requestAppReview = 68 25 | case route = 69 26 | 27 | case allDistricts = 70 28 | case districtsAPIFetch = 71 29 | case districtAction = 72 30 | case subscribedDistricts = 73 31 | 32 | case freeTestPinUpload = 80 33 | case freeTestSubscriptionInfo = 81 34 | case freeTestPinCodeFetch = 82 35 | 36 | case historicalData = 90 37 | case historicalDataRemove = 91 38 | 39 | case covidStatsSubscription = 100 40 | case setCovidStatsSubscription = 101 41 | case dashboardStats = 102 42 | case agregatedStats = 103 43 | 44 | case detailsStats = 104 45 | 46 | case sendSMS = 105 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/NetworkMonitoring.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Network.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 17/06/2020. 6 | // 7 | 8 | import UIKit 9 | import Network 10 | 11 | final class NetworkMonitoring { 12 | static let shared = NetworkMonitoring() 13 | 14 | var isInternetAvailable: Bool { 15 | return networkPath.currentPath.status == .satisfied 16 | } 17 | private let networkPath: NWPathMonitor 18 | 19 | func start() { 20 | networkPath.start(queue: DispatchQueue.global(qos: .background)) 21 | } 22 | 23 | func showInternetAlert(in viewController: UIViewController, action: @escaping (AlertAction) -> ()) { 24 | let alert = UIAlertController(title: "INTERNET_CONNECTION_ALERT_TITLE".localized(), message: "INTERNET_CONNECTION_ALERT_MESSAGE".localized(), preferredStyle: .alert) 25 | let retryAction = UIAlertAction(title: "RETRY_BUTTON_TITLE".localized(), style: .default) { _ in 26 | action(.retry) 27 | } 28 | let cancelAction = UIAlertAction(title: "CANCEL_BUTTON_TITLE".localized(), style: .cancel) { _ in 29 | action(.cancel) 30 | } 31 | alert.addAction(retryAction) 32 | alert.addAction(cancelAction) 33 | 34 | viewController.present(alert, animated: true) 35 | } 36 | 37 | private init() { 38 | self.networkPath = NWPathMonitor() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /safesafe/Services/Permissions/ExposureNotificationPermission.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureNotificationPermission.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 15/05/2020. 6 | // 7 | 8 | import Foundation 9 | import PromiseKit 10 | import ExposureNotification 11 | 12 | final class ExposureNotificationPermission: PermissionType { 13 | 14 | func state(shouldAsk: Bool) -> Promise { 15 | return readState() 16 | } 17 | 18 | private func readState() -> Promise { 19 | if #available(iOS 13.5, *) { 20 | switch ENManager.authorizationStatus { 21 | case .authorized: 22 | return .value(.authorized) 23 | case .notAuthorized, .restricted: 24 | return .value(.rejected) 25 | case .unknown: 26 | return .value(.unknown) 27 | default: 28 | return .value(.cantUse) 29 | } 30 | } else { 31 | return .value(.cantUse) 32 | } 33 | } 34 | } 35 | 36 | extension Permissions.State { 37 | var asJSBridgeStatus: ServicesResponse.Status.ExposureNotificationStatus { 38 | switch self { 39 | case .authorized: 40 | return .on 41 | case .rejected, .neverAsked, .unknown: 42 | return .off 43 | default: 44 | return .restricted 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sdasdasdas/NotificationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationService.swift 3 | // sdasdasdas 4 | // 5 | // Created by Łukasz Szyszkowski on 19/11/2020. 6 | // 7 | 8 | import UserNotifications 9 | 10 | class NotificationService: UNNotificationServiceExtension { 11 | 12 | var contentHandler: ((UNNotificationContent) -> Void)? 13 | var bestAttemptContent: UNMutableNotificationContent? 14 | 15 | override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { 16 | self.contentHandler = contentHandler 17 | bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) 18 | 19 | if let bestAttemptContent = bestAttemptContent { 20 | // Modify the notification content here... 21 | bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" 22 | 23 | contentHandler(bestAttemptContent) 24 | } 25 | } 26 | 27 | override func serviceExtensionTimeWillExpire() { 28 | // Called just before the extension will be terminated by the system. 29 | // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. 30 | if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { 31 | contentHandler(bestAttemptContent) 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /safesafe/Common/Error/InternalError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InternalError.swift 3 | // safesafe Live 4 | // 5 | // Created by Rafał Małczyński on 20/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum InternalError: Error { 12 | 13 | // Common 14 | case deinitialized 15 | case nilValue 16 | case timeout 17 | 18 | // Login 19 | case signInFailed 20 | 21 | // AppManagerStatus 22 | case serializationFailed 23 | 24 | // JSON 25 | case jsonSerializingData 26 | 27 | // Files & Folders 28 | case locatingDictionary 29 | case writingToFile 30 | case extractingDirectoryName 31 | 32 | // Onboarding 33 | case waitingForUser 34 | case notManagableYet 35 | 36 | // DeviceCheck 37 | case deviceCheckTokenGenerationFailed 38 | 39 | // Remote config 40 | case remoteConfigNotExistingKey 41 | case remoteActivate 42 | case remoteUnknownStatus 43 | 44 | // Type casting 45 | case invalidDataType 46 | 47 | // Keychain 48 | case keychainKeyNotExists 49 | 50 | // Networking 51 | case cantMakeRequest 52 | case noInternet 53 | 54 | // Uplod 55 | case uploadValidation 56 | 57 | // Get diagnosis keys 58 | case shareKeysUserCanceled 59 | 60 | // Detect exposure 61 | case detectExposuresNoKeys 62 | 63 | // Free test 64 | case freeTestPinUploadFailed 65 | } 66 | -------------------------------------------------------------------------------- /safesafe/Language/LanguageController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LanguageController.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 04/08/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct LanguageChangeModel { 11 | let fromLanguage: String 12 | let toLanguage: String 13 | } 14 | 15 | enum LanguageController { 16 | enum Constants { 17 | static var defaultLanguage = "pl" 18 | } 19 | 20 | static let `default` = Constants.defaultLanguage 21 | static var selected: String { StoredDefaults.standard.get(key: .selectedLanguage) ?? systemLanguage } 22 | 23 | static let systemLanguage = Locale.current.languageCode ?? Constants.defaultLanguage 24 | 25 | static func update(languageCode: String) { 26 | let oldLanguage = selected 27 | 28 | StoredDefaults.standard.set(value: languageCode, key: .selectedLanguage) 29 | StoredDefaults.standard.set(value: languageCode, key: .selectedLanguage, useAppGroup: true) 30 | 31 | NotificationCenter.default.post(name: .languageDidChange, object: LanguageChangeModel(fromLanguage: oldLanguage, toLanguage: languageCode)) 32 | } 33 | } 34 | 35 | extension Notification.Name { 36 | static let languageDidChange = Notification.Name(rawValue: "languageDidChangeNotification") 37 | } 38 | 39 | extension StoredDefaults.Key { 40 | static let selectedLanguage = StoredDefaults.Key("selectedLanguage") 41 | static let defaultLanguage = StoredDefaults.Key("defaultLanguage") 42 | } 43 | -------------------------------------------------------------------------------- /Documentation/DownloadingDiagnosisKeys.md: -------------------------------------------------------------------------------- 1 | # Downloading Diagnosis Keys 2 | 3 | A regularly scheduled background task is executed to download files with batch of Temporary Exposure Keys (**TEK**) of positively diagnosed, called Diagnosis Keys files. Each of the batch file has unique timestamp. The timestamp that identifying when a certain Diagnosis Keys file was created is used to select only not yet analyzed files for download. 4 | 5 | Steps: 6 | - The app gets a list of available Diagnosis Keys (`index.txt`) files from CDN: 7 | - Moya Target endpoint: [`case get`](../safesafe/Networking/ExposureKeysTarget.swift) 8 | - Service function: [`DiagnosisKeysDownloadServiceProtocol.download() -> Promise<[URL]>`](../safesafe/Services/ExposureNotification/DiagnosisKeysDownloadService.swift) 9 | - Only files with the timestamp older than the latest successfully provided to analyze batch are selected for download 10 | - Files are downloaded over HTTPS protocol to internal device storage 11 | - Moya Target endpoint: [`case download(fileName: String, destination: DownloadDestination)`](../safesafe/Networking/ExposureKeysTarget.swift) 12 | - Service function: [`DiagnosisKeysDownloadService.downloadFiles(withNames names: [String], keysDirectoryURL: URL) -> Promise<[URL]>`](../safesafe/Services/ExposureNotification/DiagnosisKeysDownloadService.swift) 13 | - Downloaded zip archives are decompressed to provide flat array of URL pairs: 14 | - `export.bin` - the binary containing Diagnosis Keys 15 | - `export.sig` - the raw signature and information needed for verification 16 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/WebCacheCleaner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebCacheCleaner.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 04/06/2020. 6 | // 7 | 8 | import Foundation 9 | import WebKit 10 | import PromiseKit 11 | 12 | final class WebCacheCleaner { 13 | 14 | @discardableResult 15 | class func clean() -> Promise { 16 | return Promise { seal in 17 | let dispatchGroup = DispatchGroup() 18 | HTTPCookieStorage.shared.removeCookies(since: Date.distantPast) 19 | 20 | WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in 21 | records.forEach { record in 22 | dispatchGroup.enter() 23 | WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: { dispatchGroup.leave() }) 24 | } 25 | } 26 | 27 | dispatchGroup.notify(queue: .main) { 28 | seal.fulfill(()) 29 | } 30 | } 31 | } 32 | 33 | class func removeLocalStorage() { 34 | do { 35 | let dir = try Directory.webkitLocalStorage() 36 | let content = try FileManager.default.contentsOfDirectory(atPath: dir.path) 37 | for path in content { 38 | let fullPath = dir.appendingPathComponent(path) 39 | try FileManager.default.removeItem(at: fullPath) 40 | } 41 | } catch { console(error) } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /safesafe/Services/JailbreakService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JailbreakService.swift 3 | // safesafe 4 | // 5 | // Created by Rafał Małczyński on 03/06/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol JailbreakServiceProtocol { 11 | 12 | var isJailbroken: Bool { get } 13 | 14 | } 15 | 16 | final class JailbreakService: JailbreakServiceProtocol { 17 | 18 | var isJailbroken: Bool { 19 | #if TARGET_IOS_SIMULATOR 20 | return true 21 | #endif 22 | 23 | let fileManager = FileManager.default 24 | var result = false 25 | 26 | unsafePaths 27 | .map { fileManager.fileExists(atPath: $0) } 28 | .first(where: { $0 == true }) 29 | .map { _ in result = true } 30 | 31 | _ = unsafePaths.map { 32 | let file = fopen($0, "r") 33 | 34 | if file != nil { 35 | fclose(file) 36 | result = true 37 | } 38 | } 39 | 40 | do { 41 | try "jailbreak_test".write(toFile: "/private/jailbreak.txt", atomically: true, encoding: .utf8) 42 | result = true 43 | } catch { } 44 | 45 | return result 46 | } 47 | 48 | private let unsafePaths = [ 49 | "/Applications/Cydia.app", 50 | "/Library/MobileSubstrate/MobileSubstrate.dylib", 51 | "/bin/bash", 52 | "/usr/sbin/sshd", 53 | "/etc/apt", 54 | "/usr/bin/ssh", 55 | "/private/var/lib/apt/" 56 | ] 57 | 58 | } 59 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/HiderController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HiderController.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 06/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | final class HiderController { 11 | 12 | private enum Constants { 13 | static let hiderStoryboard = "Hider" 14 | static let windowLevel: UIWindow.Level = .alert + 1 15 | } 16 | 17 | static let shared = HiderController() 18 | 19 | private var privacyProtectionWindow: UIWindow? 20 | private var hiderViewController: UIViewController { 21 | return UIStoryboard(name: Constants.hiderStoryboard, bundle: nil).instantiateViewController(withIdentifier: HiderViewController.identifier) 22 | } 23 | 24 | private init() {} 25 | 26 | @available(iOS 13.0, *) 27 | func show(windowScene: UIWindowScene?) { 28 | guard let windowScene = windowScene else { 29 | return 30 | } 31 | privacyProtectionWindow = UIWindow(windowScene: windowScene) 32 | setup() 33 | } 34 | 35 | func show() { 36 | privacyProtectionWindow = UIWindow(frame: UIScreen.main.bounds) 37 | setup() 38 | } 39 | 40 | func hide() { 41 | privacyProtectionWindow?.isHidden = true 42 | privacyProtectionWindow = nil 43 | } 44 | 45 | private func setup() { 46 | privacyProtectionWindow?.rootViewController = hiderViewController 47 | privacyProtectionWindow?.windowLevel = Constants.windowLevel 48 | privacyProtectionWindow?.makeKeyAndVisible() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Notifications/Models/PushNotificationHistoryModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushNotificationHistoryModel.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 06/12/2020. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | final class PushNotificationHistoryModel: Object, LocalStorable { 12 | 13 | struct EncodableModel: Encodable { 14 | let id: String 15 | let timestamp: Int 16 | let title: String 17 | let content: String 18 | } 19 | 20 | @objc dynamic var id: String = .empty 21 | @objc dynamic var timestamp: Int = .zero 22 | @objc dynamic var title: String = .empty 23 | @objc dynamic var content: String = .empty 24 | @objc dynamic var route: String? = nil 25 | 26 | override class func primaryKey() -> String? { "id" } 27 | 28 | convenience init?(object: [String: Any], keys: NotificationUserInfoParser.Key.Type) { 29 | self.init() 30 | 31 | guard 32 | let id = object[keys.id.rawValue] as? String, 33 | let timestamp = object[keys.timestamp.rawValue] as? Int, 34 | let title = object[keys.title.rawValue] as? String, 35 | let content = object[keys.content.rawValue] as? String 36 | else { 37 | return nil 38 | } 39 | 40 | self.id = id 41 | self.timestamp = timestamp 42 | self.title = title 43 | self.content = content 44 | self.route = object[keys.route.rawValue] as? String 45 | } 46 | 47 | func asEncodable() -> EncodableModel { 48 | .init(id: id, timestamp: timestamp, title: title, content: content) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /safesafe/Networking/FreeTestTarget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FreeTestTarget.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 22/10/2020. 6 | // 7 | 8 | import Moya 9 | 10 | enum FreeTestTarget { 11 | case createSubscription(header: FreeTestRequestHeader, request: FreeTestCreateSubscriptionRequestModel) 12 | case getSubscription(header: FreeTestRequestHeader, request: FreeTestGetSubscriptionRequestModel) 13 | } 14 | 15 | extension FreeTestTarget: TargetType { 16 | var baseURL: URL { 17 | URL(string: ConfigManager.default.freeTestBaseURL)! 18 | } 19 | 20 | var path: String { 21 | switch self { 22 | case .createSubscription: 23 | return "/createSubscription" 24 | case .getSubscription: 25 | return "/getSubscription" 26 | } 27 | } 28 | 29 | var method: Method { 30 | .post 31 | } 32 | 33 | var sampleData: Data { 34 | Data() 35 | } 36 | 37 | var task: Task { 38 | switch self { 39 | case .createSubscription(header: _, request: let request): 40 | return .requestJSONEncodable(request) 41 | case .getSubscription(header:_, request: let request): 42 | return .requestJSONEncodable(request) 43 | } 44 | } 45 | 46 | var headers: [String : String]? { 47 | switch self { 48 | case .createSubscription(header: let header, request: _): 49 | return header.asDictionary 50 | case .getSubscription(header: let header, request: _): 51 | return header.asDictionary 52 | } 53 | } 54 | 55 | var validationType: ValidationType { 56 | return .successCodes 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policies and Procedures 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 4.1.x | :white_check_mark: | 8 | | 3.0.1 | :white_check_mark: | 9 | | 2.0.X | :white_check_mark: | 10 | 11 | This document outlines Security Procedures and General Policies for this Project. 12 | 13 | * [Reporting a Vulnerability](#reporting-a-vulnerability) 14 | * [Disclosure Policy](#disclosure-policy) 15 | * [Comments on this Policy](#comments-on-this-policy) 16 | 17 | ## Reporting a Vulnerability 18 | 19 | Report security vulnerabilities by emailing the lead maintainer at security@safesafe.app. 20 | 21 | The lead maintainer will acknowledge your email within 48 hours, and will send a 22 | more detailed response within 48 hours indicating the next steps in handling 23 | your report. After the initial reply to your report, the security team will 24 | endeavor to keep you informed of the progress towards a fix and full 25 | announcement, and may ask for additional information or guidance. 26 | 27 | Report security vulnerabilities in third-party software to the person or team maintaining 28 | that software. 29 | 30 | ## Disclosure Policy 31 | 32 | When the security team receives a security vulnerability report, they will assign it to a 33 | primary handler. This person will coordinate the fix and release process, 34 | involving the following steps: 35 | 36 | * Confirm the problem and determine the affected versions. 37 | * Audit code to find any potential similar problems. 38 | * Prepare fixes for all releases still under maintenance. These fixes will be 39 | released as fast as possible to all distribution providers. 40 | 41 | ## Comments on this Policy 42 | 43 | If you have suggestions on how this process could be improved please submit a 44 | pull request. 45 | -------------------------------------------------------------------------------- /safesafe/Services/Permissions/NotificationsPermission.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationsPermission.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 26/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UserNotifications 11 | import PromiseKit 12 | 13 | final class NotificationsPermission: PermissionType { 14 | func state(shouldAsk: Bool) -> Promise { 15 | if shouldAsk { 16 | return readState().then { currentState -> Promise in 17 | if currentState == .neverAsked { 18 | return self.askForPermissions() 19 | } else { 20 | return Promise.value(currentState) 21 | } 22 | } 23 | } else { 24 | return readState() 25 | } 26 | } 27 | 28 | private func readState() -> Promise { 29 | return Promise { seal in 30 | UNUserNotificationCenter.current().getNotificationSettings { settings in 31 | switch settings.authorizationStatus { 32 | case .authorized, .provisional: 33 | seal.fulfill(.authorized) 34 | case .denied: 35 | seal.fulfill(.rejected) 36 | case .notDetermined: 37 | seal.fulfill(.neverAsked) 38 | @unknown default: 39 | seal.fulfill(.unknown) 40 | } 41 | } 42 | } 43 | } 44 | 45 | private func askForPermissions() -> Promise { 46 | return Promise { seal in 47 | UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in 48 | seal.fulfill(granted ? .authorized : .rejected) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /safesafe/Services/AppStatus/ExposureNotificationStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureNotificationStatus.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 25/05/2020. 6 | // 7 | 8 | import Foundation 9 | import PromiseKit 10 | import ExposureNotification 11 | import UIKit.UIDevice 12 | 13 | @available(iOS 13.5, *) 14 | final class ExposureNotificationStatus: ExposureNotificationStatusProtocol { 15 | 16 | private let service: ExposureServiceProtocol 17 | 18 | init(service: ExposureServiceProtocol) { 19 | self.service = service 20 | } 21 | 22 | var status: Promise { 23 | guard UIDevice.current.model == "iPhone" else { 24 | return .value(.restricted) 25 | } 26 | return service.activateManager() 27 | .map { 28 | if ENManager.authorizationStatus != .authorized { 29 | return .off 30 | } else { 31 | switch $0 { 32 | case .active: return .on 33 | case .bluetoothOff, .disabled: return .off 34 | default: return .restricted 35 | } 36 | } 37 | } 38 | } 39 | 40 | func isBluetoothOn(delay: TimeInterval) -> Promise { 41 | guard UIDevice.current.model == "iPhone" else { 42 | return .value(false) 43 | } 44 | return service.activateManager() 45 | .map { 46 | switch $0 { 47 | case .bluetoothOff: return false 48 | default: return true 49 | } 50 | } 51 | } 52 | } 53 | 54 | final class ExposureNotificationStatusMock: ExposureNotificationStatusProtocol { 55 | var status: Promise = .value(.restricted) 56 | func isBluetoothOn(delay: TimeInterval) -> Promise { .value(false) } 57 | } 58 | -------------------------------------------------------------------------------- /Documentation/ControllingExposureNotification.md: -------------------------------------------------------------------------------- 1 | # Controlling Exposure Notification 2 | 3 | Enables users to start and stop broadcasting and scanning, indicate if exposure notifications are running. 4 | 5 | - Start the Exposure Notification Framework broadcasting and scanning process. 6 | - PWA requests start of Exposure Notification Framework 7 | - Call **ExposureService.setExposureNotificationEnabled(_)** with `true`. 8 | - If not previously started, Exposure Notification Framework shows a user dialog for consent to start exposure detection and get permission. 9 | - If permission is granted and required services are enabled, Exposure Notification Framework will start broadcasting and scanning. 10 | - All of Exposure Notification Framework methods are now available. 11 | - Pass Exposure Notification Framework status - **ENABLED** - to PWA. 12 | - If user denies on any of Exposure Notification Framework requests, onboarding is skipped and Exposure Notification Framework disabled. 13 | - Pass Exposure Notification Framework status - **DISABLED** - to PWA. 14 | - If Exposure Notification Framework is not available on user's device 15 | - Pass Exposure Notification Framework status - **NOT_SUPPORTED** - to PWA. 16 | - If any of the required services is disabled during Exposure Notification Framework 17 | - Broadcasting and scanning process are stopped. 18 | - Pass services status to PWA to inform user that something is missing. 19 | - Exposure Notification Framework shows a user dialog to enable required service. 20 | - Indicate if exposure notifications are enabled. 21 | - Call **ExposureService.isExposureNotificationEnabled** 22 | - Disable broadcasting and scanning. 23 | - Call **ExposureService.setExposureNotificationEnabled(_)** with `false` 24 | - Contents of the Exposure Notification Framework database and keys will remain. 25 | - If the app has been uninstalled by the user, this will be automatically invoked and the database and keys will be wiped from the device. -------------------------------------------------------------------------------- /safesafe/Services/Notifications/NotificationsAlertManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationsAlertManager.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 12/10/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | final class NotificationsAlertManager: AlertManager { 11 | var viewController: UIViewController? 12 | 13 | func show(type: AlertType, result: @escaping (AlertAction) -> ()) { 14 | guard let viewController = presentingViewController() else { 15 | return 16 | } 17 | let (title, message) = content(type: type) 18 | guard !title.isEmpty && !message.isEmpty else { return } 19 | 20 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 21 | let cancelAction = UIAlertAction(title: AlertAction.cancel.title, style: .cancel) { _ in 22 | result(.cancel) 23 | } 24 | 25 | let setttingsAction = UIAlertAction(title: AlertAction.settings.title, style: .default) { _ in 26 | result(.settings) 27 | } 28 | 29 | alert.addAction(cancelAction) 30 | alert.addAction(setttingsAction) 31 | 32 | viewController.present(alert, animated: true) 33 | } 34 | 35 | private func content(type: AlertType) -> (title: String, message: String) { 36 | switch type { 37 | case .pushNotificationSettings: 38 | return (title: "NOTIFICATIONS_ALERT_TITLE".localized(), message: "NOTIFICATIONS_ALERT_MESSAGE".localized()) 39 | default: 40 | return (title: "", message: "") 41 | } 42 | } 43 | 44 | private func presentingViewController() -> UIViewController? { 45 | if let viewController = viewController { 46 | return viewController 47 | } 48 | 49 | guard let keyWindow = UIWindow.key, let rootViewController = keyWindow.rootViewController else { 50 | return nil 51 | } 52 | 53 | return rootViewController 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /safesafe/Services/District/Models/LocalStorage/DistrictStorageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DistrictStorageModel.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 11/10/2020. 6 | // 7 | 8 | import Foundation 9 | import RealmSwift 10 | 11 | final class DistrictStorageModel: Object, LocalStorable { 12 | 13 | private static let initState: Int = -1 14 | 15 | @objc dynamic var id: Int = .zero 16 | @objc dynamic var name: String = .empty 17 | @objc dynamic var state: Int = DistrictStorageModel.initState 18 | @objc dynamic var lastState: Int = DistrictStorageModel.initState 19 | @objc dynamic var updatedAt: Int = .zero 20 | @objc dynamic var order: Int = .zero 21 | @objc dynamic var voivodeship: VoivodeshipStorageModel? 22 | 23 | var stateChanged: Bool { lastState != state } 24 | var localizedZoneName: String { 25 | switch state { 26 | case 0: 27 | return "DISTRICT_ZONE_NEUTRAL".localized() 28 | case 1: 29 | return "DISTRICT_ZONE_YELLOW".localized() 30 | case 2: 31 | return "DISTRICT_ZONE_RED".localized() 32 | default: 33 | return .empty 34 | } 35 | } 36 | 37 | override class func primaryKey() -> String? { "id" } 38 | 39 | convenience init( 40 | with districtAPIModel: DistrictResponseModel.Voivodeship.District, 41 | currentModel: DistrictStorageModel?, 42 | voivodeship: VoivodeshipStorageModel?, 43 | index: Int, 44 | updatedAt: Int 45 | ) { 46 | 47 | self.init() 48 | 49 | self.id = districtAPIModel.id 50 | self.name = districtAPIModel.name 51 | self.updatedAt = updatedAt 52 | self.state = districtAPIModel.state 53 | self.order = index 54 | 55 | if let currentModel = currentModel { 56 | self.lastState = currentModel.state 57 | } else { 58 | self.lastState = districtAPIModel.state 59 | } 60 | 61 | self.voivodeship = voivodeship 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 16/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public final class Logger { 12 | 13 | public enum LogType { 14 | case regular 15 | case warning 16 | case error 17 | 18 | var prefix: String { 19 | switch self { 20 | case .regular: 21 | return "Debug" 22 | case .warning: 23 | return "⚠️ Warning" 24 | case .error: 25 | return "⛔️ Error" 26 | } 27 | } 28 | } 29 | 30 | private static var dateFormatter: DateFormatter = { 31 | let formatter = DateFormatter() 32 | formatter.dateFormat = "yyyy-MM-dd_HH:mm:ss.SSS" 33 | 34 | return formatter 35 | }() 36 | 37 | public static func log(_ value: Any?, type: LogType, file: String, function: String, line: Int, fullPath: Bool = false) { 38 | #if !LIVE 39 | let formattedMessage = logFormat(value, type: type, file: file, function: function, line: line, fullPath: fullPath) 40 | print(formattedMessage) 41 | Self.fileLog(formattedMessage) 42 | #endif 43 | } 44 | 45 | private static func logFormat(_ value: Any?, type: LogType, file: String, function: String, line: Int, fullPath: Bool = false) -> String { 46 | var file = file 47 | if !fullPath { 48 | file = String(file.split(separator: "/").last ?? "") 49 | } 50 | 51 | return "\(type.prefix)[\(file):\(function):\(line)] \(String(describing: value))" 52 | } 53 | 54 | private static func fileLog(_ message: String) { 55 | if #available(iOS 13.5, *) { 56 | DispatchQueue.global(qos: .background).async { 57 | let datePrefix = "{\(Logger.dateFormatter.string(from: Date()))}" 58 | let line = "\(datePrefix) \(message)" 59 | File.logToFile(line) 60 | } 61 | } 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Networking/NetworkingAlertManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkingAlertManager.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 30/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | final class NetworkingAlertManager: AlertManager { 11 | private(set) var viewController: UIViewController? 12 | 13 | func show(type: AlertType, result: @escaping (AlertAction) -> Void) { 14 | guard let viewController = presentingViewController() else { 15 | return 16 | } 17 | let (title, message) = content(type: type) 18 | guard !title.isEmpty && !message.isEmpty else { return } 19 | 20 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 21 | let cancelAction = UIAlertAction(title: AlertAction.cancel.title, style: .cancel) { _ in 22 | result(.cancel) 23 | } 24 | 25 | let retryAction = UIAlertAction(title: AlertAction.retry.title, style: .default) { _ in 26 | result(.retry) 27 | } 28 | 29 | alert.addAction(cancelAction) 30 | alert.addAction(retryAction) 31 | 32 | viewController.present(alert, animated: true) 33 | } 34 | 35 | private func content(type: AlertType) -> (title: String, message: String) { 36 | switch type { 37 | case .noInternet: 38 | return (title: "INTERNET_CONNECTION_ALERT_TITLE".localized(), message: "INTERNET_CONNECTION_ALERT_MESSAGE".localized()) 39 | case .uploadGeneral: 40 | return (title: "COMMON_ERROR_ALERT_TITLE".localized(), message: "UNKNOWN_ERROR_ALERT_MESSAGE".localized()) 41 | default: 42 | return (title: "", message: "") 43 | } 44 | } 45 | 46 | private func presentingViewController() -> UIViewController? { 47 | if let viewController = viewController { 48 | return viewController 49 | } 50 | 51 | guard let keyWindow = UIWindow.key, let rootViewController = keyWindow.rootViewController else { 52 | return nil 53 | } 54 | 55 | return rootViewController 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Validation/Upload/UploadValidationAlertManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UploadValidationAlertManager.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 20/08/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | final class UploadValidationAlertManager: AlertManager { 11 | var viewController: UIViewController? 12 | 13 | func show(type: AlertType, result: @escaping (AlertAction) -> ()) { 14 | guard let viewController = presentingViewController() else { 15 | return 16 | } 17 | 18 | let (title, message) = content(type: type) 19 | guard !title.isEmpty && !message.isEmpty else { return } 20 | 21 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 22 | let cancelAction = UIAlertAction(title: AlertAction.ok.title, style: .cancel) { _ in 23 | result(.ok) 24 | } 25 | 26 | alert.addAction(cancelAction) 27 | 28 | viewController.present(alert, animated: true) 29 | } 30 | 31 | private func content(type: AlertType) -> (title: String, message: String) { 32 | switch type { 33 | case .keysAtLeast: 34 | return (title: "UPLOAD_VALIDATION_ALERT_TITLE".localized(), message: "UPLOAD_VALIDATION_KEYS_AT_LEAST_MESSAGE".localized()) 35 | case .keysMax: 36 | return (title: "UPLOAD_VALIDATION_ALERT_TITLE".localized(), message: "UPLOAD_VALIDATION_KEYS_MAX_LEAST_MESSAGE".localized()) 37 | case .keysPerDayMax: 38 | return (title: "UPLOAD_VALIDATION_ALERT_TITLE".localized(), message: "UPLOAD_VALIDATION_KEYS_PER_DAY_MAX_LEAST_MESSAGE".localized()) 39 | default: 40 | return (title: "", message: "") 41 | } 42 | } 43 | 44 | private func presentingViewController() -> UIViewController? { 45 | if let viewController = viewController { 46 | return viewController 47 | } 48 | 49 | guard let keyWindow = UIWindow.key, let rootViewController = keyWindow.rootViewController else { 50 | return nil 51 | } 52 | 53 | return rootViewController 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /safesafe/Networking/ExposureKeysTarget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureKeysTarget.swift 3 | // safesafe 4 | // 5 | 6 | import Moya 7 | 8 | @available(iOS 13.5, *) 9 | enum ExposureKeysTarget { 10 | case auth(TemporaryExposureKeysAuthData) 11 | case post(TemporaryExposureKeysData) 12 | case get 13 | case download(fileName: String, destination: DownloadDestination) 14 | } 15 | 16 | @available(iOS 13.5, *) 17 | extension ExposureKeysTarget: TargetType { 18 | 19 | var baseURL: URL { 20 | switch self { 21 | case .get, .download: 22 | return URL(string: ConfigManager.default.enStorageURL)! 23 | case .auth: 24 | return URL(string: ConfigManager.default.enGatBaseURL)! 25 | case .post: 26 | return URL(string: ConfigManager.default.enUdkBaseURL)! 27 | } 28 | 29 | } 30 | 31 | var path: String { 32 | switch self { 33 | case .auth: 34 | return "getAccessToken" 35 | 36 | case .post: 37 | return "uploadDiagnosisKeys" 38 | 39 | case .get: 40 | return "/index.txt" 41 | 42 | case let .download(fileName, _): 43 | return "/\(fileName)" 44 | } 45 | } 46 | 47 | var method: Moya.Method { 48 | switch self { 49 | case .auth, .post: 50 | return .post 51 | case .get, .download: 52 | return .get 53 | } 54 | } 55 | 56 | var sampleData: Data { 57 | return Data() 58 | } 59 | 60 | var task: Task { 61 | switch self { 62 | case .auth(let temporaryExposureKeysAuthData): 63 | return .requestJSONEncodable(temporaryExposureKeysAuthData) 64 | 65 | case .post(let temporaryExposureKeys): 66 | return .requestJSONEncodable(temporaryExposureKeys) 67 | 68 | case .get: 69 | return .requestPlain 70 | 71 | case let .download(_, destination): 72 | return .downloadDestination(destination) 73 | } 74 | } 75 | 76 | var headers: [String : String]? { 77 | return nil 78 | } 79 | 80 | var validationType: ValidationType { 81 | return .successCodes 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /safesafe/Services/JavaScript Bridge/Models/AppReviewMockAlertManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppReviewMockAlertManager.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 23/11/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | final class AppReviewMockAlertManager: AlertManager { 11 | private(set) var viewController: UIViewController? 12 | 13 | func show(type: AlertType, result: @escaping (AlertAction) -> Void) { 14 | guard let viewController = presentingViewController() else { 15 | return 16 | } 17 | let (title, message) = content(type: type) 18 | guard !title.isEmpty && !message.isEmpty else { return } 19 | 20 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 21 | let okAction = UIAlertAction(title: AlertAction.cancel.title, style: .cancel) { _ in 22 | result(.ok) 23 | } 24 | 25 | let moreAction = UIAlertAction(title: AlertAction.more.title, style: .default) { _ in 26 | UIApplication.shared.open( 27 | URL(string: "https://developer.apple.com/documentation/storekit/skstorereviewcontroller/2851536-requestreview")!, 28 | options: [:], 29 | completionHandler: nil) 30 | } 31 | 32 | alert.addAction(okAction) 33 | alert.addAction(moreAction) 34 | 35 | viewController.present(alert, animated: true) 36 | } 37 | 38 | private func content(type: AlertType) -> (title: String, message: String) { 39 | switch type { 40 | case .appReviewMock: 41 | return (title: "App Review".localized(), message: "This is an app review mock alert. While it's running from testflight or ad-hoc there is no possibility to show real system App Review Alert.".localized()) 42 | default: 43 | return (title: "", message: "") 44 | } 45 | } 46 | 47 | private func presentingViewController() -> UIViewController? { 48 | if let viewController = viewController { 49 | return viewController 50 | } 51 | 52 | guard let keyWindow = UIWindow.key, let rootViewController = keyWindow.rootViewController else { 53 | return nil 54 | } 55 | 56 | return rootViewController 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /safesafe/Services/ExposureNotification/ExposureServiceHistoricalData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExposureServiceHistoricalData.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 07/12/2020. 6 | // 7 | 8 | import Foundation 9 | import PromiseKit 10 | 11 | protocol ExposureServiceHistoricalDataProtocol { 12 | func getHistoricalRiskCheck() -> Promise<[ExposureHistoryRiskCheck]> 13 | func getHistoricalAnalyzeCheck() -> Promise<[ExposureHistoryAnalyzeCheck]> 14 | func getAgregatedData() -> Promise 15 | func clearHistoricalData(riskIds: [String], analyzeIds: [String]) -> Promise 16 | } 17 | 18 | 19 | final class ExposureServiceHistoricalData: ExposureServiceHistoricalDataProtocol { 20 | 21 | private let storageService: LocalStorageProtocol? 22 | 23 | init(storageService: LocalStorageProtocol?) { 24 | self.storageService = storageService 25 | } 26 | 27 | func getHistoricalRiskCheck() -> Promise<[ExposureHistoryRiskCheck]> { 28 | Promise { seal in 29 | let riskChecks: [ExposureHistoryRiskCheck] = (storageService?.fetch() ?? []).sorted { $0.date < $1.date } 30 | seal.fulfill(riskChecks) 31 | } 32 | } 33 | 34 | func getHistoricalAnalyzeCheck() -> Promise<[ExposureHistoryAnalyzeCheck]> { 35 | Promise { seal in 36 | let analyzeChecks: [ExposureHistoryAnalyzeCheck] = (storageService?.fetch() ?? []).sorted { $0.date < $1.date } 37 | seal.fulfill(analyzeChecks) 38 | } 39 | } 40 | 41 | func getAgregatedData() -> Promise { 42 | Promise { seal in 43 | let model: ExposureHistoryRiskCheckAgregated? = storageService?.fetch(primaryKey: ExposureHistoryRiskCheckAgregated.identifier) 44 | seal.fulfill(model) 45 | } 46 | } 47 | 48 | func clearHistoricalData(riskIds: [String], analyzeIds: [String]) -> Promise { 49 | Promise { seal in 50 | let analyzeChecks: [ExposureHistoryAnalyzeCheck] = (storageService?.fetch() ?? []).filter { analyzeIds.contains($0.id) } 51 | let riskChecks: [ExposureHistoryRiskCheck] = (storageService?.fetch() ?? []).filter { riskIds.contains($0.id) } 52 | 53 | storageService?.remove(analyzeChecks, completion: nil) 54 | storageService?.remove(riskChecks, completion: nil) 55 | 56 | seal.fulfill(()) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /safesafe/Common/Base/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 09/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, ViewControllerType { 12 | var viewModel: T 13 | required init(viewModel: T) { 14 | self.viewModel = viewModel 15 | super.init(nibName: nil, bundle: nil) 16 | start() 17 | viewModel.start() 18 | } 19 | required init?(coder: NSCoder) { 20 | Fatal.execute("Not implemented") 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | viewModel.onViewDidLoad(setupFinished: false) 27 | setup() 28 | viewModel.onViewDidLoad(setupFinished: true) 29 | } 30 | 31 | override func viewWillAppear(_ animated: Bool) { 32 | super.viewWillAppear(animated) 33 | 34 | viewModel.onViewWillAppear(layoutFinished: false) 35 | layout() 36 | viewModel.onViewWillAppear(layoutFinished: true) 37 | 38 | } 39 | 40 | override func viewDidAppear(_ animated: Bool) { 41 | super.viewDidAppear(animated) 42 | 43 | viewModel.onViewDidAppear() 44 | } 45 | 46 | override func viewWillDisappear(_ animated: Bool) { 47 | super.viewWillAppear(animated) 48 | 49 | viewModel.onViewWillDisappear() 50 | } 51 | 52 | override func viewDidDisappear(_ animated: Bool) { 53 | super.viewDidDisappear(animated) 54 | 55 | viewModel.onViewDidDisappear() 56 | } 57 | 58 | func start() { 59 | Fatal.execute("This method is called after super.init(nibName: nil, bundle: nil) and before viewModel.start(). Please use it for vc initialising job like assignig delegate for view model etc") 60 | } 61 | 62 | func setup() { 63 | Fatal.execute("This method is called when views are loaded and available. Please use it for setup your views") 64 | } 65 | 66 | func layout() { 67 | Fatal.execute("This method is called after setup() method. Please use it arrange your views with constraints, frames etc") 68 | } 69 | 70 | func add(subview: UIView, translatesAutoresizingMask: Bool = false) { 71 | subview.translatesAutoresizingMaskIntoConstraints = translatesAutoresizingMask 72 | view.addSubview(subview) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Notifications/Models/RouteModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RouteModel.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 03/12/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RouteModel: Decodable { 11 | let name: String 12 | var params: [String: Value] 13 | 14 | enum Value: Decodable { 15 | case int(Int) 16 | case string(String) 17 | case float(Float) 18 | case double(Double) 19 | case bool(Bool) 20 | case unknown 21 | 22 | init(from decoder: Decoder) throws { 23 | let container = try decoder.singleValueContainer() 24 | 25 | if let value = try? container.decode(Double.self) { 26 | self = .double(value) 27 | } else if let value = try? container.decode(Float.self) { 28 | self = .float(value) 29 | } else if let value = try? container.decode(Int.self) { 30 | self = .int(value) 31 | } else if let value = try? container.decode(Bool.self) { 32 | self = .bool(value) 33 | } else if let value = try? container.decode(String.self) { 34 | self = .string(value) 35 | } else { 36 | self = .unknown 37 | } 38 | } 39 | } 40 | 41 | func asDictionary() -> [String: Any] { 42 | var dictionary: [String: Any] = ["name": name] 43 | var dictionaryParams: [String: Any] = [:] 44 | 45 | for param in params { 46 | switch param.value { 47 | case .int(let value): 48 | dictionaryParams[param.key] = value 49 | case .string(let value): 50 | dictionaryParams[param.key] = value 51 | case .float(let value): 52 | dictionaryParams[param.key] = value 53 | case .double(let value): 54 | dictionaryParams[param.key] = value 55 | case .bool(let value): 56 | dictionaryParams[param.key] = value 57 | case .unknown: () 58 | } 59 | } 60 | 61 | dictionary["params"] = dictionaryParams 62 | return dictionary 63 | } 64 | 65 | func asJSONString() -> String? { 66 | guard let data = try? JSONSerialization.data(withJSONObject: asDictionary(), options: .fragmentsAllowed) else { 67 | return nil 68 | } 69 | 70 | return String(bytes: data, encoding: .utf8) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /safesafe/Services/Local Storage/FileStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileStorage.swift 3 | // safesafe 4 | // 5 | // Created by Namedix on 19/02/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol FileStorageType: AnyObject { 11 | func write(to fileName: FileStorage.Key, content: Data) -> Result 12 | func read(from fileName: FileStorage.Key) -> Result 13 | } 14 | 15 | final class FileStorage: FileStorageType { 16 | enum Key: String { 17 | case timestamps 18 | case dashboard 19 | case details 20 | } 21 | 22 | enum FileError: Error { 23 | case wrongDirectory 24 | case write(error: Error) 25 | case read(error: Error) 26 | } 27 | 28 | private let fileManager: FileManager 29 | 30 | init(fileManager: FileManager = FileManager.default) { 31 | self.fileManager = fileManager 32 | } 33 | 34 | func write(to fileName: Key, content: Data) -> Result { 35 | switch getFileUrl(for: fileName) { 36 | case .success(let fileUrl): 37 | do { 38 | try content.write(to: fileUrl, options: .noFileProtection) 39 | return .success(()) 40 | } catch let error { 41 | return .failure(.write(error: error)) 42 | } 43 | case .failure(let error): 44 | return .failure(error) 45 | } 46 | } 47 | 48 | func read(from fileName: Key) -> Result { 49 | switch getFileUrl(for: fileName) { 50 | case .success(let url): 51 | do { 52 | let content = try Data(contentsOf: url) 53 | return .success(content) 54 | } catch let error { 55 | return .failure(.read(error: error)) 56 | } 57 | case .failure(let error): 58 | return .failure(error) 59 | } 60 | } 61 | 62 | private func getFileUrl(for key: Key, pathExtension: String = "json") -> Result { 63 | let fileName = key.rawValue 64 | guard let documentDirectoryURL = try? fileManager.url( 65 | for: .cachesDirectory, 66 | in: .userDomainMask, 67 | appropriateFor: nil, 68 | create: true 69 | ) else { return .failure(FileError.wrongDirectory) } 70 | 71 | return .success( 72 | documentDirectoryURL 73 | .appendingPathComponent(fileName) 74 | .appendingPathExtension(pathExtension) 75 | ) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /PushMutableContent/NotificationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationService.swift 3 | // PushMutableContent 4 | // 5 | // Created by Łukasz Szyszkowski on 19/11/2020. 6 | // 7 | 8 | import UserNotifications 9 | 10 | class NotificationService: UNNotificationServiceExtension { 11 | 12 | var contentHandler: ((UNNotificationContent) -> Void)? 13 | 14 | override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { 15 | self.contentHandler = contentHandler 16 | let parser = NotificationUserInfoParser() 17 | 18 | if let covidStatsDictionary = parser.parseCovidStats(userInfo: request.content.userInfo)?.dictionary { 19 | parser.addSharedData(data: covidStatsDictionary, for: .sharedCovidStats) 20 | } 21 | 22 | guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else { return } 23 | 24 | guard 25 | let selectedLanguageISO: String = StoredDefaults.standard.get(key: .selectedLanguage, useAppGroup: true), 26 | let selectedModel = parser.selectedLanguageData(lang: selectedLanguageISO, userInfo: request.content.userInfo) 27 | else { 28 | // Save default content if translations doesn't exist 29 | // 30 | let routeRaw: String? = parser.routeData(userInfo: request.content.userInfo) 31 | let dictionary = parser.parseNotification(title: bestAttemptContent.title, content: bestAttemptContent.body, route: routeRaw) 32 | parser.addSharedData(data: dictionary, for: .sharedNotifications) 33 | contentHandler(bestAttemptContent) 34 | return 35 | } 36 | 37 | let routeRaw: String? = parser.routeData(userInfo: request.content.userInfo) 38 | let dictionary = parser.parseNotification(title: selectedModel.title, content: selectedModel.content, route: routeRaw) 39 | parser.addSharedData(data: dictionary, for: .sharedNotifications) 40 | 41 | var modifiedUserInfo = request.content.userInfo 42 | modifiedUserInfo[NotificationUserInfoParser.Key.uuid.rawValue] = dictionary[NotificationUserInfoParser.Key.id.rawValue] as? String 43 | bestAttemptContent.userInfo = modifiedUserInfo 44 | bestAttemptContent.title = selectedModel.title 45 | bestAttemptContent.body = selectedModel.content 46 | 47 | contentHandler(bestAttemptContent) 48 | 49 | } 50 | 51 | override func serviceExtensionTimeWillExpire() { 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Documentation/ProvidingDiagnosisKeys.md: -------------------------------------------------------------------------------- 1 | # Providing Diagnosis Keys 2 | 3 | Each new downloaded Diagnosis Key file (please see [Downloading Diagnosis Keys](DownloadingDiagnosisKeys.md) section) is provided for exposure checking. The check is performed by [Exposure Notification API](https://developer.apple.com/documentation/exposurenotification) with Exposure Configuration options that tune the matching algorithm. In order to provide elastic architecture, the exposure configuration is obtained from Firebase RemoteConfig service. 4 | Periodic fetch of Diagnosis Keys is performed by [BackgroundTaskService](../safesafe/Services/ExposureNotification/BackgroundTasksService.swift) without need of user's interaction in the foreground. 5 | 6 | Steps: 7 | - New Diagnosis Key files that have never been analyzed are downloaded as described in [Downloading Diagnosis Keys](DownloadingDiagnosisKeys.md) section. Diagnosis Key files must be signed appropriately - the matching algorithm only runs on data that has been verified with the public key distributed by the device configuration mechanism. See more [here](https://static.googleusercontent.com/media/www.google.com/pt-BR//covid19/exposurenotifications/pdfs/Exposure-Key-File-Format-and-Verification.pdf). 8 | - Exposure configuration options are obtained from Firebase RemoteConfig. The configuration allows to provide Health Authority recommendations regarding different aspects of exposure (like duration, attenuation or days since exposure). More about ExposureConfiguration can be found [here](https://developer.apple.com/documentation/exposurenotification/enexposureconfiguration). 9 | - Service function: [RemoteConfiguration.configuration()](../safesafe/Networking/RemoteConfig/RemoteConfig.swift) 10 | - The new downloaded Diagnosis Key files and the Exposure Configuration are provided for [ENManager](https://developer.apple.com/documentation/exposurenotification/enmanager) 11 | - Service function: [ExposureService.detectExposures()](../safesafe/Services/ExposureNotification/ExposureService.swift) 12 | - When the data has been successfully provided for [ENManager](https://developer.apple.com/documentation/exposurenotification/enmanager) then: 13 | - Information about the latest analyzed Diagnosis Key file timestamp is stored locally on a device (encrypted Shared Preferences). This information is used to select the future Diagnosis Key files that have not been analyzed yet – only files with higher Diagnosis Key timestamp are selected 14 | - Service function: [DiagnosisKeysDownloadService.download()](../safesafe/Services/ExposureNotification/DiagnosisKeysDownloadService.swift) 15 | - All sent to analysis Diagnosis Key files are deleted from device internal storage 16 | - Service function: [DiagnosisKeysDownloadService.deleteFiles()](../safesafe/Services/ExposureNotification/DiagnosisKeysDownloadService.swift) -------------------------------------------------------------------------------- /safesafe/Common/Helpers/Networking/RenewableRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenewableRequest.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 30/06/2020. 6 | // 7 | 8 | import Foundation 9 | import Moya 10 | import PromiseKit 11 | 12 | final class RenewableRequest { 13 | private var (pendingPromise, seal) = Promise.pending() 14 | private let provider: MoyaProvider 15 | private let alertManager: AlertManager 16 | private let notRenewableErrorCodes: [Int] 17 | 18 | init(provider: MoyaProvider, alertManager: AlertManager, notRenewableErrorCodes: [Int] = []) { 19 | self.provider = provider 20 | self.alertManager = alertManager 21 | self.notRenewableErrorCodes = notRenewableErrorCodes 22 | } 23 | 24 | func make(target: Target) -> Promise { 25 | defer { execute(target: target) } 26 | (pendingPromise, seal) = Promise.pending() 27 | return pendingPromise 28 | } 29 | 30 | private func execute(target: Target) { 31 | guard NetworkMonitoring.shared.isInternetAvailable else { 32 | alertManager.show(type: .noInternet) { [weak self] action in 33 | if case .cancel = action { 34 | self?.seal.reject(InternalError.noInternet) 35 | } else if case .retry = action { 36 | self?.execute(target: target) 37 | } 38 | } 39 | return 40 | } 41 | 42 | var syncResult: Swift.Result = .failure(.underlying(InternalError.cantMakeRequest, nil)) 43 | let semaphore = DispatchSemaphore(value: 0) 44 | let queue = DispatchQueue.global(qos: .background) 45 | 46 | provider.request(target, callbackQueue: queue) { result in 47 | syncResult = result 48 | semaphore.signal() 49 | } 50 | 51 | _ = semaphore.wait(timeout: .now() + 20) 52 | 53 | if case .failure(let error) = syncResult { 54 | guard let responseCode = error.response?.statusCode, (responseCode != 404 && !notRenewableErrorCodes.contains(responseCode)) else { 55 | seal.reject(error) 56 | return 57 | } 58 | 59 | alertManager.show(type: .uploadGeneral) { [weak self] action in 60 | if case .cancel = action { 61 | self?.seal.reject(InternalError.cantMakeRequest) 62 | } else if case .retry = action { 63 | self?.execute(target: target) 64 | } 65 | } 66 | } else if case .success(let response) = syncResult { 67 | seal.fulfill(response) 68 | } 69 | } 70 | 71 | } 72 | 73 | -------------------------------------------------------------------------------- /safesafe/Common/Helpers/HistoricalDataWorker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoricalDataWorker.swift 3 | // safesafe 4 | // 5 | // Created by Łukasz Szyszkowski on 07/12/2020. 6 | // 7 | 8 | import Foundation 9 | import PromiseKit 10 | 11 | struct HistoricalData: Encodable { 12 | let notifications: [PushNotificationHistoryModel.EncodableModel] 13 | let riskChecks: [ExposureHistoryAnalyzeCheck.EncodableModel] 14 | let exposures: [ExposureHistoryRiskCheck.EncodableModel] 15 | } 16 | 17 | protocol HistoricalDataWorkerType { 18 | func getData() -> Promise 19 | func getAgregatedExposureData() -> Promise 20 | func clearData(request: DeleteHistoricalDataRequest) -> Promise 21 | } 22 | 23 | 24 | final class HistoricalDataWorker: HistoricalDataWorkerType { 25 | 26 | private let notificationsHistoryWorker: NotificationHistoryWorkerType 27 | private let exposureHistoricalDataService: ExposureServiceHistoricalDataProtocol 28 | 29 | init( 30 | notificationsHistoryWorker: NotificationHistoryWorkerType, 31 | exposureHistoricalDataService: ExposureServiceHistoricalDataProtocol 32 | ) { 33 | self.notificationsHistoryWorker = notificationsHistoryWorker 34 | self.exposureHistoricalDataService = exposureHistoricalDataService 35 | } 36 | 37 | func getData() -> Promise { 38 | return notificationsHistoryWorker 39 | .fetchAllNotifications() 40 | .then { notifications in 41 | return self.exposureHistoricalDataService.getHistoricalRiskCheck().map { (notifications, $0) } 42 | } 43 | .then { notifications, riskCheck in 44 | self.exposureHistoricalDataService.getHistoricalAnalyzeCheck().map { (notifications, riskCheck, $0) } 45 | } 46 | .then { notifications, riskCheck, analyzeCheck -> Promise in 47 | return .value( 48 | .init( 49 | notifications: notifications.map { $0.asEncodable() }, 50 | riskChecks: analyzeCheck.map { $0.asEncodable() }, 51 | exposures: riskCheck.map { $0.asEncodable() } 52 | ) 53 | ) 54 | } 55 | 56 | } 57 | 58 | func getAgregatedExposureData() -> Promise { 59 | exposureHistoricalDataService.getAgregatedData() 60 | } 61 | 62 | func clearData(request: DeleteHistoricalDataRequest) -> Promise { 63 | notificationsHistoryWorker 64 | .clearHistory(with: request.notifications ) 65 | .then { 66 | self.exposureHistoricalDataService.clearHistoricalData(riskIds: request.exposures, analyzeIds: request.riskChecks) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /safesafe/Services/AppStatus/ServiceStatusManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppStatusManager.swift 3 | // safesafe Live 4 | // 5 | // Created by Rafał Małczyński on 22/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PromiseKit 11 | 12 | protocol ServiceStatusManagerProtocol { 13 | 14 | func currentServiceStatus(delay: TimeInterval) -> Promise 15 | func serviceStatusJson(delay: TimeInterval) -> Promise 16 | 17 | } 18 | 19 | final class ServiceStatusManager: ServiceStatusManagerProtocol { 20 | 21 | // MARK: - Properties 22 | 23 | private let notificationManager: NotificationManagerProtocol 24 | private let exposureNotificationStatus: ExposureNotificationStatusProtocol 25 | 26 | func currentServiceStatus(delay: TimeInterval) -> Promise { 27 | Promise { seal in 28 | 29 | firstly { 30 | when(fulfilled: 31 | Permissions.instance.state(for: .notifications), 32 | exposureNotificationStatus.status, 33 | exposureNotificationStatus.isBluetoothOn(delay: delay) 34 | ) 35 | }.done { notificationStatus, exposureStatus, bluetoothStatus in 36 | let status = ServicesResponse.Status( 37 | exposureNotificationStatus: exposureStatus, 38 | isBluetoothOn: bluetoothStatus, 39 | isNotificationEnabled: notificationStatus == .authorized) 40 | seal.fulfill(ServicesResponse(status: status)) 41 | }.catch { error in 42 | console(error, type: .error) 43 | seal.reject(error) 44 | } 45 | } 46 | } 47 | 48 | func serviceStatusJson(delay: TimeInterval) -> Promise { 49 | Promise { [weak self] seal in 50 | guard let self = self else { 51 | seal.reject(InternalError.deinitialized) 52 | return 53 | } 54 | 55 | self.currentServiceStatus(delay: delay) 56 | .done { status in 57 | guard let json = status.jsonString else { 58 | seal.reject(InternalError.serializationFailed) 59 | return 60 | } 61 | console(json) 62 | seal.fulfill(json) 63 | }.catch { error in 64 | seal.reject(error) 65 | } 66 | } 67 | } 68 | 69 | // MARK: - Life Cycle 70 | 71 | init( 72 | notificationManager: NotificationManagerProtocol, 73 | exposureNotificationStatus: ExposureNotificationStatusProtocol 74 | ) { 75 | self.notificationManager = notificationManager 76 | self.exposureNotificationStatus = exposureNotificationStatus 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /safesafe/Services/DetailsWorker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsWorker.swift 3 | // safesafe 4 | // 5 | // Created by Namedix on 22/02/2021. 6 | // 7 | 8 | import Foundation 9 | import PromiseKit 10 | import Moya 11 | 12 | protocol DetailsWorkerType: AnyObject { 13 | @discardableResult func fetchData() -> Promise 14 | } 15 | 16 | final class DetailsWorker: DetailsWorkerType { 17 | 18 | //MARK: - Properties 19 | 20 | private let detailsProvider: MoyaProvider 21 | private let timestampsWorker: TimestampsWorkerType 22 | private let fileStorage: FileStorageType 23 | private let decoder: JSONDecoder 24 | private let getCurrentDate: () -> Date 25 | 26 | //MARK: - Initialization 27 | 28 | init( 29 | detailsProvider: MoyaProvider, 30 | timestampsWorker: TimestampsWorkerType, 31 | fileStorage: FileStorageType, 32 | decoder: JSONDecoder = JSONDecoder(), 33 | getCurrentDate: @escaping () -> Date = Date.init 34 | ) { 35 | self.detailsProvider = detailsProvider 36 | self.timestampsWorker = timestampsWorker 37 | self.fileStorage = fileStorage 38 | self.decoder = decoder 39 | self.getCurrentDate = getCurrentDate 40 | } 41 | 42 | //MARK: - DetailsWorkerType 43 | 44 | @discardableResult func fetchData() -> Promise { 45 | timestampsWorker.fetchTimestamps() 46 | .map { $0.detailsUpdated < Int(self.getCurrentDate().timeIntervalSince1970) } 47 | .then(getData(shouldDownload:)) 48 | .then(toString) 49 | } 50 | 51 | //MARK: - Private methods 52 | 53 | private func getData(shouldDownload: Bool) -> Promise { 54 | if shouldDownload { 55 | return downloadAndCacheData() 56 | } else { 57 | return self.fileStorage 58 | .read(from: .details) 59 | .toPromise() 60 | .recover { [unowned self] error -> Promise in 61 | console(error) 62 | return self.downloadAndCacheData() 63 | } 64 | } 65 | } 66 | 67 | private func downloadAndCacheData() -> Promise { 68 | detailsProvider.request(.fetchDetails) 69 | .map { $0.data } 70 | .then(updateLocalData(responseData:)) 71 | } 72 | 73 | private func updateLocalData(responseData: Data) -> Promise { 74 | self.fileStorage 75 | .write(to: .details, content: responseData) 76 | .map { responseData } 77 | .toPromise() 78 | } 79 | 80 | private func toString(from data: Data) -> Promise { 81 | Promise { seal in 82 | if let jsonString = String(data: data, encoding: .utf8) { 83 | seal.fulfill(jsonString) 84 | } else { 85 | seal.reject(InternalError.nilValue) 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /safesafe/Components/Debug/SQLite/SQLiteManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SQLiteManager.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 07/10/2020. 6 | // 7 | 8 | import Foundation 9 | import SQLite3 10 | 11 | final class SQLiteManager { 12 | 13 | private var db:OpaquePointer? 14 | 15 | init() { 16 | db = openDatabase() 17 | } 18 | 19 | func openDatabase(fileName: String = "file__0.localstorage") -> OpaquePointer? { 20 | guard let dbDir = try? Directory.webkitLocalStorage() else { return nil } 21 | let dbURL = dbDir.appendingPathComponent(fileName) 22 | var db: OpaquePointer? = nil 23 | if sqlite3_open(dbURL.path, &db) != SQLITE_OK 24 | { 25 | console("error opening database", type: .error) 26 | return nil 27 | } 28 | else 29 | { 30 | console("Successfully opened connection to database at \(dbURL)") 31 | return db 32 | } 33 | } 34 | 35 | func read(fileName: String) -> String { 36 | sqlite3_close(db) 37 | db = openDatabase(fileName: fileName) 38 | 39 | var output = "NO_DATA" 40 | let queryStatementString = "SELECT * FROM ItemTable;" 41 | var queryStatement: OpaquePointer? = nil 42 | if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK { 43 | while sqlite3_step(queryStatement) == SQLITE_ROW { 44 | let key = String(cString: sqlite3_column_text(queryStatement, 0)) 45 | let valueLen = sqlite3_column_bytes(queryStatement, 1) 46 | let valuePoint = sqlite3_column_blob(queryStatement, 1) 47 | guard let bytes = valuePoint else { 48 | console("Can't read SQLite bytes") 49 | return output 50 | } 51 | 52 | let data = Data(bytes: bytes, count: Int(valueLen)) 53 | let valueStr = String(bytes: data, encoding: .utf16LittleEndian) ?? "NO_VALUE" 54 | let jsonData = valueStr.data(using: .utf8)! 55 | do { 56 | guard let dict = try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [String: Any] else { return output } 57 | output = "" 58 | 59 | output.append("> SQLITE KEY: \(key)\n") 60 | for (key, value) in dict { 61 | output.append("\(key.replacingOccurrences(of: "\\", with: "")): \(value)\n") 62 | } 63 | 64 | return output 65 | 66 | } catch { console(error, type: .error) } 67 | } 68 | } else { 69 | console("SELECT statement could not be prepared", type: .error) 70 | } 71 | sqlite3_finalize(queryStatement) 72 | 73 | return output 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /safesafe/Services/TimestampsWorker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimestampsWorker.swift 3 | // safesafe 4 | // 5 | // Created by Namedix on 22/02/2021. 6 | // 7 | 8 | import Foundation 9 | import PromiseKit 10 | import Moya 11 | 12 | protocol TimestampsWorkerType: AnyObject { 13 | func fetchTimestamps() -> Promise 14 | } 15 | 16 | final class TimestampsWorker: TimestampsWorkerType { 17 | 18 | //MARK: - Properties 19 | 20 | private let timestampsProvider: MoyaProvider 21 | private let fileStorage: FileStorageType 22 | private let decoder: JSONDecoder 23 | private let getCurrentDate: () -> Date 24 | 25 | //MARK: - Initialization 26 | 27 | init( 28 | timestampsProvider: MoyaProvider, 29 | fileStorage: FileStorageType, 30 | decoder: JSONDecoder = JSONDecoder(), 31 | getCurrentDate: @escaping () -> Date = Date.init 32 | ) { 33 | self.timestampsProvider = timestampsProvider 34 | self.fileStorage = fileStorage 35 | self.decoder = decoder 36 | self.getCurrentDate = getCurrentDate 37 | } 38 | 39 | //MARK: - TimestampsWorkerType 40 | 41 | func fetchTimestamps() -> Promise { 42 | fileStorage.read(from: .timestamps) 43 | .toPromise() 44 | .then { data -> Promise in 45 | guard let timestamps = try? self.decoder.decode(TimestampsResponse.self, from: data), 46 | timestamps.nextUpdate > Int(self.getCurrentDate().timeIntervalSince1970) 47 | else { 48 | return self.downloadAndSaveTimestamps() 49 | } 50 | return .value(timestamps) 51 | } 52 | .recover { error -> Promise in 53 | console("Don't have saved timestamps or error: \(error)") 54 | return self.downloadAndSaveTimestamps() 55 | } 56 | } 57 | 58 | //MARK: - Private methods 59 | 60 | private func downloadAndSaveTimestamps() -> Promise { 61 | self.downloadTimestamps() 62 | .then(self.writeTimestampsToFile(data:)) 63 | .then(self.decodeTimestamps(data:)) 64 | } 65 | 66 | private func downloadTimestamps() -> Promise { 67 | timestampsProvider.request(.fetchTimestamps) 68 | .map { $0.data } 69 | } 70 | 71 | private func writeTimestampsToFile(data: Data) -> Promise { 72 | fileStorage.write(to: .timestamps, content: data) 73 | .map { data } 74 | .toPromise() 75 | } 76 | 77 | private func decodeTimestamps(data: Data) -> Promise { 78 | guard let timestamps = try? self.decoder.decode(TimestampsResponse.self, from: data) else { 79 | console("Can't decode timestamps") 80 | return .init(error: InternalError.nilValue) 81 | } 82 | return .value(timestamps) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift 3 | # Edit at https://www.gitignore.io/?templates=swift 4 | 5 | ### Swift ### 6 | # Xcode 7 | # 8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData/ 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xccheckout 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | safesafe/resources/Dev/GoogleService-Info-Dev.plist 37 | safesafe/resources/Live/GoogleService-Info-Live.plist 38 | safesafe/resources/Stage/GoogleService-Info-Stage.plist 39 | 40 | safesafe/resources/Dev/GoogleService-Info.plist 41 | safesafe/resources/Live/GoogleService-Info.plist 42 | safesafe/resources/Stage/GoogleService-Info.plist 43 | 44 | safesafe/resources/Dev/Config-dev.plist 45 | safesafe/resources/Stage/Config-stage.plist 46 | 47 | safesafe/Resources/pwa/ 48 | 49 | ## Xcodegen 50 | *.xcodeproj 51 | *.xcworkspace 52 | 53 | ## Playgrounds 54 | timeline.xctimeline 55 | playground.xcworkspace 56 | 57 | # Swift Package Manager 58 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 59 | # Packages/ 60 | # Package.pins 61 | # Package.resolved 62 | .build/ 63 | .idea/ 64 | # Add this line if you want to avoid checking in Xcode SPM integration. 65 | # .swiftpm/xcode 66 | 67 | # CocoaPods 68 | # We recommend against adding the Pods directory to your .gitignore. However 69 | # you should judge for yourself, the pros and cons are mentioned at: 70 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 71 | Pods/ 72 | # Add this line if you want to avoid checking in source code from the Xcode workspace 73 | # *.xcworkspace 74 | 75 | # Carthage 76 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 77 | # Carthage/Checkouts 78 | 79 | Carthage/Build 80 | 81 | # Accio dependency management 82 | Dependencies/ 83 | .accio/ 84 | 85 | # fastlane 86 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 87 | # screenshots whenever they are needed. 88 | # For more information about the recommended setup visit: 89 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 90 | 91 | fastlane/report.xml 92 | fastlane/Preview.html 93 | fastlane/screenshots/**/*.png 94 | fastlane/test_output 95 | fastlane/*secrets.yml 96 | 97 | # Code Injection 98 | # After new code Injection tools there's a generated folder /iOSInjectionProject 99 | # https://github.com/johnno1962/injectionforxcode 100 | 101 | iOSInjectionProject/ 102 | 103 | # End of https://www.gitignore.io/api/swift 104 | 105 | .DS_Store 106 | -------------------------------------------------------------------------------- /safesafe/Common/Files/File.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 18/08/2020. 6 | // 7 | 8 | import Foundation 9 | import ZIPFoundation 10 | 11 | final class File { 12 | 13 | static let logFileName = "log.txt" 14 | 15 | private static var dateFormatter: DateFormatter { 16 | let formatter = DateFormatter() 17 | formatter.dateFormat = "yyyy-MM-dd_hh-mm-ss" 18 | return formatter 19 | } 20 | 21 | static func save(_ data: Data?, name: String, directory: URL) { 22 | let fileURL = directory.appendingPathComponent(name) 23 | do { 24 | try data?.write(to: fileURL) 25 | } catch { 26 | console(error, type: .error) 27 | } 28 | } 29 | 30 | @available(iOS 13.4, *) 31 | static func append(data: Data, to fileName: String, in directory: URL) { 32 | let fileURL = directory.appendingPathComponent(fileName) 33 | if FileManager.default.fileExists(atPath: fileURL.path) { 34 | do { 35 | let fileHandle = try FileHandle(forWritingTo: fileURL) 36 | try fileHandle.seekToEnd() 37 | fileHandle.write(data) 38 | fileHandle.closeFile() 39 | } catch { /* Can't print this error to console(...) because it will loop forever */ } 40 | } else { 41 | do { 42 | try data.write(to: fileURL, options: .atomicWrite) 43 | } catch { /* Can't print this error to console(...) because it will loop forever */ } 44 | } 45 | } 46 | } 47 | 48 | extension File { 49 | static func logFileURL() throws -> URL? { 50 | do { 51 | return try Directory.logs().appendingPathComponent(logFileName) 52 | } catch { /* Can't print this error to console(...) because it will loop forever */ } 53 | 54 | return nil 55 | } 56 | 57 | @available(iOS 13.5, *) 58 | static func logToFile(_ message: String) { 59 | guard let data = message.appending("\n").data(using: .utf8) else { return } 60 | do { 61 | append(data: data, to: logFileName, in: try Directory.logs()) 62 | } catch { /* Can't print this error to console(...) because it will loop forever */ } 63 | } 64 | 65 | @available(iOS 13.5, *) 66 | static func saveUploadedPayload(_ keysData: TemporaryExposureKeysData) { 67 | do { 68 | let encodedData = try JSONEncoder().encode(keysData) 69 | let fileName = "\(Self.dateFormatter.string(from: Date()))_up.txt" 70 | save(encodedData, name: fileName, directory: try Directory.uploadedPayloads()) 71 | } catch { 72 | console(error, type: .error) 73 | } 74 | } 75 | 76 | static func uploadedPayloadsZIP() throws -> URL { 77 | let destinationURL = try Directory.uploadedPayloadsTemp().appendingPathComponent("\(Self.dateFormatter.string(from: Date())).zip") 78 | try FileManager.default.zipItem(at: try Directory.uploadedPayloads(), to: destinationURL) 79 | return destinationURL 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/README.md: -------------------------------------------------------------------------------- 1 | ## Pull Requests 2 | Use apropriate template and don't forget about [DISCLAIMER](#DISCLAIMER) 3 | - [Pull Request Template](pull_request_template.md) 4 | - [Personal License Disclaimer Form](../../DISCLAIMER.pdf) 5 | 6 | ## DISCLAIMER 7 | We require Digitally Signed Personal Agreement for Contributors collaborating on the project to accept their Pull Requests - it's Public Health so We need Public Trust thus introducing digital two-factor authentication for The Makers - also Apple doesn't go well with GNU so We make them do so manually by following these steps: 8 | 1. Remember ProteGO-Safe software is being released on [GNU GENERAL PUBLIC LICENSE](../../LICENSE) 9 | 2. If You would like to contribute any changes to our project repositories You are the author and the owner of these changes, by the law of [GitHub Terms of Use](https://help.github.com/en/github/site-policy/github-terms-of-service#6-contributions-under-repository-license) You automatically grant license for these changes . 10 | 3. Mitigating any kind of misunderstanding around copyrights as well as lack of trust for unknown entities in public sector we ask that every Pull Request author verify aforementioned license grants for proposed changes through : 11 | * [Downloading personal license disclaimer form](/DISCLAIMER.pdf) which states that the author: 12 | * confirms GPL license grant for proposed changes 13 | * permits everyone and anyone who agrees with the license statements to distribute this software freely in any form or format even in public software providers such as Apple's App Store, Google's Play Store, GitHub, etc. 14 | * commits the author of changes not to execute his personal copyrights to dismiss them or their license for any reason 15 | * Digitally signing the completed license statement form with compatible digital signature: 16 | * [Trusted digital signature](https://www.gov.pl/web/gov/podpisz-dokument-elektronicznie-wykorzystaj-podpis-zaufany) 17 | * [Qualified digital signature](https://pl.wikipedia.org/wiki/Podpis_kwalifikowany) 18 | * [Personal digital signature](https://www.gov.pl/web/e-dowod/podpis-osobisty) 19 | * Sending completed and signed license disclaimer form to [protego@mc.gov.pl](mailto:protego@mc.gov.pl) with: 20 | * Title: `Oświadczenie GitHub` 21 | * Note: Ministry of Digitalization will become your administrator of personal data submitted during process (Detailed Personal Data Regulations for each third-party involved are available to public in their own Privacy Policy statements) 22 | 4. After sending completed and signed license disclaimer form You will be added to [CONTRIBUTORS.md](../../CONTRIBUTORS.md) file on the first merged Pull Request. 23 | 5. Then since We accept Pull Requests from new contributors only if they've already sent their completed and signed license disclaimer form thus are present on this list [CONTRIBUTORS.md](/CONTRIBUTORS.md) we should process Your future requests much, much faster. Thank You. 24 | 6. Last and at least we'd like You to know We're very sorry as We also wish there was a simpler way to do all this and promise to look for it in the future. 25 | -------------------------------------------------------------------------------- /safesafe/App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // safesafe 4 | // 5 | // Created by Lukasz szyszkowski on 09/04/2020. 6 | // Copyright © 2020 Lukasz szyszkowski. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | private var appCoordinator: AppCoordinator? 14 | 15 | var window: UIWindow? 16 | 17 | @available(iOS 13.0, *) 18 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 19 | 20 | if let windowScene = scene as? UIWindowScene, let appDelegate = UIApplication.shared.delegate as? AppDelegate { 21 | window = window ?? UIWindow(windowScene: windowScene) 22 | appCoordinator = AppCoordinator(appWindow: window, dependencyContainer: appDelegate.dependencyContainer) 23 | appCoordinator?.start() 24 | } 25 | 26 | guard let _ = (scene as? UIWindowScene) else { return } 27 | 28 | guard 29 | let userActivity = connectionOptions.userActivities.first, 30 | userActivity.activityType == NSUserActivityTypeBrowsingWeb, 31 | let incomingURL = userActivity.webpageURL 32 | else { 33 | return 34 | } 35 | 36 | DeepLinkingWorker.shared.navigate(with: incomingURL) 37 | } 38 | 39 | @available(iOS 13.0, *) 40 | func sceneDidDisconnect(_ scene: UIScene) { 41 | // Called as the scene is being released by the system. 42 | // This occurs shortly after the scene enters the background, or when its session is discarded. 43 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 44 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 45 | } 46 | 47 | @available(iOS 13.0, *) 48 | func sceneDidBecomeActive(_ scene: UIScene) { 49 | NotificationManager.shared.clearBadgeNumber() 50 | NotificationManager.shared.registerAPNSIfNeeded() 51 | NotificationManager.shared.registerNotificationCategories() 52 | } 53 | 54 | @available(iOS 13.0, *) 55 | func sceneWillResignActive(_ scene: UIScene) { 56 | // Called when the scene will move from an active state to an inactive state. 57 | // This may occur due to temporary interruptions (ex. an incoming phone call). 58 | } 59 | 60 | @available(iOS 13.0, *) 61 | func sceneWillEnterForeground(_ scene: UIScene) { 62 | HiderController.shared.hide() 63 | } 64 | 65 | @available(iOS 13.0, *) 66 | func sceneDidEnterBackground(_ scene: UIScene) { 67 | HiderController.shared.show(windowScene: window?.windowScene) 68 | } 69 | 70 | @available(iOS 13.0, *) 71 | func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { 72 | guard let url = userActivity.webpageURL else { return } 73 | DeepLinkingWorker.shared.navigate(with: url) 74 | } 75 | 76 | @available(iOS 13.0, *) 77 | func scene(_ scene: UIScene, willContinueUserActivityWithType userActivityType: String) { 78 | 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /Documentation/UploadingTemporaryExposureKeys.md: -------------------------------------------------------------------------------- 1 | # Temporary Exposure Keys Upload 2 | 3 | Once the user is diagnosed with COVID-19 they can share their Temporary Exposure Keys (**TEK**) to inform others, with whom they had contact with, about potential exposition to virus. 4 | 5 | Steps: 6 | 7 | - User diagnosed with COVID-19 is given a unique 6-character code (**PIN**) from the authority employee 8 | - User enters the code and agrees for TEK upload (**PWA** module) 9 | - PWA module notifies native code through JS bridge that provided **PIN** should be used for **TEK**s upload 10 | - Exposure Notification Framework asks the user for a permission to share TEK with STOP COVID - ProteGO Safe App 11 | - The app checks if 6-character code is valid by requesting ProteGo Safe server for a **Token** (Firebase Cloud Function is called with **PIN** as a payload): 12 | - Moya Target endpoint: [`case auth(TemporaryExposureKeysAuthData)`](../safesafe/Networking/ExposureKeysTarget.swift) 13 | - Service function: [`DiagnosisKeysUploadService.getToken(usingAuthCode authCode: String) -> Promise`](../safesafe/Services/ExposureNotification/DiagnosisKeysUploadService.swift) 14 | - The app generates Device Verification Payload using [DeviceCheck Framework](https://developer.apple.com/documentation/devicecheck). It is used to confirm a genuine iOS device. 15 | - Service function: [`DeviceCheckServiceProtocol.generatePayload(bundleID: String, exposureKeys: [Data], regions: [String]) -> Promise`](../safesafe/Services/DeviceCheckService.swift) 16 | - Device Verification Payload has following structure: 17 | - `deviceToken` - base 64–encoded representation of encrypted device information returned by [`generateToken(completionHandler completion: @escaping (Data?, Error?) -> Void)`](https://developer.apple.com/documentation/devicecheck/dcdevice/2902276-generatetoken) 18 | - `transactionId` - SHA256 hash of the concatenation of: 19 | - `appPackageName` - app's Bundle Identifier 20 | - `appPackageName` - Concatenation of the `TrackingKey.Key` values in their base64 encoding, sorted lexicographically 21 | - `appPackageName` - Concatenation of regions, uppercased, sorted lexicographically 22 | - `timestamp` - UTC timestamp in milliseconds since the Unix epoch 23 | - The app generates Upload Request Payload(**URP**) with the following data: 24 | - `temporaryExposureKeys` - array of Temporary Exposure Keys acquired using [`ExposureServiceProtocol.getDiagnosisKeys() -> Promise<[ENTemporaryExposureKey]>`](../safesafe/Services/ExposureNotification/ExposureService.swift) 25 | - `regions` - array of regions: `["PL"]` 26 | - `platform` - platform name: `"ios"` 27 | - `appPackageName` - app's Bundle Identifier 28 | - `verificationPayload` - **Token** acquired from STOP COVID - ProteGO Safe server 29 | - `deviceVerificationPayload` - Device Verification Payload generated by [`DeviceCheckService`](../safesafe/Services/DeviceCheckService.swift) 30 | - The app uploads **URP** to STOP COVID - ProteGO Safe server with a proper Firebase Cloud Function 31 | - Moya Target endpoint: [`case post(TemporaryExposureKeysData)`](../safesafe/Networking/ExposureKeysTarget.swift) 32 | - Service function: [`DiagnosisKeysUploadServiceProtocol.upload(usingAuthCode authCode: String) -> Promise`](../safesafe/Services/ExposureNotification/DiagnosisKeysUploadService.swift) 33 | -------------------------------------------------------------------------------- /safesafe/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]} --------------------------------------------------------------------------------