completed iOS push notification setup: created the NotificationServiceExtension Xcode target programmatically (via xcodeproj), embedded it in Runner, and added its xcconfig base configs; fixed Automatic Signing by switching the NSE↔Runner shared keychain from an app-group to a team-prefixed access group across entitlements, Dart and Swift; fixed the analyzer break from dio 5.9.2 dropping DioExceptionType.transformTimeout

This commit is contained in:
Marianum
2026-07-17 15:04:48 +02:00
parent e625216a90
commit a0c55a811c
13 changed files with 256 additions and 46 deletions
@@ -0,0 +1 @@
#include "Generated.xcconfig"
@@ -0,0 +1 @@
#include "Generated.xcconfig"
@@ -0,0 +1 @@
#include "Generated.xcconfig"
@@ -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] = [
@@ -8,7 +8,7 @@
</array>
<key>keychain-access-groups</key>
<array>
<string>group.eu.mhsl.marianum.mobile.client.widget</string>
<string>$(AppIdentifierPrefix)eu.mhsl.marianum.mobile.client.push</string>
</array>
</dict>
</plist>
+36 -10
View File
@@ -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.13.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.33.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 `<TeamID>.*` — 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
`<TeamID>.*` 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.
+188 -9
View File
@@ -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 = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
17E2EE012C4361AC05DCA4C9 /* NotificationServiceExtension-Release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "NotificationServiceExtension-Release.xcconfig"; path = "Flutter/NotificationServiceExtension-Release.xcconfig"; sourceTree = "<group>"; };
2960F029246C39E2A03F6D87 /* NotificationService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NotificationService.swift; path = NotificationService.swift; sourceTree = "<group>"; };
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 = "<group>"; };
36C6C71327C68B43F522F5B2 /* NotificationServiceExtension-Debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "NotificationServiceExtension-Debug.xcconfig"; path = "Flutter/NotificationServiceExtension-Debug.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
5C2F4C79DD573778092882AB /* NotificationServiceExtension.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; name = NotificationServiceExtension.entitlements; path = NotificationServiceExtension.entitlements; sourceTree = "<group>"; };
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 = "<group>"; };
64801C012A9112D500E8B558 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
@@ -94,10 +113,12 @@
BB0001040000000011111111 /* TimetableWidget-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "TimetableWidget-Debug.xcconfig"; path = "Flutter/TimetableWidget-Debug.xcconfig"; sourceTree = "<group>"; };
BB0001050000000011111111 /* TimetableWidget-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "TimetableWidget-Release.xcconfig"; path = "Flutter/TimetableWidget-Release.xcconfig"; sourceTree = "<group>"; };
BB0001060000000011111111 /* TimetableWidget-Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "TimetableWidget-Profile.xcconfig"; path = "Flutter/TimetableWidget-Profile.xcconfig"; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
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 = "<group>"; };
/* 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 = "<group>";
};
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 = "<group>";
};
80D9B9919D3D7CCA2A80C8C5 /* iOS */ = {
isa = PBXGroup;
children = (
F5B421EAF56B77B775E58E92 /* Foundation.framework */,
);
name = iOS;
sourceTree = "<group>";
};
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 = "<group>";
@@ -222,6 +275,7 @@
97C146EF1CF9000F007C117D /* Products */,
345F4BD4143471FDA71626DE /* Pods */,
731388A08E3B330B216381D0 /* Frameworks */,
553E8F3190182FD2E527FEB5 /* NotificationServiceExtension */,
);
sourceTree = "<group>";
};
@@ -231,6 +285,7 @@
97C146EE1CF9000F007C117D /* Runner.app */,
3321F8052FB1C00C0011C712 /* Share Extension.appex */,
AA0101020000000011111111 /* TimetableWidgetExtension.appex */,
C3B91710361EFED8934F0FDC /* NotificationServiceExtension.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -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;
@@ -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"
}
},
{
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -11,7 +11,7 @@
</array>
<key>keychain-access-groups</key>
<array>
<string>group.eu.mhsl.marianum.mobile.client.widget</string>
<string>$(AppIdentifierPrefix)eu.mhsl.marianum.mobile.client.push</string>
</array>
</dict>
</plist>
-1
View File
@@ -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);
@@ -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);
+8 -4
View File
@@ -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 `<TeamID>.*` 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