-
Notifications
You must be signed in to change notification settings - Fork 19
fix(ios): stop 0xdead10cc account-creation crash and fix lost-login error message #701
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -179,6 +179,8 @@ final class NotificationService: UNNotificationServiceExtension { | |
| let logsDir = logsDirURL.path | ||
| try? FileManager.default.createDirectory(at: dataDirURL, withIntermediateDirectories: true) | ||
| try? FileManager.default.createDirectory(at: logsDirURL, withIntermediateDirectories: true) | ||
| WhiteNoiseDataProtection.apply(atPath: dataDir) | ||
| WhiteNoiseDataProtection.apply(atPath: logsDir) | ||
|
|
||
| guard let json = collectNotificationsJson( | ||
| dataDir: dataDir, | ||
|
|
@@ -269,6 +271,62 @@ final class NotificationService: UNNotificationServiceExtension { | |
| } | ||
| } | ||
|
|
||
| /// Applies `completeUntilFirstUserAuthentication` data protection to a directory | ||
| /// and everything inside it. | ||
| /// | ||
| /// The White Noise database lives in the shared App Group container that this | ||
| /// extension reads. Files there default to a protection class that is | ||
| /// unavailable while the device is locked; holding a SQLite/WAL lock on such a | ||
| /// file in a shared container across suspension makes iOS terminate the process | ||
| /// with 0xdead10cc. Setting the relaxed class keeps the files reachable after | ||
| /// first unlock and avoids that termination. | ||
| enum WhiteNoiseDataProtection { | ||
| private static let markerName = ".wn_data_protection_v1" | ||
|
|
||
| @discardableResult | ||
| static func apply(atPath path: String) -> Bool { | ||
| let fileManager = FileManager.default | ||
| let attributes: [FileAttributeKey: Any] = [ | ||
| .protectionKey: FileProtectionType.completeUntilFirstUserAuthentication | ||
| ] | ||
| // Always set the class on the directory itself; new files (db, -wal, -shm, | ||
| // media cache) inherit it, so this is the only step needed on most launches. | ||
| var success = setAttributes(attributes, atPath: path, using: fileManager) | ||
|
|
||
| // Downgrade files that predate this fix exactly once. The extension runs on | ||
| // a tight time/memory budget, so recursing the whole data dir on every push | ||
| // is unacceptable; a marker gates it after the first successful pass. | ||
| let markerPath = (path as NSString).appendingPathComponent(markerName) | ||
| guard !fileManager.fileExists(atPath: markerPath) else { | ||
| return success | ||
| } | ||
| if let enumerator = fileManager.enumerator(atPath: path) { | ||
| for case let relativePath as String in enumerator { | ||
| let itemPath = (path as NSString).appendingPathComponent(relativePath) | ||
| success = setAttributes(attributes, atPath: itemPath, using: fileManager) && success | ||
| } | ||
| } | ||
| if success { | ||
| fileManager.createFile(atPath: markerPath, contents: nil, attributes: attributes) | ||
| } | ||
| return success | ||
| } | ||
|
|
||
| private static func setAttributes( | ||
| _ attributes: [FileAttributeKey: Any], | ||
| atPath path: String, | ||
| using fileManager: FileManager | ||
| ) -> Bool { | ||
| do { | ||
| try fileManager.setAttributes(attributes, ofItemAtPath: path) | ||
| return true | ||
| } catch { | ||
| NSLog("White Noise NSE failed to set data protection on %@: %@", path, error.localizedDescription) | ||
| return false | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+274
to
+328
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff Duplicated This enum is copied verbatim from If extraction is not feasible, add a comment in both files noting the duplication so future changes are synchronized. 🤖 Prompt for AI Agents |
||
|
|
||
| private enum BackgroundPushCollectionResult { | ||
| case success(BackgroundPushResult) | ||
| case failure(String) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
Consider writing the marker file even on partial success.
If
setAttributesfails for some files (e.g., a locked file) but succeeds for others, the current logic skips creating the marker. On the next launch the code re-enumerates and re-attempts the entire directory. This is resilient for transient failures but means every launch incurs the full recursive walk until 100% success.Given that the PR describes this as a one-time migration for files that "predate this fix" and the NSE operates under tight time/memory constraints, consider creating the marker unconditionally (or on first invocation regardless of outcome) so the recursive pass is truly bounded to once. Persistent failures would still be logged.
🤖 Prompt for AI Agents