diff --git a/ios/Flutter/NotificationServiceExtension-Debug.xcconfig b/ios/Flutter/NotificationServiceExtension-Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/NotificationServiceExtension-Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/NotificationServiceExtension-Profile.xcconfig b/ios/Flutter/NotificationServiceExtension-Profile.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/NotificationServiceExtension-Profile.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/NotificationServiceExtension-Release.xcconfig b/ios/Flutter/NotificationServiceExtension-Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/NotificationServiceExtension-Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/NotificationServiceExtension/NotificationService.swift b/ios/NotificationServiceExtension/NotificationService.swift index 9a85390..ac18b3a 100644 --- a/ios/NotificationServiceExtension/NotificationService.swift +++ b/ios/NotificationServiceExtension/NotificationService.swift @@ -31,7 +31,7 @@ class NotificationService: UNNotificationServiceExtension { /// Must exactly match `kPushKeychainGroup` in lib/push/push_secure_storage.dart /// and the `keychain-access-groups` entitlement of BOTH the Runner and this /// extension. Wrong value here => keychain reads return nil => placeholder. - private static let keychainAccessGroup = "group.eu.mhsl.marianum.mobile.client.widget" + private static let keychainAccessGroup = "MY55VF3KPG.eu.mhsl.marianum.mobile.client.push" private static let devicePrivateKeyAccount = "push_device_private_key_pem" private static let serverPublicKeyAccount = "push_server_public_key_pem" @@ -233,7 +233,7 @@ class NotificationService: UNNotificationServiceExtension { /// kSecClass = kSecClassGenericPassword /// kSecAttrAccount = the Dart key, verbatim /// kSecAttrService = (unset — the Dart IOSOptions set no accountName) - /// kSecAttrAccessGroup = the App Group id + /// kSecAttrAccessGroup = the team-prefixed shared keychain group /// value = raw UTF-8 bytes of the string private func keychainString(_ account: String) -> String? { let query: [CFString: Any] = [ diff --git a/ios/NotificationServiceExtension/NotificationServiceExtension.entitlements b/ios/NotificationServiceExtension/NotificationServiceExtension.entitlements index 59b1233..b0ee48b 100644 --- a/ios/NotificationServiceExtension/NotificationServiceExtension.entitlements +++ b/ios/NotificationServiceExtension/NotificationServiceExtension.entitlements @@ -8,7 +8,7 @@ keychain-access-groups - group.eu.mhsl.marianum.mobile.client.widget + $(AppIdentifierPrefix)eu.mhsl.marianum.mobile.client.push diff --git a/ios/PUSH_NSE_SETUP.md b/ios/PUSH_NSE_SETUP.md index 488c219..6bddbd4 100644 --- a/ios/PUSH_NSE_SETUP.md +++ b/ios/PUSH_NSE_SETUP.md @@ -55,8 +55,9 @@ iOS zeigt die fertige Notification | `ios/Runner/AppDelegate.swift` | **geändert** | TALK_MESSAGE-Category + native Action-Behandlung | | `lib/push/push_registration_store.dart` | **geändert** | schreibt `nextcloud_username` + `nextcloud_base_url` group-scoped | | `lib/push/push_registration.dart` | **geändert** | `_persistNativeAuthContext()` bei `register()` | -| **Xcode-Target „NotificationServiceExtension"** | **FEHLT** | muss in Xcode angelegt werden (Abschnitt 3) | -| `ios/Runner.xcodeproj/project.pbxproj` | **unverändert** | bewusst NICHT von Hand editiert — Xcode legt das Target an | +| **Xcode-Target „NotificationServiceExtension"** | **existiert** | programmatisch via `xcodeproj`-Gem angelegt (2026-07-07), gespiegelt an der Share-Extension | +| `ios/Runner.xcodeproj/project.pbxproj` | **geändert** | NSE-Target, Dependency + „Embed Foundation Extensions" ergänzt | +| `ios/Flutter/NotificationServiceExtension-{Debug,Release,Profile}.xcconfig` | **neu** | Base-Configs, inkludieren `Generated.xcconfig` (Flutter-Versionsvariablen) | > **Wichtig:** Die vier Dateien unter `ios/NotificationServiceExtension/` liegen > schon auf der Platte. Beim Anlegen des Targets erzeugt Xcode eigene @@ -67,6 +68,15 @@ iOS zeigt die fertige Notification ## 3. Xcode-Checkliste (auf dem Mac) +> **Stand 2026-07-07:** Abschnitte 3.1–3.2 (Target anlegen, Dateien zuordnen) sind +> bereits **programmatisch** erledigt (via `xcodeproj`-Gem). Der unsignierte Build +> aller Targets läuft durch (`flutter build ios --no-codesign`), die +> `NotificationServiceExtension.appex` wird korrekt in `Runner.app/PlugIns/` +> eingebettet. **Offen bleiben nur noch Signing/Capabilities (3.3–3.4, 3.6)** — +> die brauchen den Apple-Developer-Account und einen signierten Build/Archive. +> Die 3.1/3.2-Anleitung unten bleibt als Referenz stehen (falls das Target mal neu +> aufgesetzt werden muss). + ### 3.1 Target anlegen 1. `ios/Runner.xcworkspace` in Xcode öffnen (nicht `.xcodeproj`). 2. **File → New → Target… → iOS → Notification Service Extension**. @@ -132,7 +142,18 @@ iOS zeigt die fertige Notification ## 4. Ermittelte Keychain-Details (verbindlich) Die Dart-Seite schreibt mit -`IOSOptions(groupId: 'group.eu.mhsl.marianum.mobile.client.widget', accessibility: first_unlock)`. +`IOSOptions(groupId: 'MY55VF3KPG.eu.mhsl.marianum.mobile.client.push', accessibility: first_unlock)`. + +> **Wichtig (Stand 2026-07-07):** Als Keychain-Access-Group wird **nicht** mehr die +> App-Group (`group.*`) genutzt, sondern eine **team-prefixed** Group +> (`$(AppIdentifierPrefix)eu.mhsl.marianum.mobile.client.push`). Grund: Die +> Xcode-verwalteten Provisioning-Profile gewähren als `keychain-access-groups` +> nur `.*` — eine `group.*`-App-Group fällt da **nicht** drunter, was +> Automatic Signing mit „doesn't match the entitlements file's value for the +> keychain-access-groups entitlement" abbricht. Runner und NSE teilen die Group, +> weil sie mit demselben Team (`MY55VF3KPG`) signieren. Der `MY55VF3KPG.`-Prefix +> ist der stabile AppIdentifierPrefix und in Dart/Swift hart hinterlegt. + Aus dem Quellcode von **`flutter_secure_storage_darwin` 0.3.2** (gepinnt in `pubspec.lock`) ergibt sich die exakte Ablage im Keychain: @@ -141,7 +162,7 @@ Aus dem Quellcode von **`flutter_secure_storage_darwin` 0.3.2** (gepinnt in | `kSecClass` | `kSecClassGenericPassword` | | `kSecAttrAccount` | der Dart-**Key**, **wortwörtlich** (kein Hash, kein Prefix) | | `kSecAttrService` | **nicht gesetzt** (die `IOSOptions` setzen kein `accountName`) | -| `kSecAttrAccessGroup` | `group.eu.mhsl.marianum.mobile.client.widget` | +| `kSecAttrAccessGroup` | `MY55VF3KPG.eu.mhsl.marianum.mobile.client.push` (team-prefixed) | | `kSecAttrAccessible` | `kSecAttrAccessibleAfterFirstUnlock` (aus `first_unlock`) | | Wert (`kSecValueData`) | **rohe UTF-8-Bytes** des Strings (PEM/Passwort im Klartext) | @@ -278,9 +299,14 @@ ist der fragilste Teil und **muss auf dem Gerät verifiziert werden**: innerhalb des NSE-Budgets). Nicht implementiert. 3. **`aps-environment = production`** ist noch nicht hart gesetzt (Abschnitt 3.6) — vor dem Release erledigen und im Archive gegenchecken (5.2). -4. **Keychain-Access-Group-Schreibweise.** Die Entitlements listen die App-Group - ohne `$(AppIdentifierPrefix)` als `keychain-access-groups`. Das ist das von - `flutter_secure_storage` erwartete Verhalten (Access-Group == App-Group-ID). - Sollte der Keychain-Zugriff wider Erwarten scheitern (Status `-34018` / - `errSecMissingEntitlement`), in **beiden** Targets die Keychain-Sharing- - Capability über die Xcode-UI neu setzen und Provisioning-Profile erneuern. +4. **Keychain-Access-Group-Schreibweise (gelöst 2026-07-07).** Die Entitlements + listen `$(AppIdentifierPrefix)eu.mhsl.marianum.mobile.client.push` als + `keychain-access-groups` (team-prefixed, **keine** App-Group). Damit greift das + `.*` der Xcode-Profile und Automatic Signing läuft ohne Portal-Änderung + durch (verifiziert: `flutter build ios --release` signiert Runner **und** NSE + mit `MY55VF3KPG.eu.mhsl.marianum.mobile.client.push`). Der frühere App-Group- + Ansatz (`group.*`) scheiterte an genau diesem Profil-Matching. Falls der + Keychain-Zugriff zur Laufzeit doch scheitert (Status `-34018` / + `errSecMissingEntitlement`), prüfen, dass Dart (`push_secure_storage.dart`) und + Swift (`AppDelegate.swift`, `NotificationService.swift`) **exakt denselben** + vollqualifizierten Group-String verwenden. diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 9d15eb5..266c5d1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -12,14 +12,18 @@ 3321F80F2FB1C00C0011C712 /* Share Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3321F8052FB1C00C0011C712 /* Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 33FDB0982EE9ABDC000B2391 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 33FDB0972EE9ABDC000B2391 /* GoogleService-Info.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 725388B5C3A724B19BD6FD06 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2960F029246C39E2A03F6D87 /* NotificationService.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7832A860F2264966809A9402 /* NotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C3B91710361EFED8934F0FDC /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 */; }; + 97D9AFE39CF2369A97F04721 /* PEM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509DCCD474353408FE5806C5 /* PEM.swift */; }; AA0101070000000011111111 /* TimetableWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AA0101020000000011111111 /* TimetableWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; AA0102010000000022222222 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0102020000000022222222 /* SceneDelegate.swift */; }; B8263932DB64B022CCEE7A53 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90960A132A5F91779B3FBE28 /* Pods_Runner.framework */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + C40CF71846788CD98CB99E2B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5B421EAF56B77B775E58E92 /* Foundation.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -37,6 +41,13 @@ remoteGlobalIDString = AA0101010000000011111111; remoteInfo = TimetableWidgetExtension; }; + AABEF26FF24F76E01DA5ADEA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = AEDC710FEBFF2CC736D88AB2; + remoteInfo = NotificationServiceExtension; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -48,6 +59,7 @@ files = ( 3321F80F2FB1C00C0011C712 /* Share Extension.appex in Embed Foundation Extensions */, AA0101070000000011111111 /* TimetableWidgetExtension.appex in Embed Foundation Extensions */, + 7832A860F2264966809A9402 /* NotificationServiceExtension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -65,17 +77,24 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 12B96C78930441C73F123636 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = Info.plist; path = Info.plist; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 17E2EE012C4361AC05DCA4C9 /* NotificationServiceExtension-Release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "NotificationServiceExtension-Release.xcconfig"; path = "Flutter/NotificationServiceExtension-Release.xcconfig"; sourceTree = ""; }; + 2960F029246C39E2A03F6D87 /* NotificationService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NotificationService.swift; path = NotificationService.swift; sourceTree = ""; }; 3321F8052FB1C00C0011C712 /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 33FDB0972EE9ABDC000B2391 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 36C6C71327C68B43F522F5B2 /* NotificationServiceExtension-Debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "NotificationServiceExtension-Debug.xcconfig"; path = "Flutter/NotificationServiceExtension-Debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4509EC31CB08BA9BF367AF6C /* 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 = ""; }; 4F2428AC5384E0EF8DAB462A /* Pods_Share_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Share_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 509DCCD474353408FE5806C5 /* PEM.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PEM.swift; path = PEM.swift; sourceTree = ""; }; + 5C2F4C79DD573778092882AB /* NotificationServiceExtension.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; name = NotificationServiceExtension.entitlements; path = NotificationServiceExtension.entitlements; sourceTree = ""; }; 60E1803A3FB28FCC6F435E99 /* Pods-Share Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.release.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.release.xcconfig"; sourceTree = ""; }; 64801C012A9112D500E8B558 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 90960A132A5F91779B3FBE28 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -94,10 +113,12 @@ BB0001040000000011111111 /* TimetableWidget-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "TimetableWidget-Debug.xcconfig"; path = "Flutter/TimetableWidget-Debug.xcconfig"; sourceTree = ""; }; BB0001050000000011111111 /* TimetableWidget-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "TimetableWidget-Release.xcconfig"; path = "Flutter/TimetableWidget-Release.xcconfig"; sourceTree = ""; }; BB0001060000000011111111 /* TimetableWidget-Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "TimetableWidget-Profile.xcconfig"; path = "Flutter/TimetableWidget-Profile.xcconfig"; sourceTree = ""; }; + C3B91710361EFED8934F0FDC /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; C7E1879BE78835C7E3256316 /* 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 = ""; }; DD904D7C0FC0AD11449CEB80 /* Pods-Share Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.debug.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.debug.xcconfig"; sourceTree = ""; }; EF5279D9BF8FCBB117AF998E /* Pods-Share Extension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.profile.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.profile.xcconfig"; sourceTree = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + F5B421EAF56B77B775E58E92 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + F758339399E7B5FD1A88FC92 /* NotificationServiceExtension-Profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "NotificationServiceExtension-Profile.xcconfig"; path = "Flutter/NotificationServiceExtension-Profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -169,6 +190,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D2447D92E9CCD96B3292B17B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C40CF71846788CD98CB99E2B /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -185,15 +214,36 @@ path = Pods; sourceTree = ""; }; + 553E8F3190182FD2E527FEB5 /* NotificationServiceExtension */ = { + isa = PBXGroup; + children = ( + 2960F029246C39E2A03F6D87 /* NotificationService.swift */, + 509DCCD474353408FE5806C5 /* PEM.swift */, + 12B96C78930441C73F123636 /* Info.plist */, + 5C2F4C79DD573778092882AB /* NotificationServiceExtension.entitlements */, + ); + name = NotificationServiceExtension; + path = NotificationServiceExtension; + sourceTree = SOURCE_ROOT; + }; 731388A08E3B330B216381D0 /* Frameworks */ = { isa = PBXGroup; children = ( 90960A132A5F91779B3FBE28 /* Pods_Runner.framework */, 4F2428AC5384E0EF8DAB462A /* Pods_Share_Extension.framework */, + 80D9B9919D3D7CCA2A80C8C5 /* iOS */, ); name = Frameworks; sourceTree = ""; }; + 80D9B9919D3D7CCA2A80C8C5 /* iOS */ = { + isa = PBXGroup; + children = ( + F5B421EAF56B77B775E58E92 /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -208,6 +258,9 @@ BB0001040000000011111111 /* TimetableWidget-Debug.xcconfig */, BB0001050000000011111111 /* TimetableWidget-Release.xcconfig */, BB0001060000000011111111 /* TimetableWidget-Profile.xcconfig */, + 36C6C71327C68B43F522F5B2 /* NotificationServiceExtension-Debug.xcconfig */, + 17E2EE012C4361AC05DCA4C9 /* NotificationServiceExtension-Release.xcconfig */, + F758339399E7B5FD1A88FC92 /* NotificationServiceExtension-Profile.xcconfig */, ); name = Flutter; sourceTree = ""; @@ -222,6 +275,7 @@ 97C146EF1CF9000F007C117D /* Products */, 345F4BD4143471FDA71626DE /* Pods */, 731388A08E3B330B216381D0 /* Frameworks */, + 553E8F3190182FD2E527FEB5 /* NotificationServiceExtension */, ); sourceTree = ""; }; @@ -231,6 +285,7 @@ 97C146EE1CF9000F007C117D /* Runner.app */, 3321F8052FB1C00C0011C712 /* Share Extension.appex */, AA0101020000000011111111 /* TimetableWidgetExtension.appex */, + C3B91710361EFED8934F0FDC /* NotificationServiceExtension.appex */, ); name = Products; sourceTree = ""; @@ -278,9 +333,6 @@ productType = "com.apple.product-type.app-extension"; }; 97C146ED1CF9000F007C117D /* Runner */ = { - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( @@ -299,8 +351,12 @@ dependencies = ( 3321F80E2FB1C00C0011C712 /* PBXTargetDependency */, AA0101090000000011111111 /* PBXTargetDependency */, + 43763BD5552A36CA28890DFA /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -325,13 +381,27 @@ productReference = AA0101020000000011111111 /* TimetableWidgetExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + AEDC710FEBFF2CC736D88AB2 /* NotificationServiceExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = C3A6F3FA3E3FD220B736D6E5 /* Build configuration list for PBXNativeTarget "NotificationServiceExtension" */; + buildPhases = ( + DF8C1170E7DF96711030EAC0 /* Sources */, + D2447D92E9CCD96B3292B17B /* Frameworks */, + 239068341DC6E6B36193EC96 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NotificationServiceExtension; + productName = NotificationServiceExtension; + productReference = C3B91710361EFED8934F0FDC /* NotificationServiceExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, - ); isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; @@ -360,6 +430,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -367,11 +440,19 @@ 97C146ED1CF9000F007C117D /* Runner */, 3321F8042FB1C00C0011C712 /* Share Extension */, AA0101010000000011111111 /* TimetableWidgetExtension */, + AEDC710FEBFF2CC736D88AB2 /* NotificationServiceExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 239068341DC6E6B36193EC96 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3321F8032FB1C00C0011C712 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -520,6 +601,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DF8C1170E7DF96711030EAC0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 725388B5C3A724B19BD6FD06 /* NotificationService.swift in Sources */, + 97D9AFE39CF2369A97F04721 /* PEM.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -528,6 +618,12 @@ target = 3321F8042FB1C00C0011C712 /* Share Extension */; targetProxy = 3321F80D2FB1C00C0011C712 /* PBXContainerItemProxy */; }; + 43763BD5552A36CA28890DFA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = NotificationServiceExtension; + target = AEDC710FEBFF2CC736D88AB2 /* NotificationServiceExtension */; + targetProxy = AABEF26FF24F76E01DA5ADEA /* PBXContainerItemProxy */; + }; AA0101090000000011111111 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AA0101010000000011111111 /* TimetableWidgetExtension */; @@ -929,6 +1025,29 @@ }; name = Release; }; + A2B046F78AD55232F08583F3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 36C6C71327C68B43F522F5B2 /* NotificationServiceExtension-Debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = MY55VF3KPG; + INFOPLIST_FILE = NotificationServiceExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client.NotificationServiceExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; AA01010A0000000011111111 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = BB0001040000000011111111 /* TimetableWidget-Debug.xcconfig */; @@ -1051,6 +1170,54 @@ }; name = Profile; }; + E5EE58583E83B7694F6C3C5E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 17E2EE012C4361AC05DCA4C9 /* NotificationServiceExtension-Release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = MY55VF3KPG; + INFOPLIST_FILE = NotificationServiceExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client.NotificationServiceExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + F418DF43742DC2E30B161652 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F758339399E7B5FD1A88FC92 /* NotificationServiceExtension-Profile.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = MY55VF3KPG; + INFOPLIST_FILE = NotificationServiceExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client.NotificationServiceExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1094,13 +1261,25 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C3A6F3FA3E3FD220B736D6E5 /* Build configuration list for PBXNativeTarget "NotificationServiceExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E5EE58583E83B7694F6C3C5E /* Release */, + A2B046F78AD55232F08583F3 /* Debug */, + F418DF43742DC2E30B161652 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ + /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; /* End XCLocalSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { isa = XCSwiftPackageProductDependency; diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ca61889..d506520 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/app-check.git", "state" : { - "revision" : "61b85103a1aeed8218f17c794687781505fbbef5", - "version" : "11.2.0" + "revision" : "bb4002485ff867768dec13bf904a2ddb050bd1b1", + "version" : "11.3.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk", "state" : { - "revision" : "d10045cace0b4c335c4efa8f7df7e9a9fc5a7c60", - "version" : "12.13.0" + "revision" : "42e81d245e30e49ea6a5830cf2842d44a1591270", + "version" : "12.15.0" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk", "state" : { - "revision" : "19dffda9a9caf8d86570ff846535902d8509d7bf", - "version" : "3.5.0" + "revision" : "9bfcc6cf435b2e7c5562c1900b8680c594fa9a64", + "version" : "3.6.0" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "c2c76bebcfbb90d90ea10599f934f9af160e1604", - "version" : "12.13.0" + "revision" : "144855f40d8668927f256a3045f7fdc4c3f4338b", + "version" : "12.15.0" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleUtilities.git", "state" : { - "revision" : "60da361632d0de02786f709bdc0c4df340f7613e", - "version" : "8.1.0" + "revision" : "c46e5f8b7c23265f17c24ca7f9fa1b13ded7a822", + "version" : "8.1.1" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/leveldb.git", "state" : { - "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", - "version" : "1.22.2" + "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", + "version" : "1.22.5" } }, { @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/nanopb.git", "state" : { - "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", - "version" : "2.30910.0" + "revision" : "3851d94a41890dea16dc3db34caf60e585cb4163", + "version" : "2.30910.1" } }, { @@ -140,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/promises.git", "state" : { - "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", - "version" : "2.4.0" + "revision" : "f4a19a3c313dc2616c70bb49d29a799fb16be837", + "version" : "2.4.1" } }, { diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index c806d8f..881e60f 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -12,7 +12,7 @@ import UserNotifications private let markReadActionId = "TALK_MARK_READ" // Shared (App Group) keychain — same group as the NSE and the Dart side. - private let keychainAccessGroup = "group.eu.mhsl.marianum.mobile.client.widget" + private let keychainAccessGroup = "MY55VF3KPG.eu.mhsl.marianum.mobile.client.push" private let usernameAccount = "nextcloud_username" private let appPasswordAccount = "nextcloud_app_password" private let baseUrlAccount = "nextcloud_base_url" diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index 01e802c..275fdd2 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -11,7 +11,7 @@ keychain-access-groups - group.eu.mhsl.marianum.mobile.client.widget + $(AppIdentifierPrefix)eu.mhsl.marianum.mobile.client.push diff --git a/lib/api/errors/error_mapper.dart b/lib/api/errors/error_mapper.dart index 9cafa3a..a100623 100644 --- a/lib/api/errors/error_mapper.dart +++ b/lib/api/errors/error_mapper.dart @@ -27,7 +27,6 @@ AppException? _dioToAppException(DioException error) { case DioExceptionType.connectionTimeout: case DioExceptionType.sendTimeout: case DioExceptionType.receiveTimeout: - case DioExceptionType.transformTimeout: return NetworkException.timeout(technicalDetails: error.message); case DioExceptionType.connectionError: return NetworkException(technicalDetails: error.message); diff --git a/lib/api/marianumconnect/errors/marianumconnect_error.dart b/lib/api/marianumconnect/errors/marianumconnect_error.dart index 18fa4b7..4160cc4 100644 --- a/lib/api/marianumconnect/errors/marianumconnect_error.dart +++ b/lib/api/marianumconnect/errors/marianumconnect_error.dart @@ -14,7 +14,6 @@ AppException mapMarianumConnectError(DioException error) { case DioExceptionType.connectionTimeout: case DioExceptionType.sendTimeout: case DioExceptionType.receiveTimeout: - case DioExceptionType.transformTimeout: return NetworkException.timeout(technicalDetails: error.message); case DioExceptionType.connectionError: return NetworkException(technicalDetails: error.message); diff --git a/lib/push/push_secure_storage.dart b/lib/push/push_secure_storage.dart index c157f21..0ab50c9 100644 --- a/lib/push/push_secure_storage.dart +++ b/lib/push/push_secure_storage.dart @@ -5,11 +5,15 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; /// the server public key and the Nextcloud app password to decrypt pushes /// while the app is not running. /// -/// The value reuses the existing app-group id already present in the iOS -/// project (`ios/Runner/Runner.entitlements`). Phase 3 must additionally list -/// it under `keychain-access-groups` for both the Runner and the NSE target. +/// A team-prefixed keychain access group (`$(AppIdentifierPrefix)eu.mhsl…push`) +/// listed under `keychain-access-groups` in both the Runner and the NSE +/// entitlements. The literal `MY55VF3KPG.` prefix is the team's stable +/// AppIdentifierPrefix; Runner and NSE share the group because they sign with +/// the same team. (App-group ids like `group.*` can't be used here because the +/// Xcode-managed profiles only grant `.*` keychain groups.) /// On Android `groupId` is ignored, so this is a no-op there. -const String kPushKeychainGroup = 'group.eu.mhsl.marianum.mobile.client.widget'; +const String kPushKeychainGroup = + 'MY55VF3KPG.eu.mhsl.marianum.mobile.client.push'; /// [IOSOptions] used for every push-related secure-storage entry. Uses /// `first_unlock` accessibility so the NSE can read the key material after the