From 773f076abdd6df93139aaf4d48d2da49853b0260 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sat, 5 Apr 2025 00:16:54 +0200 Subject: [PATCH] working on #53 --- ios/NotificationService/Info.plist | 13 + .../NotificationService.swift | 93 ++++++ ios/Podfile | 4 + ios/Podfile.lock | 2 +- ios/Runner.xcodeproj/project.pbxproj | 275 +++++++++++++++++- ios/Runner/AppDelegate.swift | 98 ++++++- ios/Runner/Base.lproj/Main.storyboard | 13 +- ios/Runner/Info.plist | 11 +- lib/src/database/tables/messages_table.dart | 4 +- lib/src/proto/api/client_to_server.pb.dart | 42 +++ .../proto/api/client_to_server.pbjson.dart | 45 ++- 11 files changed, 565 insertions(+), 35 deletions(-) create mode 100644 ios/NotificationService/Info.plist create mode 100644 ios/NotificationService/NotificationService.swift diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist new file mode 100644 index 0000000..57421eb --- /dev/null +++ b/ios/NotificationService/Info.plist @@ -0,0 +1,13 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift new file mode 100644 index 0000000..e515fb4 --- /dev/null +++ b/ios/NotificationService/NotificationService.swift @@ -0,0 +1,93 @@ +// +// NotificationService.swift +// NotificationService +// +// Created by Tobi on 03.04.25. +// + +import UserNotifications +import CryptoKit +import Foundation + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + + + if let bestAttemptContent = bestAttemptContent { + + + // Extract the ciphertext and nonce from the notification's userInfo + guard let _userInfo = bestAttemptContent.userInfo as? [String: Any], + let ciphertextString = bestAttemptContent.userInfo["ciphertext"] as? String, + let nonceString = bestAttemptContent.userInfo["nonce"] as? String else { + return + } + + // Convert the base64 encoded strings to Data + guard let ciphertextData = Data(base64Encoded: ciphertextString), + let nonceData = Data(base64Encoded: nonceString) else { + return + } + + // Create the key (32 bytes of "A") + let keyString = String(repeating: "A", count: 32) + guard let keyData = keyString.data(using: .utf8) else { + return + } + + // Ensure the key is 32 bytes + guard keyData.count == 32 else { + return + } + + // Ensure the ciphertext is more than 16 Bytes + guard ciphertextData.count >= 16 else { + return + } + + // Split the ciphertextData into the actual ciphertext and the tag + let tagLength = 16 + let ciphertext = ciphertextData.prefix(ciphertextData.count - tagLength) + let tag = ciphertextData.suffix(tagLength) + + // Create a SymmetricKey from the key data + let key = SymmetricKey(data: keyData) + + // Decrypt the ciphertext using ChaCha20 + do { + let nonce = try ChaChaPoly.Nonce(data: nonceData) + let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: Data(tag)) + let decryptedData = try ChaChaPoly.open(sealedBox, using: key) + + // Convert decrypted data to a string + if let decryptedMessage = String(data: decryptedData, encoding: .utf8) { + NSLog("Decrypted message: \(decryptedMessage)") + + bestAttemptContent.title = "\(bestAttemptContent.title)" + bestAttemptContent.body = decryptedMessage + + } + } catch { + NSLog("Decryption failed: \(error)") + } + + contentHandler(bestAttemptContent) + } + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + contentHandler(bestAttemptContent) + } + } + +} diff --git a/ios/Podfile b/ios/Podfile index 06cfecf..a4d3d31 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -72,3 +72,7 @@ post_install do |installer| end end end + +target 'NotificationService' do + # pod 'Firebase/Messaging' +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 72d6904..5218817 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -345,6 +345,6 @@ SPEC CHECKSUMS: sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d -PODFILE CHECKSUM: eda8ac661dab0c3d1e1b175d40ebbf2becd0ce86 +PODFILE CHECKSUM: 4d78ee29daee4dd5268f87f2e6b41e472cc27728 COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 12712ce..d336b8e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B78043D680BC6AA2F7329CB5 /* libPods-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CF25E171552AB36BFB27E57B /* libPods-NotificationService.a */; }; + D21FCEAB2D9F2B750088701D /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D21FCEA42D9F2B750088701D /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; F3C66D726A2EB28484DF0B10 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ @@ -27,6 +29,13 @@ remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; + D21FCEA92D9F2B750088701D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = D21FCEA32D9F2B750088701D; + remoteInfo = NotificationService; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -40,6 +49,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + D21FCEAC2D9F2B750088701D /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + D21FCEAB2D9F2B750088701D /* NotificationService.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -50,7 +70,9 @@ 1D2A72578A796660F4740845 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 35366FD433E0EFC6EF19A452 /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4D78471482626812FE2468E9 /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = ""; }; 5F3BB8E3AC9CEA61248BD989 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 6EB462F87F0A23758713308F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 70E8A5E1DA4031C0E3F86C77 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; @@ -66,10 +88,34 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B3B27B7FBEEA31DB7793A0C2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + CF25E171552AB36BFB27E57B /* libPods-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D21FCEA42D9F2B750088701D /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; D2265DD42D920142000D99BB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; E96A5ACA32A7118204F050A5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + F02F7A1D63544AA9F23A1085 /* Pods-NotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.profile.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + D21FCEB02D9F2B750088701D /* Exceptions for "NotificationService" folder in "NotificationService" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = D21FCEA32D9F2B750088701D /* NotificationService */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + D21FCEA52D9F2B750088701D /* NotificationService */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + D21FCEB02D9F2B750088701D /* Exceptions for "NotificationService" folder in "NotificationService" target */, + ); + path = NotificationService; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 58BD64CB3861C01B305B451B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -87,6 +133,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D21FCEA12D9F2B750088701D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B78043D680BC6AA2F7329CB5 /* libPods-NotificationService.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -114,6 +168,7 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + D21FCEA52D9F2B750088701D /* NotificationService */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */, @@ -127,6 +182,7 @@ children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + D21FCEA42D9F2B750088701D /* NotificationService.appex */, ); name = Products; sourceTree = ""; @@ -152,6 +208,7 @@ children = ( 5F3BB8E3AC9CEA61248BD989 /* libPods-Runner.a */, 1D2A72578A796660F4740845 /* libPods-RunnerTests.a */, + CF25E171552AB36BFB27E57B /* libPods-NotificationService.a */, ); name = Frameworks; sourceTree = ""; @@ -165,6 +222,9 @@ 1581CC44342D555EFB889768 /* Pods-RunnerTests.debug.xcconfig */, E96A5ACA32A7118204F050A5 /* Pods-RunnerTests.release.xcconfig */, 7FC59147CD9A45BFAC98EA05 /* Pods-RunnerTests.profile.xcconfig */, + 4D78471482626812FE2468E9 /* Pods-NotificationService.debug.xcconfig */, + 35366FD433E0EFC6EF19A452 /* Pods-NotificationService.release.xcconfig */, + F02F7A1D63544AA9F23A1085 /* Pods-NotificationService.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -200,6 +260,7 @@ 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, + D21FCEAC2D9F2B750088701D /* Embed Foundation Extensions */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, A3027D78D4FF6E79C9EFD470 /* [CP] Copy Pods Resources */, @@ -207,12 +268,35 @@ buildRules = ( ); dependencies = ( + D21FCEAA2D9F2B750088701D /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; + D21FCEA32D9F2B750088701D /* NotificationService */ = { + isa = PBXNativeTarget; + buildConfigurationList = D21FCEB12D9F2B750088701D /* Build configuration list for PBXNativeTarget "NotificationService" */; + buildPhases = ( + 2915B65131340B0D0BD69DF9 /* [CP] Check Pods Manifest.lock */, + D21FCEA02D9F2B750088701D /* Sources */, + D21FCEA12D9F2B750088701D /* Frameworks */, + D21FCEA22D9F2B750088701D /* Resources */, + 058310D0E53ED9534CBBCE88 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + D21FCEA52D9F2B750088701D /* NotificationService */, + ); + name = NotificationService; + productName = NotificationService; + productReference = D21FCEA42D9F2B750088701D /* NotificationService.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -220,6 +304,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1620; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -231,10 +316,12 @@ CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; + D21FCEA32D9F2B750088701D = { + CreatedOnToolsVersion = 16.2; + }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -242,12 +329,14 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + preferredProjectObjectVersion = 77; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 331C8080294A63A400263BE5 /* RunnerTests */, + D21FCEA32D9F2B750088701D /* NotificationService */, ); }; /* End PBXProject section */ @@ -272,9 +361,55 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D21FCEA22D9F2B750088701D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 058310D0E53ED9534CBBCE88 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-NotificationService/Pods-NotificationService-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-NotificationService/Pods-NotificationService-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NotificationService/Pods-NotificationService-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 2915B65131340B0D0BD69DF9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NotificationService-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 38633DCCA126701FCA357CBC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -311,7 +446,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; @@ -387,6 +522,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D21FCEA02D9F2B750088701D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -395,6 +537,11 @@ target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; }; + D21FCEAA2D9F2B750088701D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D21FCEA32D9F2B750088701D /* NotificationService */; + targetProxy = D21FCEA92D9F2B750088701D /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -721,6 +868,120 @@ }; name = Release; }; + D21FCEAD2D9F2B750088701D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4D78471482626812FE2468E9 /* Pods-NotificationService.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = CN332ZUGRP; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = eu.twonly.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D21FCEAE2D9F2B750088701D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 35366FD433E0EFC6EF19A452 /* Pods-NotificationService.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = CN332ZUGRP; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = eu.twonly.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + D21FCEAF2D9F2B750088701D /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F02F7A1D63544AA9F23A1085 /* Pods-NotificationService.profile.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = CN332ZUGRP; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = eu.twonly.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -754,6 +1015,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D21FCEB12D9F2B750088701D /* Build configuration list for PBXNativeTarget "NotificationService" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D21FCEAD2D9F2B750088701D /* Debug */, + D21FCEAE2D9F2B750088701D /* Release */, + D21FCEAF2D9F2B750088701D /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 713d15e..4aa4d70 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,5 +1,9 @@ import Flutter import UIKit +import UserNotifications +import CryptoKit +import Foundation + @main @objc class AppDelegate: FlutterAppDelegate { @@ -9,8 +13,10 @@ import UIKit ) -> Bool { if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate + //UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate } + + UNUserNotificationCenter.current().delegate = self // if (@available(iOS 10.0, *)) { // [UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; @@ -19,4 +25,94 @@ import UIKit GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + override func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + NSLog("Application delegate method userNotificationCenter:didReceive:withCompletionHandler: is called with user info: %@", response.notification.request.content.userInfo) + //... + } + + override func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + NSLog("userNotificationCenter:willPresent") + completionHandler([.alert, .sound]) + } + + +} + + +import Security +import CommonCrypto +import Foundation +import CryptoKit + +class KeychainHelper { + static func save(key: String, data: Data) -> OSStatus { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecValueData as String: data + ] + SecItemDelete(query as CFDictionary) // Delete any existing item + return SecItemAdd(query as CFDictionary, nil) + } + + static func load(key: String) -> Data? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecReturnData as String: kCFBooleanTrue!, + kSecMatchLimit as String: kSecMatchLimitOne + ] + + var dataTypeRef: AnyObject? = nil + let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) + + if status == errSecSuccess { + return dataTypeRef as? Data + } else { + return nil + } + } +} + +// Function to decrypt data +func decrypt(data: Data, key: SymmetricKey) -> Data? { + do { + // Extract nonce, ciphertext, and tag + let nonceData = data.prefix(12) // ChaCha20-Poly1305 nonce is 12 bytes + let ciphertext = data.dropFirst(12).dropLast(16) // Exclude nonce and tag + let tag = data.suffix(16) // Last 16 bytes are the tag + + // Create a nonce from the extracted nonce data + let nonce = try ChaChaPoly.Nonce(data: nonceData) + + // Create a sealed box with the ciphertext and tag + let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: tag) + + // Decrypt the data + let decryptedData = try ChaChaPoly.open(sealedBox, using: key) + return decryptedData + } catch { + print("Decryption error: \(error)") + return nil + } +} + + +func handleReceivedMessage(encryptedMessage: Data, keyID: String) { + // Load the AES key from Keychain + guard let keyData = KeychainHelper.load(key: keyID) else { + print("Key not found") + return + } + + let key = SymmetricKey(data: keyData); + + // Decrypt the message + if let decryptedData = decrypt(data: encryptedMessage, key: key) { + let decryptedMessage = String(data: decryptedData, encoding: .utf8) + print("Decrypted message: \(decryptedMessage ?? "nil")") + } else { + print("Decryption failed") + } } diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index f3c2851..04713cb 100644 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -14,13 +16,14 @@ - + - + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 6c86453..99e8784 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,18 +45,11 @@ LaunchScreen UIMainStoryboardFile Main + FIREBASE_ANALYTICS_COLLECTION_ENABLED + FirebaseAutomaticScreenReportingEnabled UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight diff --git a/lib/src/database/tables/messages_table.dart b/lib/src/database/tables/messages_table.dart index 6f2a24b..4751132 100644 --- a/lib/src/database/tables/messages_table.dart +++ b/lib/src/database/tables/messages_table.dart @@ -10,7 +10,9 @@ enum MessageKind { rejectRequest, acceptRequest, opened, - ack + ack, + pushKey, + pushKeyAck, } enum DownloadState { diff --git a/lib/src/proto/api/client_to_server.pb.dart b/lib/src/proto/api/client_to_server.pb.dart index 42a77b4..9567941 100644 --- a/lib/src/proto/api/client_to_server.pb.dart +++ b/lib/src/proto/api/client_to_server.pb.dart @@ -205,6 +205,7 @@ class Handshake_Register extends $pb.GeneratedMessage { $core.List<$core.int>? signedPrekeySignature, $fixnum.Int64? signedPrekeyId, $fixnum.Int64? registrationId, + $core.bool? isIos, }) { final $result = create(); if (username != null) { @@ -228,6 +229,9 @@ class Handshake_Register extends $pb.GeneratedMessage { if (registrationId != null) { $result.registrationId = registrationId; } + if (isIos != null) { + $result.isIos = isIos; + } return $result; } Handshake_Register._() : super(); @@ -242,6 +246,7 @@ class Handshake_Register extends $pb.GeneratedMessage { ..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'signedPrekeySignature', $pb.PbFieldType.OY) ..aInt64(6, _omitFieldNames ? '' : 'signedPrekeyId') ..aInt64(7, _omitFieldNames ? '' : 'registrationId') + ..aOB(8, _omitFieldNames ? '' : 'isIos') ..hasRequiredFields = false ; @@ -328,6 +333,15 @@ class Handshake_Register extends $pb.GeneratedMessage { $core.bool hasRegistrationId() => $_has(6); @$pb.TagNumber(7) void clearRegistrationId() => clearField(7); + + @$pb.TagNumber(8) + $core.bool get isIos => $_getBF(7); + @$pb.TagNumber(8) + set isIos($core.bool v) { $_setBool(7, v); } + @$pb.TagNumber(8) + $core.bool hasIsIos() => $_has(7); + @$pb.TagNumber(8) + void clearIsIos() => clearField(8); } class Handshake_GetAuthChallenge extends $pb.GeneratedMessage { @@ -430,6 +444,7 @@ class Handshake_Authenticate extends $pb.GeneratedMessage { factory Handshake_Authenticate({ $fixnum.Int64? userId, $core.List<$core.int>? authToken, + $core.String? appVersion, }) { final $result = create(); if (userId != null) { @@ -438,6 +453,9 @@ class Handshake_Authenticate extends $pb.GeneratedMessage { if (authToken != null) { $result.authToken = authToken; } + if (appVersion != null) { + $result.appVersion = appVersion; + } return $result; } Handshake_Authenticate._() : super(); @@ -447,6 +465,7 @@ class Handshake_Authenticate extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Handshake.Authenticate', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create) ..aInt64(1, _omitFieldNames ? '' : 'userId') ..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'authToken', $pb.PbFieldType.OY) + ..aOS(3, _omitFieldNames ? '' : 'appVersion') ..hasRequiredFields = false ; @@ -488,6 +507,15 @@ class Handshake_Authenticate extends $pb.GeneratedMessage { $core.bool hasAuthToken() => $_has(1); @$pb.TagNumber(2) void clearAuthToken() => clearField(2); + + @$pb.TagNumber(3) + $core.String get appVersion => $_getSZ(2); + @$pb.TagNumber(3) + set appVersion($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasAppVersion() => $_has(2); + @$pb.TagNumber(3) + void clearAppVersion() => clearField(3); } enum Handshake_Handshake { @@ -613,6 +641,7 @@ class ApplicationData_TextMessage extends $pb.GeneratedMessage { factory ApplicationData_TextMessage({ $fixnum.Int64? userId, $core.List<$core.int>? body, + $core.List<$core.int>? pushData, }) { final $result = create(); if (userId != null) { @@ -621,6 +650,9 @@ class ApplicationData_TextMessage extends $pb.GeneratedMessage { if (body != null) { $result.body = body; } + if (pushData != null) { + $result.pushData = pushData; + } return $result; } ApplicationData_TextMessage._() : super(); @@ -630,6 +662,7 @@ class ApplicationData_TextMessage extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.TextMessage', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create) ..aInt64(1, _omitFieldNames ? '' : 'userId') ..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'body', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'pushData', $pb.PbFieldType.OY) ..hasRequiredFields = false ; @@ -671,6 +704,15 @@ class ApplicationData_TextMessage extends $pb.GeneratedMessage { $core.bool hasBody() => $_has(1); @$pb.TagNumber(3) void clearBody() => clearField(3); + + @$pb.TagNumber(4) + $core.List<$core.int> get pushData => $_getN(2); + @$pb.TagNumber(4) + set pushData($core.List<$core.int> v) { $_setBytes(2, v); } + @$pb.TagNumber(4) + $core.bool hasPushData() => $_has(2); + @$pb.TagNumber(4) + void clearPushData() => clearField(4); } class ApplicationData_GetUserByUsername extends $pb.GeneratedMessage { diff --git a/lib/src/proto/api/client_to_server.pbjson.dart b/lib/src/proto/api/client_to_server.pbjson.dart index 5083a46..016b0e3 100644 --- a/lib/src/proto/api/client_to_server.pbjson.dart +++ b/lib/src/proto/api/client_to_server.pbjson.dart @@ -77,9 +77,11 @@ const Handshake_Register$json = { {'1': 'signed_prekey_signature', '3': 5, '4': 1, '5': 12, '10': 'signedPrekeySignature'}, {'1': 'signed_prekey_id', '3': 6, '4': 1, '5': 3, '10': 'signedPrekeyId'}, {'1': 'registration_id', '3': 7, '4': 1, '5': 3, '10': 'registrationId'}, + {'1': 'is_ios', '3': 8, '4': 1, '5': 8, '9': 1, '10': 'isIos', '17': true}, ], '8': [ {'1': '_invite_code'}, + {'1': '_is_ios'}, ], }; @@ -103,6 +105,10 @@ const Handshake_Authenticate$json = { '2': [ {'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'}, {'1': 'auth_token', '3': 2, '4': 1, '5': 12, '10': 'authToken'}, + {'1': 'app_version', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'appVersion', '17': true}, + ], + '8': [ + {'1': '_app_version'}, ], }; @@ -113,16 +119,18 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode( 'ZW50X3RvX3NlcnZlci5IYW5kc2hha2UuR2V0QXV0aENoYWxsZW5nZUgAUhBnZXRhdXRoY2hhbG' 'xlbmdlEk4KDGdldGF1dGh0b2tlbhgDIAEoCzIoLmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtl' 'LkdldEF1dGhUb2tlbkgAUgxnZXRhdXRodG9rZW4STgoMYXV0aGVudGljYXRlGAQgASgLMiguY2' - 'xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlSABSDGF1dGhlbnRpY2F0ZRq8' + 'xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlSABSDGF1dGhlbnRpY2F0ZRrj' 'AgoIUmVnaXN0ZXISGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lEiQKC2ludml0ZV9jb2RlGA' 'IgASgJSABSCmludml0ZUNvZGWIAQESLgoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDFIRcHVi' 'bGljSWRlbnRpdHlLZXkSIwoNc2lnbmVkX3ByZWtleRgEIAEoDFIMc2lnbmVkUHJla2V5EjYKF3' 'NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAUgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUSKAoQ' 'c2lnbmVkX3ByZWtleV9pZBgGIAEoA1IOc2lnbmVkUHJla2V5SWQSJwoPcmVnaXN0cmF0aW9uX2' - 'lkGAcgASgDUg5yZWdpc3RyYXRpb25JZEIOCgxfaW52aXRlX2NvZGUaEgoQR2V0QXV0aENoYWxs' - 'ZW5nZRpDCgxHZXRBdXRoVG9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhoKCHJlc3Bvbn' - 'NlGAIgASgMUghyZXNwb25zZRpGCgxBdXRoZW50aWNhdGUSFwoHdXNlcl9pZBgBIAEoA1IGdXNl' - 'cklkEh0KCmF1dGhfdG9rZW4YAiABKAxSCWF1dGhUb2tlbkILCglIYW5kc2hha2U='); + 'lkGAcgASgDUg5yZWdpc3RyYXRpb25JZBIaCgZpc19pb3MYCCABKAhIAVIFaXNJb3OIAQFCDgoM' + 'X2ludml0ZV9jb2RlQgkKB19pc19pb3MaEgoQR2V0QXV0aENoYWxsZW5nZRpDCgxHZXRBdXRoVG' + '9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhoKCHJlc3BvbnNlGAIgASgMUghyZXNwb25z' + 'ZRp8CgxBdXRoZW50aWNhdGUSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEh0KCmF1dGhfdG9rZW' + '4YAiABKAxSCWF1dGhUb2tlbhIkCgthcHBfdmVyc2lvbhgDIAEoCUgAUgphcHBWZXJzaW9uiAEB' + 'Qg4KDF9hcHBfdmVyc2lvbkILCglIYW5kc2hha2U='); @$core.Deprecated('Use applicationDataDescriptor instead') const ApplicationData$json = { @@ -150,6 +158,10 @@ const ApplicationData_TextMessage$json = { '2': [ {'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'}, {'1': 'body', '3': 3, '4': 1, '5': 12, '10': 'body'}, + {'1': 'push_data', '3': 4, '4': 1, '5': 12, '9': 0, '10': 'pushData', '17': true}, + ], + '8': [ + {'1': '_push_data'}, ], }; @@ -238,17 +250,18 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode( 'b2tlbhgIIAEoCzI2LmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlVwZGF0ZUdvb2' 'dsZUZjbVRva2VuSABSFHVwZGF0ZWdvb2dsZWZjbXRva2VuElEKC2dldGxvY2F0aW9uGAkgASgL' 'Mi0uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0TG9jYXRpb25IAFILZ2V0bG' - '9jYXRpb24aOgoLVGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJvZHkY' - 'AyABKAxSBGJvZHkaLwoRR2V0VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZX' - 'JuYW1lGjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCmdvb2dsZV9mY20YASABKAlSCWdvb2ds' - 'ZUZjbRomCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaDQoLR2V0TG9jYX' - 'Rpb24aLQoSR2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBo7Cg5H' - 'ZXRVcGxvYWRUb2tlbhIpChByZWNpcGllbnRzX2NvdW50GAEgASgNUg9yZWNpcGllbnRzQ291bn' - 'QaiQEKClVwbG9hZERhdGESIQoMdXBsb2FkX3Rva2VuGAEgASgMUgt1cGxvYWRUb2tlbhIWCgZv' - 'ZmZzZXQYAiABKA1SBm9mZnNldBISCgRkYXRhGAMgASgMUgRkYXRhEh8KCGNoZWNrc3VtGAQgAS' - 'gMSABSCGNoZWNrc3VtiAEBQgsKCV9jaGVja3N1bRpNCgxEb3dubG9hZERhdGESJQoOZG93bmxv' - 'YWRfdG9rZW4YASABKAxSDWRvd25sb2FkVG9rZW4SFgoGb2Zmc2V0GAIgASgNUgZvZmZzZXRCEQ' - 'oPQXBwbGljYXRpb25EYXRh'); + '9jYXRpb24aagoLVGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJvZHkY' + 'AyABKAxSBGJvZHkSIAoJcHVzaF9kYXRhGAQgASgMSABSCHB1c2hEYXRhiAEBQgwKCl9wdXNoX2' + 'RhdGEaLwoRR2V0VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUK' + 'FFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCmdvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbRomCg' + 'tHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaDQoLR2V0TG9jYXRpb24aLQoS' + 'R2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBo7Cg5HZXRVcGxvYW' + 'RUb2tlbhIpChByZWNpcGllbnRzX2NvdW50GAEgASgNUg9yZWNpcGllbnRzQ291bnQaiQEKClVw' + 'bG9hZERhdGESIQoMdXBsb2FkX3Rva2VuGAEgASgMUgt1cGxvYWRUb2tlbhIWCgZvZmZzZXQYAi' + 'ABKA1SBm9mZnNldBISCgRkYXRhGAMgASgMUgRkYXRhEh8KCGNoZWNrc3VtGAQgASgMSABSCGNo' + 'ZWNrc3VtiAEBQgsKCV9jaGVja3N1bRpNCgxEb3dubG9hZERhdGESJQoOZG93bmxvYWRfdG9rZW' + '4YASABKAxSDWRvd25sb2FkVG9rZW4SFgoGb2Zmc2V0GAIgASgNUgZvZmZzZXRCEQoPQXBwbGlj' + 'YXRpb25EYXRh'); @$core.Deprecated('Use responseDescriptor instead') const Response$json = {