-
Notifications
You must be signed in to change notification settings - Fork 17
Add tvOS (Apple TV) support for NetBird VPN client #36
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: main
Are you sure you want to change the base?
Conversation
This commit introduces full tvOS support for the NetBird iOS client,
enabling VPN connectivity on Apple TV devices.
- Added NetBird TV app target with tvOS 16.0+ deployment
- Created tvOS-specific UI using SwiftUI optimized for "10-foot experience"
- Tab-based navigation: Connection, Peers, Networks, Settings
- Large touch targets and text for Siri Remote navigation
- TVMainView: Main tab navigation and connection status
- TVConnectionView: Large connect/disconnect button with status display
- TVPeersView: List of connected peers with connection details
- TVNetworksView: Network routes selection and management
- TVSettingsView: App settings and logout functionality
- TVAuthView: QR code + device code authentication flow
- Implemented OAuth device authorization flow for tvOS
- Displays QR code that user scans with phone to authenticate
- Shows user code as fallback for manual entry
- Polls for authentication completion and auto-dismisses on success
tvOS has stricter sandbox restrictions than iOS:
1. **UserDefaults-based Config Storage**
- tvOS blocks file writes to App Group containers
- Config stored in shared UserDefaults instead of files
- Added Preferences methods: saveConfigToUserDefaults(),
loadConfigFromUserDefaults(), hasConfigInUserDefaults()
2. **Preloaded Config in Go SDK**
- SDK modified to accept config via setConfigFromJSON()
- Avoids file I/O that would fail in tvOS sandbox
- Config passed from UserDefaults to SDK at runtime
3. **Raw Syscall Tunnel FD Discovery**
- tvOS SDK doesn't expose ctl_info, sockaddr_ctl, CTLIOCGINFO
- Implemented findTunnelFileDescriptorTvOS() using raw memory ops
- Manually defines kernel structure layouts at byte level
- Uses getpeername() and ioctl() which ARE available on tvOS
- Added NetBirdTVNetworkExtension target
- Separate PacketTunnelProvider.swift with tvOS-specific handling
- Extensive logging for debugging via Console.app
- Handles "LoginTV" message for device auth flow
- Loads config from UserDefaults into SDK memory
- isLoginRequired() now verifies session with management server
- Previously only checked if config existed (caused post-restart failures)
- Shows QR code re-auth flow when OAuth session expires
- Added Platform.swift for iOS/tvOS conditional compilation
- Shared code uses #if os(tvOS) / #if os(iOS) where needed
- Common ViewModels work across both platforms
|
Warning Rate limit exceeded@shuuri-labs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 20 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds tvOS support: new NetBird TV app and packet-tunnel network extension, IPC/config transfer and persistence changes, platform abstractions and configuration providers, tvOS UIs and theming, extensive project/CI updates, and adapter/extension integration. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User (Apple TV)
participant TVApp as NetBird TV App
participant Pref as ConfigurationProvider / UserDefaults
participant Ext as PacketTunnel Extension
participant SDK as NetBird SDK
participant LoginSvc as Login Service
User->>TVApp: Start device-code login
TVApp->>Pref: loadConfigFromUserDefaults()
Pref-->>TVApp: configJSON
TVApp->>Ext: sendConfigToExtension(configJSON)
Ext->>SDK: initializeConfig / loginTV
SDK->>LoginSvc: Request device code
LoginSvc-->>SDK: DeviceCode + LoginURL
SDK-->>Ext: Return LoginURL + UserCode
Ext-->>TVApp: Config + userCode
TVApp->>User: Display QR and device code
loop Poll every 2s
TVApp->>Ext: checkLoginComplete()
Ext->>SDK: Check auth status
alt Auth complete
SDK-->>Ext: Success + config
Ext-->>TVApp: Login Complete
TVApp->>User: Close Auth UI
else Pending
Ext-->>TVApp: Not ready
end
end
sequenceDiagram
participant TVApp as NetBird TV App
participant Pref as ConfigurationProvider / UserDefaults
participant Ext as PacketTunnel Extension
participant SDK as NetBird SDK
participant VPN as NEPacketTunnelProvider
TVApp->>Pref: loadConfigFromUserDefaults()
Pref-->>TVApp: configJSON
TVApp->>Ext: sendConfigToExtension(configJSON)
Ext->>Ext: save config (UserDefaults / file fallback)
Ext->>SDK: NetBirdSDKNewClient(config)
SDK-->>Ext: client ready
Ext->>VPN: startTunnel()
VPN-->>Ext: Tunnel active
Ext-->>TVApp: Status update
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Update ServerViewModel to use new SDK callback-based API - Fix focus navigation on Networks and Settings tabs - Add white text on focus for better readability across all cards - Increase filter bar spacing to prevent highlight overlap - Add TVSettingsInfoRow for non-interactive display items
- Remove duplicate TVColors structs from 5 TV view files - Add ClearConfig IPC message to clear extension-local config on logout - Switch MainView from viewModel.isIpad to DeviceType.isPad - Remove unused isTV/isIpad properties from MainViewModel - Add TVColors.swift to Xcode project
- Remove dead shared UserDefaults fallback code in NetBirdAdapter - Document tvOS config storage architecture in Preferences.swift - Fix onChange deprecation warnings in TVSettingsView - Add "no peer auth method" detection in ServerViewModel
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
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.
Actionable comments posted: 15
♻️ Duplicate comments (4)
NetBird/Source/App/NetBirdApp.swift (1)
45-53: Firebase configuration duplication noted.This tvOS initialization mirrors the iOS
AppDelegatesetup at lines 27-30. See the earlier comment for a suggested refactor to eliminate this duplication.NetBird.xcodeproj/xcshareddata/xcschemes/NetbirdNetworkExtension.xcscheme (1)
3-3: Consistent with main scheme update.The
LastUpgradeVersionbump to2610matches the update inNetBird.xcscheme. Same verification note applies regarding the version number.NetBird/Source/App/ViewModels/MainViewModel.swift (2)
264-290: This code appears to be dead/duplicated.Per past review comments, management server URL handling was migrated to
ServerViewModel. If this function is no longer used, it should be removed to avoid confusion.
307-324: This code appears to be dead/duplicated.Per past review comments, setup key handling was migrated to
ServerViewModel. If this function is no longer used, it should be removed.
🧹 Nitpick comments (23)
README.md (2)
101-101: Wrap the repository URL in markdown link syntax for consistency.The other repository entries in this section use markdown link syntax
[text](url), but this line uses a bare URL. Wrap the URL to maintain consistent formatting with the rest of the list.Apply this diff:
- iOS/tvOS Client: https://github.com/netbirdio/ios-client (this repository) + iOS/tvOS Client: [https://github.com/netbirdio/ios-client](https://github.com/netbirdio/ios-client) (this repository)
90-92: Minor: Consider rephrasing for variety.Line 88 uses "needs to be connected" and line 92 uses "needs to be paired"—both opening with "needs to be." Consider varying the phrasing in one of these sentences to improve readability.
NetBird/Source/App/NetBirdApp.swift (1)
19-34: Consider extracting Firebase configuration to reduce duplication.The Firebase setup logic is duplicated between the iOS
AppDelegate(lines 27-30) and the tvOSinit()method (lines 48-51). Consider extracting this into a shared helper method to improve maintainability. Additionally, silent failure when the plist is missing or invalid could complicate debugging—adding error logging would be helpful.Example refactor:
private func configureFirebase() { guard let path = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist"), let options = FirebaseOptions(contentsOfFile: path) else { print("Warning: GoogleService-Info.plist not found or invalid. Firebase not configured.") return } FirebaseApp.configure(options: options) }Then call
configureFirebase()from both the iOSAppDelegateand tvOSinit().NetbirdKit/Device.swift (1)
27-53: LGTM! Well-designed persistent device name generation.The implementation correctly generates a unique device name once and caches it for consistency across app launches. The force unwrap on
randomElement()is safe sincecharactersis a non-empty literal.Note:
defaults.synchronize()(line 48) is deprecated on modern iOS/tvOS as the system handles persistence automatically, but it's harmless here.NetbirdKit/Preferences.swift (1)
49-59: Redundant conditional—both branches are identical.The
#if os(tvOS)and#elsebranches execute the same code. Either remove the conditional or document why it's kept for future differentiation.static func newPreferences() -> NetBirdSDKPreferences? { - #if os(tvOS) - // On tvOS, creating SDK Preferences may fail if the app doesn't have write access - // to the App Group container. Try anyway - if it fails, settings will be managed - // via the extension instead. - // Note: The SDK now uses DirectWriteOutConfig which may work better on tvOS. - return NetBirdSDKNewPreferences(configFile(), stateFile()) - #else return NetBirdSDKNewPreferences(configFile(), stateFile()) - #endif }NetbirdNetworkExtension/NetBirdAdapter.swift (1)
461-484: Regex-based JSON manipulation is fragile.Using regex patterns like
"ManagementURL"\\s*:\\s*"([^"]*)"to parse/modify JSON can fail on escaped quotes, Unicode escapes, or whitespace variations. Consider usingJSONSerializationorCodable.+ /// Extract the management URL from a config JSON string using proper JSON parsing static func extractManagementURL(from configJSON: String) -> String? { - let pattern = "\"ManagementURL\"\\s*:\\s*\"([^\"]*)\"" - guard let regex = try? NSRegularExpression(pattern: pattern, options: []), - let match = regex.firstMatch(in: configJSON, options: [], range: NSRange(configJSON.startIndex..., in: configJSON)), - let urlRange = Range(match.range(at: 1), in: configJSON) else { + guard let data = configJSON.data(using: .utf8), + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let url = json["ManagementURL"] as? String, + !url.isEmpty else { return nil } - let url = String(configJSON[urlRange]) - return url.isEmpty ? nil : url + return url }NetbirdKit/NetworkExtensionAdapter.swift (4)
17-27: Duplicate listener class across codebase.
ConfigSSOListenerduplicates the same pattern asSSOCheckListenerinMainViewModel.swiftandSSOListenerImplinServerViewModel.swift. Consider extracting a single shared implementation inNetbirdKitto avoid maintaining multiple copies.
181-216:initializeConfigViaExtension()appears unused.This function is defined but never called. If it's intended for future use, consider marking it with a TODO or removing it to reduce dead code.
#!/bin/bash # Verify that initializeConfigViaExtension is not called anywhere rg -n "initializeConfigViaExtension" --type swift
247-269: SDK client created on everyisLoginRequired()call.On tvOS, a new
NetBirdSDKNewClientis instantiated each timeisLoginRequired()is called. This may be expensive if the SDK performs key generation or other heavy initialization. Consider caching the client or deferring this check.
333-335: Remove redundantVoidreturn type.Per SwiftLint, returning
Voidin a function declaration is redundant.- func stop() -> Void { + func stop() { self.vpnManager?.connection.stopVPNTunnel() }NetBird/Source/App/ViewModels/MainViewModel.swift (1)
18-28: Duplicate SSO listener pattern.
SSOCheckListenerduplicates the same pattern asConfigSSOListenerinNetworkExtensionAdapter.swiftandSSOListenerImplinServerViewModel.swift. Consider consolidating these into a single shared implementation.NetBird/Source/App/ViewModels/ServerViewModel.swift (1)
15-54: Well-designed listener implementations.
SSOListenerImplandErrListenerImpluse typed callbacks instead of optional closures, providing cleaner semantics. However, these duplicate similar listeners inMainViewModel.swiftandNetworkExtensionAdapter.swift.Consider consolidating all SDK listener implementations into a shared location in
NetbirdKitto reduce duplication across the codebase.NetBird/Source/App/Views/MainView.swift (1)
30-30: SwiftLint naming convention note.SwiftLint flags
iOSMainViewfor not starting with an uppercase character. WhileIOSMainViewwould satisfy the linting rule,iOSMainViewfollows Apple's branding convention (iOS, iPhone, iPad) and is more readable. Consider either:
- Keeping
iOSMainViewand suppressing the lint warning with// swiftlint:disable:next type_name- Renaming to
IOSMainViewfor strict conformanceIf you choose to keep the current naming (recommended for readability), add a suppression comment:
+// swiftlint:disable:next type_name struct iOSMainView: View {NetBird/Source/App/Views/PeerTabView.swift (1)
196-225: Platform-specific context menu is well-implemented.The iOS clipboard actions and tvOS static text fallback are correctly guarded. Consider wrapping the debug
#if DEBUGfor consistency with the logging approach used elsewhere in this PR.Button("Copy FQDN") { UIPasteboard.general.string = peer.fqdn + #if DEBUG print("Copied FQDN to clipboard") + #endif viewModel.showFqdnCopiedAlert = true ... } Button("Copy IP") { UIPasteboard.general.string = peer.ip + #if DEBUG print("Copied IP to clipboard") + #endif viewModel.showIpCopiedAlert = true ... }NetBird/Source/App/Views/TV/TVNetworksView.swift (3)
11-12: Remove unusedUIKitimport.The
UIKitimport doesn't appear to be used in this file. All types used are from SwiftUI.import SwiftUI -import UIKit
121-128: Refresh animation duration is not tied to actual data fetch completion.The 2-second delay is arbitrary and doesn't reflect when
getRoutes()actually completes. Consider using a completion handler fromgetRoutes()to stop the animation when data is ready.private func refresh() { isRefreshing = true - viewModel.routeViewModel.getRoutes() - - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + viewModel.routeViewModel.getRoutes { isRefreshing = false } }This would require adding a completion parameter to
getRoutes()inRoutesViewModel.
190-198: Magic string"invalid Prefix"is fragile.The comparison against
"invalid Prefix"relies on an exact string match from another layer. Consider defining this as a constant or using a more robust check (e.g., an enum or dedicated property on the route model).NetBird/Source/App/Views/TV/TVSettingsView.swift (1)
141-185: Consider makingactionnon-optional or using a different pattern.The
TVSettingsRowaccepts an optionalactionand disables the button whenaction == nil(line 183). However, using a non-focusable info row (TVSettingsInfoRow) for display-only items would be more semantically correct. The current pattern could confuse users who focus on a row that does nothing.If you want to keep this pattern, consider providing clearer visual feedback that the row is disabled:
.disabled(action == nil) +.opacity(action == nil ? 0.5 : 1.0)NetBird/Source/App/Platform/Platform.swift (1)
182-189:tvFocusable()modifier applies.focusable()unconditionally on tvOS.On tvOS,
.focusable()makes a view focusable for the Siri Remote. This is correct, but note that applying this to views that are already naturally focusable (likeButton) is redundant. Consider documenting the intended use case (e.g., for non-interactive views that need focus).NetBird/Source/App/Views/TV/TVPeersView.swift (1)
27-28: String comparison for connection state is fragile.Comparing
viewModel.extensionStateText == "Connected"relies on a magic string. If the state text changes (e.g., localization), this check will break silently. Consider using an enum or constant for state comparison.- if viewModel.extensionStateText == "Connected" && + if viewModel.isConnected && viewModel.peerViewModel.peerInfo.count > 0 {If
isConnecteddoesn't exist on the ViewModel, consider adding it as a computed property that encapsulates this check.NetBirdTVNetworkExtension/PacketTunnelProvider.swift (3)
219-238:UserDefaults.synchronize()is deprecated.
synchronize()is deprecated and generally unnecessary as the system handles persistence automatically. While it might seem safer for IPC scenarios, it can actually cause performance issues.// Save to extension-local UserDefaults (not shared App Group) UserDefaults.standard.set(configJSON, forKey: "netbird_config_json_local") - UserDefaults.standard.synchronize()Also applies to line 252.
613-657: Redundant file clearing before writing ininitializeLogging.Lines 630-639 open the file for writing, clear it with an empty string write, then write the log message. This is overly complex. Simply truncating and writing in one step would be cleaner.
if fileManager.fileExists(atPath: logURLValid.path) { - if let fileHandle = try? FileHandle(forWritingTo: logURLValid) { - do { - try "".write(to: logURLValid, atomically: true, encoding: .utf8) - } catch { - print("Error handling the log file: \(error)") - } - if let data = logMessage.data(using: .utf8) { - fileHandle.write(data) - } - fileHandle.closeFile() - } else { - print("Failed to open the log file for writing.") - } + do { + try logMessage.write(to: logURLValid, atomically: true, encoding: .utf8) + } catch { + print("Failed to overwrite the log file: \(error.localizedDescription)") + } } else {This simplifies the existing-file case to match the new-file case.
619-620: Unused variableserrorandsuccess.The variables
error(line 619) andsuccess(line 620) are declared early but only used much later (lines 652-656). Consider moving their declarations closer to usage for clarity, or removing them if the success/error logging isn't critical.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (12)
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/netbird-tvos-icon.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/netbird-tvos-icon.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/netbird-tvos-icon.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/icon-netbird-button.imageset/icon-netbird-button.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/icon-netbird-button.imageset/[email protected]is excluded by!**/*.pngNetBird TV/Assets.xcassets/icon-netbird-button.imageset/[email protected]is excluded by!**/*.pngNetBird TV/Assets.xcassets/netbird-logo-menu.imageset/netbird-logo-menu 1.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/netbird-logo-menu.imageset/netbird-logo-menu.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/netbird-logo-menu.imageset/netbird-logo-menu@2x 1.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/netbird-logo-menu.imageset/[email protected]is excluded by!**/*.pngNetBird TV/Assets.xcassets/netbird-logo-menu.imageset/netbird-logo-menu@3x 1.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/netbird-logo-menu.imageset/[email protected]is excluded by!**/*.png
📒 Files selected for processing (60)
NetBird TV/Assets.xcassets/AccentColor.colorset/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json(1 hunks)NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json(1 hunks)NetBird TV/Assets.xcassets/Contents.json(1 hunks)NetBird TV/Assets.xcassets/icon-netbird-button.imageset/Contents.json(1 hunks)NetBird TV/Assets.xcassets/netbird-logo-menu.imageset/Contents.json(1 hunks)NetBird TV/ContentView.swift(1 hunks)NetBird TV/NetBird TVDebug.entitlements(1 hunks)NetBird.xcodeproj/project.pbxproj(25 hunks)NetBird.xcodeproj/xcshareddata/xcschemes/NetBird.xcscheme(1 hunks)NetBird.xcodeproj/xcshareddata/xcschemes/NetbirdNetworkExtension.xcscheme(1 hunks)NetBird/Source/App/NetBirdApp.swift(1 hunks)NetBird/Source/App/Platform/Platform.swift(1 hunks)NetBird/Source/App/ViewModels/MainViewModel.swift(9 hunks)NetBird/Source/App/ViewModels/PeerViewModel.swift(1 hunks)NetBird/Source/App/ViewModels/RoutesViewModel.swift(1 hunks)NetBird/Source/App/ViewModels/ServerViewModel.swift(3 hunks)NetBird/Source/App/Views/Components/SafariView.swift(2 hunks)NetBird/Source/App/Views/Components/SideDrawer.swift(2 hunks)NetBird/Source/App/Views/MainView.swift(4 hunks)NetBird/Source/App/Views/PeerTabView.swift(5 hunks)NetBird/Source/App/Views/RouteTabView.swift(3 hunks)NetBird/Source/App/Views/ServerView.swift(1 hunks)NetBird/Source/App/Views/TV/TVAuthView.swift(1 hunks)NetBird/Source/App/Views/TV/TVColors.swift(1 hunks)NetBird/Source/App/Views/TV/TVMainView.swift(1 hunks)NetBird/Source/App/Views/TV/TVNetworksView.swift(1 hunks)NetBird/Source/App/Views/TV/TVPeersView.swift(1 hunks)NetBird/Source/App/Views/TV/TVServerView.swift(1 hunks)NetBird/Source/App/Views/TV/TVSettingsView.swift(1 hunks)NetBirdTV/Info.plist(1 hunks)NetBirdTV/NetBirdTV.entitlements(1 hunks)NetBirdTV/NetBirdTVNetworkExtension.entitlements(1 hunks)NetBirdTVNetworkExtension/Info.plist(1 hunks)NetBirdTVNetworkExtension/NetBirdTVNetworkExtension.entitlements(1 hunks)NetBirdTVNetworkExtension/NetBirdTVNetworkExtensionDebug.entitlements(1 hunks)NetBirdTVNetworkExtension/PacketTunnelProvider.swift(1 hunks)NetbirdKit/ConnectionListener.swift(1 hunks)NetbirdKit/DNSManager.swift(1 hunks)NetbirdKit/Device.swift(1 hunks)NetbirdKit/EnvVarPackager.swift(1 hunks)NetbirdKit/NetworkExtensionAdapter.swift(6 hunks)NetbirdKit/Preferences.swift(1 hunks)NetbirdKit/RoutesSelectionDetails.swift(1 hunks)NetbirdKit/StatusDetails.swift(1 hunks)NetbirdNetworkExtension/NetBirdAdapter.swift(5 hunks)README.md(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
NetBird/Source/App/Views/RouteTabView.swift (1)
NetBird/Source/App/Platform/Platform.swift (1)
size(45-47)
NetBird TV/ContentView.swift (1)
NetBird/Source/App/Views/Components/TransparentGradientButton.swift (1)
body(22-53)
NetBird/Source/App/Views/TV/TVServerView.swift (4)
NetbirdKit/Preferences.swift (1)
configFile(61-66)NetbirdKit/Device.swift (1)
getName(11-17)NetBird/Source/App/Platform/Platform.swift (1)
size(45-47)NetBird/Source/App/ViewModels/ServerViewModel.swift (3)
clearErrorsFor(298-310)loginWithSetupKey(229-296)changeManagementServerAddress(144-206)
NetBird/Source/App/Views/TV/TVNetworksView.swift (4)
NetbirdKit/NetworkExtensionAdapter.swift (1)
getRoutes(478-509)NetBird/Source/App/ViewModels/RoutesViewModel.swift (3)
getRoutes(55-60)deselectRoute(76-82)selectRoute(62-68)NetBird/Source/App/Platform/Platform.swift (1)
size(45-47)NetBirdTVNetworkExtension/PacketTunnelProvider.swift (2)
deselectRoute(586-593)selectRoute(577-584)
NetBird/Source/App/ViewModels/ServerViewModel.swift (3)
NetBirdTVNetworkExtension/PacketTunnelProvider.swift (3)
onSuccess(20-22)onError(24-26)login(209-214)NetbirdKit/NetworkExtensionAdapter.swift (3)
onSuccess(20-22)onError(24-26)login(337-380)NetbirdNetworkExtension/NetBirdAdapter.swift (5)
onSuccess(40-42)onSuccess(49-51)onError(36-38)onError(53-55)login(310-313)
NetbirdNetworkExtension/NetBirdAdapter.swift (3)
NetbirdKit/Device.swift (3)
getName(11-17)getOsVersion(19-21)getOsName(23-25)NetbirdKit/Preferences.swift (3)
configFile(61-66)stateFile(68-73)saveConfigToUserDefaults(86-93)NetbirdKit/EnvVarPackager.swift (1)
getEnvironmentVariables(12-29)
NetBird/Source/App/Views/PeerTabView.swift (1)
NetBird/Source/App/Platform/Platform.swift (1)
size(45-47)
NetbirdKit/NetworkExtensionAdapter.swift (2)
NetBirdTVNetworkExtension/PacketTunnelProvider.swift (4)
onSuccess(20-22)onError(24-26)login(209-214)checkLoginComplete(356-390)NetbirdKit/Preferences.swift (4)
loadConfigFromUserDefaults(96-101)configFile(61-66)stateFile(68-73)hasConfigInUserDefaults(104-109)
NetBird/Source/App/Views/TV/TVSettingsView.swift (1)
NetBird/Source/App/ViewModels/MainViewModel.swift (3)
setRosenpassEnabled(368-379)close(168-178)clearDetails(292-305)
🪛 LanguageTool
README.md
[style] ~92-~92: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...sical device running tvOS 17.0 or later needs to be [paired with Xcode](https://support....
(REP_NEED_TO_VB)
🪛 markdownlint-cli2 (0.18.1)
README.md
101-101: Bare URL used
(MD034, no-bare-urls)
🪛 SwiftLint (0.57.0)
NetBird/Source/App/Views/MainView.swift
[Error] 30-30: Type name 'iOSMainView' should start with an uppercase character
(type_name)
NetbirdKit/NetworkExtensionAdapter.swift
[Warning] 333-333: Returning Void in a function declaration is redundant
(redundant_void_return)
🔇 Additional comments (82)
README.md (1)
31-33: Documentation update comprehensively covers tvOS support.The README expansions effectively communicate the new tvOS capability with clear build instructions, platform requirements, and device setup guidance. The separation of build commands for iOS-only vs. iOS+tvOS scenarios is particularly helpful for developers.
Also applies to: 57-59, 70-82, 86-92
NetBird/Source/App/ViewModels/RoutesViewModel.swift (1)
8-8: Explicit Foundation import is appropriate.Adding the explicit
import Foundationstatement aligns with best practices for cross-platform integration and ensures clarity of dependencies. This is a suitable change for the tvOS support refactoring.NetBird/Source/App/Views/Components/SideDrawer.swift (2)
7-13: LGTM! Clear platform separation.The comments effectively document the iOS-only nature of the side drawer pattern and reference the tvOS alternative (TVSettingsView). The
#if os(iOS)guard properly isolates this iOS-specific navigation pattern.
172-172: LGTM! Properly closes the platform guard.The
#endifcorrectly closes the iOS compilation block, and the inline comment helps readability. The entire SideDrawer and SideMenu implementation (including UIKit-dependent code likeUIDevice.current.userInterfaceIdiomandUIScreen.main.bounds) is appropriately isolated to iOS.NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json (1)
1-11: Clarify intent for the incomplete tvOS imagestack structure.The imagestack (Back, Middle layers) contains valid but minimal Contents.json files with no image filenames and no actual image asset files. Confirm whether this is:
- An intentional placeholder for future image additions
- An incomplete structure that should include image files now
(Note: Both Back and Middle layers have identical JSON structure with no filename references, and no image files exist anywhere in this imagestack.)
NetBird/Source/App/Views/Components/SafariView.swift (1)
1-11: LGTM! Proper platform gating for iOS-only SafariServices.The conditional compilation block correctly restricts SafariServices usage to iOS builds, which is necessary since this framework is unavailable on tvOS. The header comment clearly documents the iOS-only nature of this component.
NetBird/Source/App/NetBirdApp.swift (2)
14-17: LGTM: Clean conditional compilation for iOS-only Firebase Performance.The conditional import is appropriate since Firebase Performance is not available on tvOS.
59-85: Reconsider unifying lifecycle approaches—behavioral equivalence is not confirmed.iOS uses
NotificationCenterwithUIApplicationlifecycle notifications (lines 60, 65), while tvOS usesscenePhaseobservation (lines 72-84). The main difference is that onReceive hooks into UIKit's notification system, while scenePhase is SwiftUI's built-in, declarative alternative. However, these approaches may not be behaviorally equivalent. ScenePhase has a number of bugs (or at least, unexpected behavior) and is limited to only 3 states: active, inactive, and background. Verify timing alignment and edge cases between the two approaches before unifying them, as the UIApplication notifications and scenePhase changes may not fire at identical moments.NetBird TV/Assets.xcassets/Contents.json (1)
1-6: LGTM! Standard asset catalog metadata.This is a standard Xcode-generated asset catalog root configuration file with correct format and metadata.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json (1)
1-14: LGTM! Standard App Store app icon imagestack configuration.The layer structure and metadata follow Apple's tvOS asset catalog conventions correctly.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json (1)
1-16: LGTM! Correct tvOS Top Shelf Wide image configuration.The asset correctly uses the "tv" idiom and provides appropriate scale variants (1x, 2x) for tvOS Top Shelf imagery.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json (1)
1-6: LGTM! Standard imagestack layer metadata.This is a correctly formatted layer container metadata file following Xcode asset catalog conventions.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json (1)
1-16: LGTM! Correct tvOS Top Shelf image configuration.The asset correctly uses the "tv" idiom and provides appropriate scale variants for tvOS Top Shelf imagery.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json (1)
1-11: LGTM! Standard app icon layer image content.The configuration correctly uses the "tv" idiom for the App Store app icon middle layer content.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json (1)
1-6: LGTM! Standard imagestack layer metadata.This is a correctly formatted layer container metadata file following Xcode asset catalog conventions.
NetBird TV/Assets.xcassets/icon-netbird-button.imageset/Contents.json (1)
1-23: LGTM! Standard cross-platform button icon configuration.The asset correctly uses the "universal" idiom and provides all standard scale variants (1x, 2x, 3x) with proper naming conventions. This allows the icon to be shared across iOS and tvOS targets.
NetBird TV/Assets.xcassets/AccentColor.colorset/Contents.json (1)
1-11: Standard asset catalog structure.This is a correctly formatted Xcode asset catalog definition for AccentColor. Since no specific color values are defined, the app will use the system default accent color, which is a valid design choice.
NetBird TV/Assets.xcassets/netbird-logo-menu.imageset/Contents.json (1)
1-56: Well-structured asset catalog with complete variants.This asset definition properly includes all standard scales (1x, 2x, 3x) with both light and dark appearance variants, ensuring the logo displays correctly across all device resolutions and appearance modes.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json (1)
1-6: Standard metadata structure for empty layer.This minimal structure is appropriate for the back layer of the App Store icon imagestack, which typically doesn't require visible content.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json (1)
1-17: Correctly structured tvOS image stack.The three-layer structure (Front, Middle, Back) follows Apple's tvOS parallax icon conventions and properly coordinates the individual layer assets.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json (1)
1-6: Standard layer metadata structure.This metadata file correctly provides Xcode authoring information for the Middle image stack layer, coordinating with the nested Content.imageset.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json (1)
1-17: Asset catalog structure looks correct.The JSON structure is valid for a tvOS Content.imageset. The 1x scale references
netbird-tvos-icon.png, and the 2x scale entry (without a filename) is acceptable when a @2x asset is not provided.Note: The AI summary incorrectly describes this file as being for the "Front.imagestacklayer", but the actual file path indicates it's for the "Back.imagestacklayer".
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json (1)
1-32: LGTM! tvOS asset catalog structure is correct.The brandassets Contents.json properly defines all required tvOS assets with correct idioms, roles, and dimensions:
- Primary app icons at both App Store (1280x768) and standard (400x240) sizes
- Top Shelf images in both wide (2320x720) and standard (1920x720) formats
This follows Apple's tvOS asset catalog requirements.
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json (1)
1-6: File structure is correct—imagestacklayer Contents.json is metadata-only.The layer-level Contents.json file contains only metadata, which is the correct structure. Image definitions belong in the nested
Content.imageset/Contents.jsonfile (which already has theimagesarray with appropriate entries). All comparable imagestacklayer Contents.json files in this project follow the same metadata-only pattern.Likely an incorrect or invalid review comment.
NetbirdKit/ConnectionListener.swift (1)
9-9: LGTM!The
NetBirdSDKimport is required sinceConnectionListenerconforms toNetBirdSDKConnectionListenerProtocol.NetBird.xcodeproj/xcshareddata/xcschemes/NetBird.xcscheme (1)
3-3: Verify the Xcode version in scheme metadata.The
LastUpgradeVersionof2610appears unusually high. Xcode version numbers typically follow the format where 1430 = Xcode 14.3 and 1610 = Xcode 16.1. Version2610would imply Xcode 26.1, which doesn't exist.If this was generated by Xcode automatically, it's harmless. Otherwise, confirm this wasn't a typo (perhaps intended
1610for Xcode 16.1?).NetbirdKit/DNSManager.swift (1)
9-9: LGTM!The
NetBirdSDKimport is required sinceDNSManagerconforms toNetBirdSDKDnsManagerProtocol.NetBird/Source/App/ViewModels/PeerViewModel.swift (1)
8-9: LGTM!The explicit
Foundationimport improves clarity, even thoughCombinere-exports it. The view model logic is clean and well-structured.NetbirdKit/EnvVarPackager.swift (1)
17-25: LGTM! Platform-specific relay defaults are sensible.Defaulting to forced relay on iOS while allowing direct connections on tvOS aligns with the different network environments of these platforms.
NetBirdTV/NetBirdTVNetworkExtension.entitlements (1)
1-19: LGTM! Entitlements are correctly configured.The
packet-tunnel-providercapability andgroup.io.netbird.app.tvapp group are appropriate for the tvOS network extension.NetbirdKit/RoutesSelectionDetails.swift (1)
1-8: LGTM!Standard file header and imports for Combine-backed observable models.
NetbirdKit/StatusDetails.swift (1)
9-9: LGTM!The
Combineimport is required sincePeerInfoconforms toObservableObject.NetbirdNetworkExtension/NetBirdAdapter.swift (2)
133-214: Thorough low-level implementation for tvOS tunnel discovery.The raw memory operations correctly replicate the kernel structure layout for
ctl_infoandsockaddr_ctl. The hardcoded sizes (100 bytes for ctl_info, 32 bytes for sockaddr_ctl) and offsets match the Darwin kernel ABI which is stable.Good use of logging via
adapterLoggerfor debugging tunnel discovery issues on tvOS.
321-451: Well-structured async login flow with proper completion tracking.The
NSLock-based completion guard correctly prevents duplicate callbacks, and the capturedauthRefensures config can be saved after successful authentication. The separation ofhandleSuccess/handleErrorclosures keeps the logic clean.NetbirdKit/NetworkExtensionAdapter.swift (3)
356-369: tvOS response parsing handles missing userCode gracefully.The code correctly falls back to using the whole string if the expected
|separator is not found. Good defensive handling.
385-428: Well-structured diagnostic response parsing.The
checkLoginCompletemethod handles multiple response formats gracefully with progressive fallbacks, which is good for backward compatibility.
596-665: tvOS config IPC methods are well-implemented.
sendConfigToExtensionandclearExtensionConfigproperly handle session availability, encoding errors, and provide completion callbacks. Good error logging throughout.NetBird.xcodeproj/project.pbxproj (1)
1341-1341: Different development team for Release configuration.The iOS Release configuration uses
DEVELOPMENT_TEAM = 94333M4JTAwhile Debug usesTA739QLA7A. Ensure this is intentional (e.g., different teams for development vs distribution).NetBird/Source/App/ViewModels/MainViewModel.swift (4)
104-115: Good optimization with lazy preferences loading.Deferring SDK initialization until first access avoids blocking app startup, especially important on tvOS where key generation is expensive. Well-documented with comments.
149-166: Connect flow is well-structured.The button lock with 3-second timeout, logging, and async Task handling is appropriate for VPN connection management.
240-258: tvOS-specific state text updates are well-implemented.The switch statement correctly maps VPN states to user-friendly text and triggers route fetching when connected. Good use of platform-specific compilation.
297-305: Good cleanup for tvOS config on logout.Clearing both UserDefaults config and extension-local config ensures no stale credentials remain after logout.
NetBird/Source/App/ViewModels/ServerViewModel.swift (3)
164-205: Good use of checked continuation for async bridging.The
withCheckedContinuationpattern correctly bridges the callback-based SDK API to async/await. The tvOS-specific handling of file permission errors by falling back to UserDefaults is a pragmatic workaround.
208-227: tvOS config persistence fallback is well-implemented.The
saveConfigToUserDefaultshelper correctly extracts config JSON from the authenticator and persists it when file writes are blocked. Error logging is appropriate.
259-295: Login with setup key handles tvOS constraints appropriately.Same pattern as
changeManagementServerAddresswith proper continuation bridging and tvOS file permission error handling.NetBird/Source/App/Views/ServerView.swift (1)
272-275: No functional changes.Only trailing whitespace adjustment. LGTM.
NetBird TV/NetBird TVDebug.entitlements (1)
1-14: LGTM! Standard tvOS network extension entitlements.The entitlements correctly declare the packet-tunnel-provider capability and app group for IPC between the tvOS app and its network extension. The configuration follows Apple's standard pattern for VPN-enabled apps.
NetBirdTV/NetBirdTV.entitlements (2)
1-18: LGTM! Entitlements properly configured for tvOS VPN app.The entitlements correctly declare packet-tunnel-provider and app group capabilities, matching the debug entitlements and following Apple's standard pattern.
5-5: Confirm tvOS 17.0 is the correct minimum version requirement.The code consistently specifies tvOS 17.0 as the minimum: NetBirdTV/Info.plist sets MinimumOSVersion to "17.0" with the comment "17.0 required for VPN support" (line 13), and NetBirdTV/NetBirdTV.entitlements confirms this with "requires tvOS 17+" (line 5). If the PR description claims "tvOS 16.0+ deployment", it should be corrected to reflect the actual tvOS 17.0+ requirement.
NetBirdTVNetworkExtension/NetBirdTVNetworkExtensionDebug.entitlements (1)
1-14: LGTM! Network extension debug entitlements properly configured.The entitlements correctly match the main app's app group identifier (group.io.netbird.app.tv), enabling proper IPC between the tvOS app and its network extension.
NetBirdTVNetworkExtension/Info.plist (1)
1-13: LGTM! Standard packet tunnel extension configuration.The Info.plist correctly defines the extension point identifier and principal class using the standard pattern. The $(PRODUCT_MODULE_NAME) variable will be resolved at build time to reference the PacketTunnelProvider implementation.
NetBirdTV/Info.plist (2)
13-15: MinimumOSVersion set to 17.0.This aligns with the VPN/Network Extension requirement noted in the entitlements, but conflicts with the PR summary stating "tvOS 16.0+ deployment". This version requirement was already flagged in the entitlements review.
1-27: LGTM! tvOS app Info.plist properly configured.The configuration is appropriate for a tvOS utilities app with automatic UI style support. The bundle display name, category, and launch screen setup follow Apple's standard patterns.
NetBirdTVNetworkExtension/NetBirdTVNetworkExtension.entitlements (1)
1-14: LGTM! Network extension entitlements consistent across targets.The app group identifier (group.io.netbird.app.tv) is consistent across all tvOS targets (app and extension, debug and release), ensuring proper IPC functionality.
NetBird/Source/App/Views/RouteTabView.swift (2)
86-92: LGTM! Platform-agnostic layout implementation.The refactoring from UIScreen.main.bounds to Screen helper and Layout.fontScale properly abstracts platform-specific sizing, enabling code sharing between iOS and tvOS while maintaining appropriate layout for each platform.
107-110: LGTM! Consistent use of platform abstraction.The padding calculations consistently use Screen helper for cross-platform compatibility.
NetBird/Source/App/Views/MainView.swift (3)
13-27: LGTM! Clean platform-specific routing implementation.The MainView correctly acts as a platform router, delegating to TVMainView for tvOS and iOSMainView for iOS. The conditional compilation ensures each platform only compiles its relevant code, maintaining clean separation.
75-75: LGTM! Platform-agnostic sizing with device type detection.The refactoring correctly uses Screen.height and DeviceType.isPad for adaptive layout that works across iOS device types (iPhone/iPad) while being compatible with the broader platform abstraction supporting tvOS.
Also applies to: 85-85
29-566: LGTM! iOS-specific code properly isolated.The #if os(iOS) conditional compilation correctly isolates iOS-specific UI code, ensuring it doesn't interfere with tvOS builds while maintaining the full iOS implementation.
NetBird/Source/App/Views/PeerTabView.swift (2)
7-15: LGTM!The conditional UIKit import and documentation comments are well-structured. This correctly limits UIKit usage to iOS builds where
UIPasteboardis actually used.
81-89: Good adoption of cross-platform sizing abstraction.The migration from
UIScreen-based sizing toScreen.*metrics aligns well with thePlatform.swiftabstraction for consistent behavior across iOS and tvOS.NetBird/Source/App/Views/TV/TVAuthView.swift (3)
1-19: Well-documented device code flow implementation.Good documentation explaining the authentication pattern. The QR code approach is appropriate for tvOS where Safari isn't available.
214-231: QR code generation looks correct.The use of
CIFilter.qrCodeGenerator()with appropriate scaling for tvOS visibility is well-implemented.
192-198: Good timer lifecycle management.Timer is correctly started on
onAppearand invalidated ononDisappear, preventing memory leaks.NetBird/Source/App/Views/TV/TVMainView.swift (4)
24-55: Clean TabView navigation structure.The tab-based navigation with proper tags and environment object passing is well-implemented for the tvOS 10-foot experience.
62-99: Auth flow integration with TVAuthView is complete.All necessary callbacks (
onCancel,onComplete,onError,checkLoginComplete,checkLoginError) are properly wired to the network extension adapter.
267-282: Connection button logic handles transitional states.The
handleTapfunction correctly handles both "Connected" and "Connecting" states for disconnection, allowing users to cancel an in-progress connection attempt. The logger usage provides good observability.
212-240: Good tvOS focus state handling.The
@FocusStateusage with scale animation provides appropriate visual feedback for Siri Remote navigation. The button is correctly disabled whenbuttonLockis true.NetBird/Source/App/Views/TV/TVServerView.swift (2)
25-28: ServerViewModel initialization looks correct.The view model is properly initialized with the config file path and device name, matching the existing iOS implementation patterns.
305-329: Server change logic is well-structured.The function correctly handles the three cases: empty inputs, URL-only, and URL with setup key. The
Task.yield()allows UI updates before async operations.NetBird/Source/App/Views/TV/TVNetworksView.swift (2)
200-206: Toggle route implementation is clean.The delegation to
routeViewModel.selectRoute/deselectRouteis straightforward and matches the existing RoutesViewModel API.
17-36: TVNetworksView structure is well-organized.The conditional rendering based on connection state and route availability follows the established pattern from other TV views.
NetBird/Source/App/Views/TV/TVColors.swift (2)
18-57: Well-structured color abstraction with safe fallbacks.The
colorOrFallbackhelper pattern ensures graceful degradation when asset catalog colors are missing. This is a clean approach for tvOS theming.
63-165: Layout constants are comprehensive and well-documented.The constants cover all major UI aspects (padding, spacing, typography, focus effects) with appropriate values for TV viewing distances. Consider extracting these to a protocol or making them configurable if you anticipate supporting multiple TV form factors in the future.
NetBird/Source/App/Views/TV/TVSettingsView.swift (2)
17-116: Clean two-pane layout with proper EnvironmentObject usage.The settings view structure is well-organized with sections for Connection, Advanced, and Info. The binding for Rosenpass using a custom getter/setter (lines 57-60) correctly delegates to the ViewModel's
setRosenpassEnabledmethod.
268-359: Focus management in the alert is well-implemented.The
onChangehandler (lines 349-357) that pulls focus back when it escapes is a good pattern for tvOS modal dialogs. The_ = oldValueon line 350 to suppress the unused warning is acceptable, though you could also useonChange(of: focusedButton) { newValue in ... }if targeting older tvOS versions isn't a concern.NetBird/Source/App/Platform/Platform.swift (2)
15-48: tvOS screen dimensions are hardcoded to 1920x1080 points.The comment mentions "3840x2160 for 4K, but points are same" - this is correct for tvOS where the point-based coordinate system uses 1920x1080 regardless of actual resolution. However, note that these are static properties computed at call time, which is fine for tvOS but on iOS, screen dimensions could potentially change (e.g., during Split View on iPad).
91-135: VPN support via Network Extension was added in tvOS 17.0.tvOS 17 added support for third-party VPN apps through Network Extensions. The code correctly checks for tvOS 17.0+ before enabling VPN support. The Network Extension framework allows developers to create custom VPN solutions for tvOS. The capability flags are well-structured and the comments explaining tvOS limitations are helpful.
NetBird/Source/App/Views/TV/TVPeersView.swift (2)
114-184: TVPeerCard is well-implemented with proper focus handling.The focus-driven styling, scale animation, and selection state management are appropriate for tvOS. The status color mapping (lines 177-183) covers the expected states.
244-286: Filter bar with focus-aware styling looks good.The
TVFilterBarandTVFilterButtoncomponents handle focus states well with appropriate visual feedback (scale, color changes).NetBirdTVNetworkExtension/PacketTunnelProvider.swift (2)
17-27: ConfigInitSSOListener handles both success and error paths correctly.The listener properly propagates results via the closure callback. The optional typing for
onResultis appropriate.
395-470: Completion handler may be called multiple times inloginTV.The
completionHandleris called inonURLcallback (line 429), and could potentially be called again inonErrorifurlSentToAppcheck fails due to a race condition (though the lock helps). More critically, the NEPacketTunnelProvider'shandleAppMessagecompletion handler should only be called once. The current logic is safe due to the lock, but consider documenting this invariant clearly.Verify that the
onURLcallback is guaranteed to be called beforeonSuccess/onError, and that only one of success/error is ever called. If bothonURLandonSuccesscan trigger in sequence, the current design is correct. However, if there's any scenario whereonErrorcould be called beforeonURLand thenonURLis called afterward, you'd have a problem.
...f Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
Show resolved
Hide resolved
... Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
Show resolved
Hide resolved
…ndling - Fix mismatched bundle IDs between Debug/Release configs for tvOS targets - Make Preferences.configFile() and stateFile() return optionals instead of force-unwrapping, with proper error logging when app group unavailable - Make NetBirdAdapter.init failable to handle SDK client creation failures - Fail fast in NetBirdAdapter.start() when tunnel file descriptor is invalid instead of silently passing fd=0 (stdin) to the SDK - Ensure all handleAppMessage switch cases call completionHandler to prevent IPC callers from hanging indefinitely
(functionality migrated to ServerViewModel) - Remove orphaned managementURL property - Simplify supportsKeyboard to just return true (both branches were identical) - Updated README instructions on buiilding tvOS SDK (needs NetBird gomobile fork)
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
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.
Actionable comments posted: 7
♻️ Duplicate comments (2)
NetbirdNetworkExtension/PacketTunnelProvider.swift (1)
106-117: Completion handlers now called consistently.The Select/Deselect and default cases now invoke the completion handler, which resolves the IPC contract issue flagged in previous reviews.
Optional: Consider non-optional Data initializer.
SwiftLint suggests using
Data("true".utf8)instead of"true".data(using: .utf8)for lines 109 and 113. The non-optional initializer avoids unnecessary optionals when the encoding is guaranteed to succeed.NetBirdTVNetworkExtension/PacketTunnelProvider.swift (1)
118-166: LGTM: Comprehensive IPC message handling for tvOS.The handleAppMessage implementation covers all the necessary IPC commands for tvOS, including config initialization, device auth login, and config transfer. The completion handlers are now consistently called for all message types, which addresses the previous review concern.
Optional: Consider non-optional Data initializer for consistency.
Lines 149, 153, 257, 276, 285, 294, 314, 324, 331, 335, 388, and 424 all use
"string".data(using: .utf8)which returns an optional. SwiftLint recommendsData("string".utf8)which is non-optional and more concise. This is a minor style improvement.
🧹 Nitpick comments (4)
README.md (1)
96-96: Reduce repetitive phrasing for improved readability.The phrase "needs to be" is repeated across adjacent sentences. Consider rephrasing for variety.
Consider this revision:
-> **Note:** The app cannot run in the tvOS simulator. To test the app, a physical device running tvOS 17.0 or later must be [paired with Xcode](https://support.apple.com/en-us/101262).NetbirdNetworkExtension/NetBirdAdapter.swift (2)
60-63: Consider making the default management URL configurable.The hardcoded public NetBird server URL may limit users who want to connect to self-hosted instances. While
loginAsync()does attempt to extract a stored URL from config, a more explicit configuration mechanism would improve flexibility.
484-496: Regex-based JSON parsing is fragile.Using regex to extract
ManagementURLfrom JSON (line 488) can fail or produce incorrect results with:
- Malformed JSON
- Escaped quotes within the value
- The field appearing in nested structures or JSON embedded in strings
While this works for simple, well-formed config JSON, proper JSON deserialization would be more robust and maintainable.
Consider using
JSONDecoderorJSONSerializationto parse the config structure instead:static func extractManagementURL(from configJSON: String) -> String? { guard let data = configJSON.data(using: .utf8), let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let url = json["ManagementURL"] as? String, !url.isEmpty else { return nil } return url }NetbirdKit/NetworkExtensionAdapter.swift (1)
338-338: Optional: Remove redundant void return.SwiftLint flags the explicit
-> Voidreturn type as redundant. Functions without a return type implicitly returnVoid, so this can be simplified tofunc stop().
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
NetBird.xcodeproj/project.pbxproj(25 hunks)NetBird/Source/App/Platform/Platform.swift(1 hunks)NetBird/Source/App/ViewModels/MainViewModel.swift(9 hunks)NetBird/Source/App/Views/ServerView.swift(2 hunks)NetBird/Source/App/Views/TV/TVServerView.swift(1 hunks)NetBirdTVNetworkExtension/PacketTunnelProvider.swift(1 hunks)NetbirdKit/NetworkExtensionAdapter.swift(6 hunks)NetbirdKit/Preferences.swift(1 hunks)NetbirdNetworkExtension/NetBirdAdapter.swift(4 hunks)NetbirdNetworkExtension/PacketTunnelProvider.swift(10 hunks)README.md(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- NetBird/Source/App/Views/ServerView.swift
- NetBird/Source/App/Platform/Platform.swift
🧰 Additional context used
🧬 Code graph analysis (3)
NetbirdNetworkExtension/PacketTunnelProvider.swift (2)
NetbirdNetworkExtension/NetBirdAdapter.swift (3)
stop(478-480)start(296-323)login(331-334)NetBird/Source/App/ViewModels/RoutesViewModel.swift (1)
deselectRoute(76-82)
NetBird/Source/App/Views/TV/TVServerView.swift (2)
NetbirdKit/Device.swift (1)
getName(11-17)NetBird/Source/App/ViewModels/ServerViewModel.swift (3)
clearErrorsFor(298-310)loginWithSetupKey(229-296)changeManagementServerAddress(144-206)
NetBird/Source/App/ViewModels/MainViewModel.swift (4)
NetBirdTVNetworkExtension/PacketTunnelProvider.swift (2)
onError(24-26)onSuccess(20-22)NetbirdNetworkExtension/NetBirdAdapter.swift (5)
onError(36-38)onError(53-55)onSuccess(40-42)onSuccess(49-51)start(296-323)NetbirdKit/Preferences.swift (2)
newPreferences(49-63)removeConfigFromUserDefaults(120-126)NetBird/Source/App/ViewModels/RoutesViewModel.swift (1)
getRoutes(55-60)
🪛 LanguageTool
README.md
[style] ~96-~96: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...sical device running tvOS 17.0 or later needs to be [paired with Xcode](https://support....
(REP_NEED_TO_VB)
🪛 markdownlint-cli2 (0.18.1)
README.md
63-63: Bare URL used
(MD034, no-bare-urls)
105-105: Bare URL used
(MD034, no-bare-urls)
🪛 SwiftLint (0.57.0)
NetbirdNetworkExtension/PacketTunnelProvider.swift
[Warning] 109-109: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 113-113: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
NetbirdKit/NetworkExtensionAdapter.swift
[Warning] 338-338: Returning Void in a function declaration is redundant
(redundant_void_return)
NetBirdTVNetworkExtension/PacketTunnelProvider.swift
[Warning] 149-149: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 153-153: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 257-257: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 276-276: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 285-285: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 294-294: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 314-314: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 324-324: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 331-331: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 331-331: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 335-335: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 388-388: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
🔇 Additional comments (51)
README.md (1)
57-57: Verify tvOS version requirement consistency.Line 57 specifies tvOS 17.0+, but the PR objectives reference tvOS 16.0+ as the target. Please clarify which version is the actual requirement and ensure consistency across documentation.
NetBird.xcodeproj/project.pbxproj (1)
973-973: Bundle identifier consistency verified.The bundle identifier mismatch flagged in previous reviews has been resolved. Both Debug and Release configurations now use consistent identifiers:
- NetBird TV:
io.netbird.app.tv(lines 973, 1012)- NetBirdTVNetworkExtension:
io.netbird.app.tv.extension(lines 1049, 1086)This ensures proper code signing and provisioning profile compatibility across build configurations.
Also applies to: 1012-1012, 1049-1049, 1086-1086
NetbirdNetworkExtension/NetBirdAdapter.swift (4)
16-56: LGTM! Clean delegation wrapper classes.The login flow helper classes provide a clean bridge between the SDK's Objective-C protocols and Swift closures. The implementation is straightforward and safe.
87-131: LGTM! Platform-specific tunnel FD discovery is well-structured.The conditional compilation cleanly separates iOS and tvOS implementations, with appropriate logging for debugging. The iOS path uses standard SDK APIs while tvOS delegates to a custom implementation.
219-268: LGTM! Failable initializer properly handles platform differences.The initializer now uses
guard letinstead of force unwrapping (addressing the previous review comment), and gracefully handles initialization failures by returningnil. The platform-specific logic correctly handles tvOS's filesystem restrictions by loading config from UserDefaults instead of files.
296-323: LGTM! Proper validation of tunnel file descriptor.The
guardstatement now properly validates the file descriptor, checking for bothniland invalid values (fd <= 0), and returns a descriptive error instead of silently passing invalid data to the SDK. This addresses the previous review concern.NetbirdNetworkExtension/PacketTunnelProvider.swift (7)
23-25: LGTM: Defensive optional adapter initialization.Making the adapter optional allows graceful handling of initialization failures throughout the lifecycle methods. This pairs well with the new guard statements added across the file.
52-60: LGTM: Proper initialization check.The guard ensures the adapter is available before proceeding with the tunnel startup. The error code 1003 and message are clear and actionable.
162-169: LGTM: Safe network change handling.The optional chaining ensures the restart logic handles a nil adapter gracefully without crashing.
171-179: LGTM: Guarded login flow.The guard ensures the adapter is available before attempting login, preventing nil dereference.
181-248: LGTM: Status retrieval with local state caching.The guard protects against nil adapter, and extracting
clientStateto a local variable is a clean refactor that avoids repeated property access.
250-293: LGTM: Guarded route selection.The guard ensures adapter availability before retrieving routes.
295-311: LGTM: Route selection operations guarded.Both functions guard against nil adapter with early returns, maintaining consistency with the rest of the file.
NetBirdTVNetworkExtension/PacketTunnelProvider.swift (11)
15-27: LGTM: Clean SSO listener implementation.The ConfigInitSSOListener follows a standard callback pattern to bridge SDK results to async Swift code.
43-105: LGTM: Comprehensive tvOS startup flow with extensive logging.The startup sequence correctly handles tvOS sandbox restrictions by skipping file-based logging and checking UserDefaults for config. The extensive logging with
privacy: .publicis appropriate for debugging tvOS issues where Console.app is the primary diagnostic tool.Minor question: Why the 2-second delay before returning login error?
Line 86 introduces a 2-second delay before calling the completion handler with a "Login required" error. This seems arbitrary. If there's a specific reason (e.g., waiting for UI to be ready), consider documenting it. Otherwise, the delay could be removed or reduced.
233-259: LGTM: tvOS config transfer via IPC.This function implements the necessary workaround for tvOS where shared UserDefaults between app and extension don't work. Saving to extension-local
UserDefaults.standardand loading directly into the SDK client is the correct approach. The device name update ensures tvOS devices are properly identified.
261-278: LGTM: Simple config storage helpers for tvOS.These functions provide a clean interface for extension-local config storage using
UserDefaults.standard, which is the only reliable storage mechanism available in the tvOS extension sandbox.
280-342: LGTM: Config initialization with SDK file write.This function properly initializes the config file by delegating file writes to the SDK (which has the necessary permissions). Checking UserDefaults for an existing management URL before defaulting is a nice touch that preserves user preferences across initialization.
344-381: LGTM: Platform-specific config initialization.The tvOS path correctly delegates config loading to the adapter, while the iOS path uses a semaphore-based synchronous wait during startup. The 10-second timeout prevents hanging indefinitely.
383-426: LGTM: Detailed login status diagnostics.The diagnostic response format provides comprehensive state information for debugging the tvOS device auth flow. The comment on line 418 correctly notes that
client.isLoginComplete()doesn't work withAuth.Login(), so checkinglastLoginResultis the right approach.
428-512: LGTM: Well-designed tvOS device authentication flow.The loginTV implementation is well thought out:
- Forces device auth (appropriate for tvOS)
- Returns both URL and user code in a pipe-separated format
- Uses a lock to prevent double-completion
- Distinguishes between errors before and after the URL is sent (important for UX)
- Extensive logging aids debugging in the sandbox environment
514-581: LGTM: Status retrieval with proper error handling.The getStatus implementation follows the same pattern as the iOS version, with appropriate guards and a fallback to default status if encoding fails.
583-646: LGTM: Route selection with consistent error handling.The route selection methods follow the established pattern with proper guards and logging. The encoding fallback in getSelectRoutes ensures a response is always returned.
666-710: LGTM: Logging initialization for tvOS extension.The initializeLogging function sets up file-based logging in the App Group container, with appropriate error handling for file operations.
NetBird/Source/App/Views/TV/TVServerView.swift (5)
21-48: LGTM: Well-organized tvOS view structure.The view setup is clean with appropriate use of
@EnvironmentObject,@StateObject, and@FocusStatefor tvOS focus management. The initialization ofServerViewModelwith device name and config path is correct.
49-183: LGTM: Comprehensive form UI with proper focus management.The two-column layout with form fields on the left is well-suited for tvOS. Error messages are displayed per-field, and the optional setup key section is properly gated with clear UI feedback.
184-301: LGTM: Action buttons with proper state handling.The button layout provides clear actions (Cancel, Use NetBird, Change) with disabled states and loading indicators. The lifecycle hooks properly react to SSO errors and successful operations. The two-parameter
onChangesyntax on lines 289 and 294 is correct for the tvOS 17+ target.
305-353: LGTM: Clean async action methods.The action methods properly trim inputs, default to the NetBird URL when needed, clear errors, and call the appropriate async methods on
ServerViewModel. The use ofTaskwithawait Task.yield()is correct for SwiftUI async operations.
355-380: LGTM: Clean helper view and preview.The
InfoRowhelper provides consistent formatting for the info panel, and the preview provider aids development.NetbirdKit/Preferences.swift (6)
11-41: Excellent: Comprehensive architecture documentation.The extensive inline documentation explaining the tvOS config storage architecture is outstanding. It clearly documents the sandbox restrictions, the IPC-based workaround, and contrasts it with iOS behavior. This will be invaluable for future maintainers.
43-63: LGTM: Safe preferences creation with proper nil handling.The optional return type and guard statements address the previous review concern about force unwraps. The function now safely handles cases where the app group container is unavailable, which is important for tvOS.
65-81: LGTM: Safe file path retrieval.The guard statements and optional returns properly handle cases where the app group container is unavailable, addressing the force unwrap concerns from the previous review. The error messages provide clear diagnostic information.
83-101: LGTM: UserDefaults config storage for tvOS.The function provides a clean API for saving config to UserDefaults, which is the tvOS workaround for file write restrictions. The boolean return value allows callers to handle failures appropriately.
103-126: LGTM: Consistent UserDefaults helpers.The load, check, and remove methods follow a consistent pattern with proper nil handling for unavailable UserDefaults.
128-145: LGTM: Config restoration from UserDefaults to file.This function provides the bridge from UserDefaults storage to the file system when needed. The error handling with boolean return is appropriate. Note that this will only work in contexts where file writes are permitted (e.g., the extension).
NetbirdKit/NetworkExtensionAdapter.swift (8)
15-27: LGTM: SSO listener for config initialization.The ConfigSSOListener follows the standard callback pattern for bridging SDK results to Swift code.
29-66: LGTM: Platform-specific extension configuration.The conditional compilation properly sets different extension IDs for tvOS vs iOS. The addition of the
userCodeproperty supports the tvOS device authentication flow.
72-142: LGTM: VPN start sequence with tvOS config initialization.The start sequence properly logs each step for debugging. The tvOS-specific config initialization on lines 120-126 ensures config is available before checking if login is required.
144-219: LGTM: tvOS config initialization with IPC fallback.The
initializeConfigFromAppfunction implements the necessary workaround for tvOS by checking UserDefaults and sending config via IPC. TheinitializeConfigViaExtensionhelper allows requesting default config creation from the extension when it has write permissions the app lacks.
221-299: LGTM: Platform-specific login requirement checks.The tvOS implementation properly checks UserDefaults and then verifies with the management server, which is important for detecting expired tokens. The iOS implementation uses file-based config with SDK verification. The extensive logging aids debugging.
342-385: LGTM: Login flow with tvOS device authentication.The login function properly handles the tvOS device auth flow by sending "LoginTV" and parsing the "url|userCode" response format. Storing the userCode on the main thread is correct for UI binding.
387-481: LGTM: Robust login status checking with user-friendly errors.The
checkLoginCompletefunction robustly parses diagnostic responses in multiple formats for backward compatibility. ThecheckLoginErrorfunction transforms technical error messages into user-friendly guidance, which significantly improves the UX.
601-670: LGTM: IPC-based config transfer for tvOS.The
sendConfigToExtensionandclearExtensionConfigfunctions provide the necessary IPC mechanism for config management on tvOS. The error handling and completion callbacks are appropriate.NetBird/Source/App/ViewModels/MainViewModel.swift (8)
17-42: LGTM: SDK callback listeners.The
SSOCheckListenerandSetupKeyErrListenerclasses provide clean bridges from NetBirdSDK callbacks to Swift closures.
103-114: Excellent: Lazy preferences loading improves startup performance.The lazy loading of preferences avoids expensive SDK initialization (WireGuard/SSH key generation) at app startup. This is particularly important on tvOS where the SDK initialization is costly. The pattern is clean and well-commented.
123-137: LGTM: Deferred Rosenpass settings loading.Deferring the Rosenpass settings load is consistent with the lazy preferences strategy. Since these settings trigger expensive SDK initialization, loading them only when the settings view is accessed is the right trade-off.
148-165: LGTM: Enhanced connect with logging and button lock.The connect method now includes logging for debugging and a 3-second button lock to prevent rapid repeated taps. The async Task block properly awaits the network extension start.
230-261: LGTM: tvOS-specific state text mapping.The tvOS branch properly maps NEVPNStatus to human-readable text since tvOS doesn't use the iOS CustomLottieView. Fetching routes when connected (line 245) ensures the Networks counter on the home screen is accurate.
263-276: LGTM: Comprehensive logout cleanup for tvOS.The clearDetails method properly cleans up UserDefaults config and extension-local config on tvOS, preventing stale credentials from persisting after logout.
278-374: Excellent: Defensive preference methods.All preference-related methods now guard against nil preferences and return safe defaults where appropriate. This prevents crashes when the SDK fails to initialize (which can happen on tvOS) and aligns with the lazy loading strategy.
376-393: LGTM: Platform-appropriate relay connection defaults.The forced relay connection defaults to true on iOS (for battery optimization) and false on tvOS (where battery optimization is not needed). The comment clearly explains the rationale.
README.md
Outdated
|
|
||
| ## gomobile-fork | ||
|
|
||
| Since gomobile doesn't natively support tvOS targets, NetBird has created a fork that does. Please see the repo for more information: https://github.com/netbirdio/gomobile-tvos-fork |
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.
Wrap bare URLs in markdown link format.
Per markdownlint (MD034), bare URLs should be wrapped in markdown link syntax. Both lines contain unformatted URLs that should be formatted as [URL text](URL).
Apply this diff to fix the bare URL formatting:
-Since gomobile doesn't natively support tvOS targets, NetBird has created a fork that does. Please see the repo for more information: https://github.com/netbirdio/gomobile-tvos-fork
+Since gomobile doesn't natively support tvOS targets, NetBird has created a fork that does. Please see the repo for more information: [https://github.com/netbirdio/gomobile-tvos-fork](https://github.com/netbirdio/gomobile-tvos-fork)Also fix line 105:
-iOS/tvOS Client: https://github.com/netbirdio/ios-client (this repository)
+iOS/tvOS Client: [https://github.com/netbirdio/ios-client](https://github.com/netbirdio/ios-client) (this repository)Also applies to: 105-105
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
63-63: Bare URL used
(MD034, no-bare-urls)
🤖 Prompt for AI Agents
In README.md around lines 63 and 105, both lines contain bare URLs and violate
markdownlint MD034; replace the raw URLs with markdown link syntax by wrapping
each URL in [text](URL) form — for example use descriptive link text like
"gomobile tvOS fork" or the URL itself as text in square brackets — and update
both lines so they no longer contain bare URLs.
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.
Actionable comments posted: 0
♻️ Duplicate comments (2)
NetBird.xcodeproj/project.pbxproj (2)
575-580: Critical: Missing Firebase Crashlytics dSYM upload script for NetBird TV target.The NetBird TV target's buildPhases array lacks a ShellScript phase to upload dSYM files to Firebase Crashlytics. Without this script, crash reports from tvOS will not be properly symbolicated, severely limiting your ability to debug production issues.
The iOS NetBird target includes this script at line 654 (detailed at lines 778-799). Add a similar ShellScript phase to the NetBird TV target after the Resources phase.
727-733: Critical: Empty Resources phase for NetBird TV target.The NetBird TV target's Resources build phase is empty but the target declares dependencies on Firebase Crashlytics, Firebase Analytics, and Lottie (lines 592-594).
Required resources:
GoogleService-Info.plist- Required for Firebase services to initialize- Lottie animation JSON files - The iOS target includes 9 animation files (button-disconnecting.json, logo_NetBird.json, button-connected.json, button-start-connecting.json, button-full.json, button-connecting-loop.json, button-full2.json, button-full2-dark.json, loading.json)
While the FileSystemSynchronizedRootGroup (line 312) may auto-sync files from the "NetBird TV" directory, verify that these resources are present in that directory or explicitly add them to this Resources phase.
🧹 Nitpick comments (2)
.github/workflows/build.yml (1)
79-105: Consider adding tvOS build steps.The workflow currently builds only the iOS app and network extension. Since this PR adds tvOS support with new targets (NetBird TV app and NetBirdTVNetworkExtension), consider adding build steps for these tvOS targets to ensure they compile successfully in CI.
Example tvOS build steps
- name: Build tvOS App working-directory: ios-client run: | set -o pipefail xcodebuild build \ -project NetBird.xcodeproj \ -scheme "NetBird TV" \ -destination 'generic/platform=tvOS' \ -configuration Debug \ CODE_SIGNING_ALLOWED=NO \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGN_IDENTITY="" \ | xcpretty --color - name: Build tvOS Network Extension working-directory: ios-client run: | set -o pipefail xcodebuild build \ -project NetBird.xcodeproj \ -scheme NetBirdTVNetworkExtension \ -destination 'generic/platform=tvOS' \ -configuration Debug \ CODE_SIGNING_ALLOWED=NO \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGN_IDENTITY="" \ | xcpretty --color.github/workflows/test.yml (1)
64-64: Consider adding tvOS simulator tests.The tests currently run only on an iOS simulator (iPhone 16). Since this PR introduces tvOS support, consider adding a test step for tvOS targets using an Apple TV simulator to ensure tvOS-specific code is tested.
Example tvOS test step
- name: Run tvOS Tests working-directory: ios-client run: | set -o pipefail xcodebuild test \ -project NetBird.xcodeproj \ -scheme "NetBird TV" \ -destination 'platform=tvOS Simulator,name=Apple TV' \ -configuration Debug \ CODE_SIGNING_ALLOWED=NO \ | xcpretty --color --test
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.github/workflows/build.yml.github/workflows/test.ymlNetBird.xcodeproj/project.pbxproj
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and Test
- GitHub Check: Build iOS App
🔇 Additional comments (4)
.github/workflows/test.yml (1)
16-21: LGTM! Consistent upgrade with build workflow.The test workflow has been updated consistently with the build workflow. The macos-15 runner and Xcode 16.1 selection are necessary for the Xcode 16 project format.
Note: The same verification requests from the build.yml review apply here regarding runner availability and Xcode path.
.github/workflows/build.yml (2)
36-36: Go version 1.24 has been released and is available as of December 2025 (with 1.25.5 being the latest stable version). The original concern about version availability is unfounded. If using an older Go version is intentional for compatibility reasons, no action is needed; otherwise, consider updating to a more recent version.Likely an incorrect or invalid review comment.
16-21: The macos-15 runner and Xcode 16.1 are available on GitHub Actions. The upgrade is correct and no action is needed.NetBird.xcodeproj/project.pbxproj (1)
734-740: This concern is incorrect. The animation JSON files (button-*.json, loading.json, etc.) exist in the main app's./NetBird/Source/animationsdirectory, not in either network extension. The GoogleService-Info.plist referenced in the iOS extension's pbxproj does not actually exist in the repository. The tvOS extension usesFileSystemSynchronizedRootGroup, which automatically includes all files from the directory except those listed in the exception set (currently onlyInfo.plist). Since no resource files exist in theNetBirdTVNetworkExtensiondirectory, the empty Resources phase is correct. Firebase services in the tvOS extension are linked via frameworks, not resources.Likely an incorrect or invalid review comment.
- Add complete tvOS icon assets (App Icon, App Store Icon, Top Shelf) - Add arm64 to UIRequiredDeviceCapabilities for TV Network Extension - Add TVTopShelfImage configuration to tvOS Info.plist - Fix iOS build cycle by reordering Embed Frameworks before Crashlytics script
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.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In @NetBird.xcodeproj/project.pbxproj:
- Line 229: The PBXFileReference 44DCF5B82EDF4D900026078E for libresolv.tbd
contains a hardcoded SDK path
"Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS26.1.sdk/usr/lib/libresolv.tbd"
and sourceTree = DEVELOPER_DIR; update this entry so the path is the
SDK-relative form "usr/lib/libresolv.tbd" and set sourceTree = SDKROOT to allow
Xcode to resolve the tvOS SDK dynamically (matching the other libresolv.tbd
entry).
In @NetbirdKit/ConfigurationProvider.swift:
- Around line 216-234: The updateJSONStringField method currently manually
escapes only backslashes and quotes which can produce invalid JSON for values
containing control characters; instead parse the existing JSON returned by
getConfigJSON() into a mutable dictionary or Codable struct (using
JSONSerialization.jsonObject or JSONDecoder), update the specific key (field)
with the new string value, then re-serialize the whole object using
JSONSerialization.data(withJSONObject:options:) or JSONEncoder to get a
correctly escaped JSON string and call saveConfigJSON(json); keep the existing
behavior of only saving when the field exists by checking the parsed object
contains the key before modifying.
- Around line 201-214: The updateJSONField method currently silently no-ops when
the JSON key is missing; change updateJSONField(field:value:) to parse the
config via getConfigJSON() into a mutable JSON object (e.g., JSONSerialization
to [String:Any] or a Codable struct), set or insert the boolean at the given
key, re-serialize and call saveConfigJSON(_:), and return a Bool or throw on
failure so callers (and commit()) can detect errors; alternatively, if you keep
the regex approach, at minimum log a warning via your logger when the regex
finds no match and return false from updateJSONField so commit() can aggregate
failures—make analogous changes for the other update methods and ensure commit()
reflects and propagates any update failures.
- Around line 159-163: The commit() implementation always returns true which
breaks the protocol contract; modify ConfigurationProvider so commit() reflects
real persistence outcome by tracking the success of JSON write operations:
introduce a private Bool (e.g., lastWriteSucceeded) that all methods which
update the config JSON set to true on successful write and false on failure,
then have commit() return that value (and reset it if appropriate);
alternatively, if you prefer the tvOS behavior is always immediate, update the
protocol documentation comment for commit() to state it always returns true on
tvOS—choose one approach and apply it consistently across the JSON-update
methods and the commit() implementation.
- Around line 179-188: The extractJSONBool function is brittle because it uses a
regex that can match nested keys or occurrences inside string values; replace
the regex approach by parsing the getConfigJSON() result into a JSON object (use
JSONSerialization.jsonObject or JSONDecoder into [String: Any]) and then safely
read the boolean for the top-level key named by the field parameter (e.g., in
extractJSONBool, decode json to [String: Any] and return jsonDict[field] as?
Bool), returning nil if parsing fails or the value is not a Bool; ensure you
reference extractJSONBool and getConfigJSON while making these changes.
- Around line 126-236: The tvOSConfigurationProvider currently manipulates JSON
via fragile regexes (see extractJSONBool, extractJSONString, updateJSONField,
updateJSONStringField, getConfigJSON, saveConfigJSON); replace this with a
Codable-backed approach: define a private Config: Codable with RosenpassEnabled,
RosenpassPermissive, PreSharedKey (and defaults), implement getConfig() to
decode Preferences.loadConfigFromUserDefaults() via JSONDecoder, implement
saveConfig(_:) to encode via JSONEncoder and call
Preferences.saveConfigToUserDefaults, and update the property getters/setters
(rosenpassEnabled, rosenpassPermissive, preSharedKey, hasPreSharedKey) to
read/modify the Config and persist it so updates never silently fail, support
inserting missing fields via defaults, and rely on JSONEncoder/Decoder for
correct escaping instead of manual regex/string manipulation.
- Around line 91-94: The preSharedKey getter currently calls
preferences.getPreSharedKey(nil) and swallows read errors; change it to follow
the rosenpass getters pattern by passing an NSError? variable (e.g., var error:
NSError?) to preferences.getPreSharedKey(&error), check if error != nil and
handle it (log the error via your logger or return a distinguishable value), and
only return the key when no error occurred; update the preSharedKey computed
property to surface or log read errors consistently with the rosenpass getters.
- Line 49: Rename the type iOSConfigurationProvider to follow Swift naming
conventions by changing the class name to IOSConfigurationProvider and update
all references accordingly; also update the factory/creation site that currently
instantiates iOSConfigurationProvider (the factory referenced in the review) to
instantiate IOSConfigurationProvider instead so all usages (initializers,
protocol conformance to ConfigurationProvider) remain consistent.
- Line 130: Rename the type tvOSConfigurationProvider to follow Swift
conventions (TVOSConfigurationProvider) and update all references and
instantiations accordingly; specifically, change the class declaration final
class tvOSConfigurationProvider: ConfigurationProvider to final class
TVOSConfigurationProvider: ConfigurationProvider and update the factory that
currently constructs tvOSConfigurationProvider to construct
TVOSConfigurationProvider (and adjust any other references, imports or tests) so
the project compiles with the new identifier.
🧹 Nitpick comments (2)
NetbirdKit/ConfigurationProvider.swift (2)
59-72: Consider propagating configuration read errors.The getter catches errors from
getRosenpassEnabled()but silently returnsfalseas a default value. This could mask genuine configuration issues (e.g., corrupted config file, permission errors) and make debugging difficult.Consider whether returning a default value is appropriate, or if the error should be surfaced to the caller through a different mechanism (e.g., making the property throw, or exposing a separate validation method).
49-122: Consider thread safety for concurrent access.Neither implementation includes synchronization mechanisms to protect against concurrent access. If the
ConfigurationProvideris accessed from multiple threads simultaneously (e.g., main thread for UI updates and background threads for network operations), this could lead to:
- iOS: Race conditions in file I/O operations through
NetBirdSDKPreferences- tvOS: Partial updates to the JSON config in UserDefaults
While SwiftUI views typically run on the main thread, verify whether background tasks or network callbacks might access the configuration concurrently.
If concurrent access is possible, consider adding a serial queue or lock to synchronize operations:
private let queue = DispatchQueue(label: "com.netbird.config", qos: .userInitiated) var rosenpassEnabled: Bool { get { queue.sync { /* existing get logic */ } } set { queue.async { /* existing set logic */ } } }Alternatively, document that the provider must only be accessed from the main thread.
Also applies to: 130-236
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (12)
NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/app-store-icon.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/app-store-icon.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/netbird-tvos-icon.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/[email protected]is excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/netbird-tvos-icon.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/[email protected]is excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/netbird-tvos-icon.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/[email protected]is excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top-shelf-wide.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/[email protected]is excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top-shelf.pngis excluded by!**/*.pngNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/[email protected]is excluded by!**/*.png
📒 Files selected for processing (12)
.gitignoreNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.jsonNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.jsonNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.jsonNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.jsonNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.jsonNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.jsonNetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.jsonNetBird.xcodeproj/project.pbxprojNetBirdTV/Info.plistNetBirdTVNetworkExtension/Info.plistNetbirdKit/ConfigurationProvider.swift
✅ Files skipped from review due to trivial changes (2)
- .gitignore
- NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
🚧 Files skipped from review as they are similar to previous changes (6)
- NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
- NetBirdTVNetworkExtension/Info.plist
- NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
- NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
- NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
- NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-24T15:54:54.967Z
Learnt from: shuuri-labs
Repo: netbirdio/ios-client PR: 36
File: NetBird/Source/App/Views/TV/TVSettingsView.swift:611-617
Timestamp: 2025-12-24T15:54:54.967Z
Learning: On iOS, the pre-shared key (PSK) is stored in the SDK's config file, not in the Keychain. On tvOS, the config (including PSK) is stored in UserDefaults and transferred to the network extension via IPC as a single JSON blob, making Keychain integration architecturally more complex.
Applied to files:
NetbirdKit/ConfigurationProvider.swift
🧬 Code graph analysis (1)
NetbirdKit/ConfigurationProvider.swift (1)
NetBird/Source/App/ViewModels/MainViewModel.swift (4)
getRosenpassEnabled(327-329)setRosenpassEnabled(309-325)getRosenpassPermissive(331-333)setRosenpassPermissive(344-353)
🪛 SwiftLint (0.57.0)
NetbirdKit/ConfigurationProvider.swift
[Error] 49-49: Type name 'iOSConfigurationProvider' should start with an uppercase character
(type_name)
[Error] 130-130: Type name 'tvOSConfigurationProvider' should start with an uppercase character
(type_name)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build iOS App
- GitHub Check: Build and Test
🔇 Additional comments (5)
NetBird.xcodeproj/project.pbxproj (3)
6-6: LGTM!The
objectVersionupgrade to 70 is appropriate for supporting the new tvOS targets and modern Xcode features.
967-1120: LGTM!The tvOS build configurations are properly structured with appropriate settings for both Debug and Release builds. The deployment target (17.0), signing configuration, and device family settings are all correctly configured for tvOS development.
1473-1487: LGTM!The Swift package dependencies for the tvOS targets are correctly configured, reusing the same Firebase and Lottie packages as the iOS app.
NetBirdTV/Info.plist (1)
13-15: No action needed. The tvOS version requirement is consistent across all configurations: both theTVOS_DEPLOYMENT_TARGETin project.pbxproj andMinimumOSVersionin Info.plist are correctly set to 17.0, which matches the README documentation stating "tvOS 17.0+".NetBird TV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json (1)
1-18: Asset catalog configuration verified and correct.The referenced image files exist with the correct dimensions for tvOS layered app icons:
- netbird-tvos-icon.png: 400×240 pixels (1x) ✓
- [email protected]: 800×480 pixels (2x) ✓
The JSON structure follows Xcode conventions correctly. No changes required.
|
|
||
| #if os(iOS) | ||
| /// iOS implementation using NetBirdSDKPreferences (file-based storage) | ||
| final class iOSConfigurationProvider: ConfigurationProvider { |
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.
Fix type name to follow Swift naming conventions.
The type name should be IOSConfigurationProvider (not iOSConfigurationProvider) to comply with Swift naming conventions.
Based on static analysis hints.
📝 Proposed fix
-final class iOSConfigurationProvider: ConfigurationProvider {
+final class IOSConfigurationProvider: ConfigurationProvider {Also update the factory on Line 244:
- return iOSConfigurationProvider()
+ return IOSConfigurationProvider()Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 SwiftLint (0.57.0)
[Error] 49-49: Type name 'iOSConfigurationProvider' should start with an uppercase character
(type_name)
🤖 Prompt for AI Agents
In @NetbirdKit/ConfigurationProvider.swift at line 49, Rename the type
iOSConfigurationProvider to follow Swift naming conventions by changing the
class name to IOSConfigurationProvider and update all references accordingly;
also update the factory/creation site that currently instantiates
iOSConfigurationProvider (the factory referenced in the review) to instantiate
IOSConfigurationProvider instead so all usages (initializers, protocol
conformance to ConfigurationProvider) remain consistent.
| var preSharedKey: String { | ||
| get { | ||
| return preferences.getPreSharedKey(nil) | ||
| } |
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.
PSK getter silently ignores errors.
Unlike the rosenpass getters, getPreSharedKey(nil) passes nil for the error parameter, completely ignoring any read errors. This inconsistency could lead to returning an empty string when an error occurred, which would be indistinguishable from "no PSK configured."
Consider applying the same error-handling pattern used in the rosenpass getters for consistency.
🤖 Prompt for AI Agents
In @NetbirdKit/ConfigurationProvider.swift around lines 91 - 94, The
preSharedKey getter currently calls preferences.getPreSharedKey(nil) and
swallows read errors; change it to follow the rosenpass getters pattern by
passing an NSError? variable (e.g., var error: NSError?) to
preferences.getPreSharedKey(&error), check if error != nil and handle it (log
the error via your logger or return a distinguishable value), and only return
the key when no error occurred; update the preSharedKey computed property to
surface or log read errors consistently with the rosenpass getters.
| /// tvOS implementation that reads/writes settings directly to the config JSON. | ||
| /// This mirrors iOS behavior where all settings live in one config file. | ||
| /// The config JSON is stored in UserDefaults and sent to the extension via IPC. | ||
| final class tvOSConfigurationProvider: ConfigurationProvider { |
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.
Fix type name to follow Swift naming conventions.
The type name should be TVOSConfigurationProvider (not tvOSConfigurationProvider) to comply with Swift naming conventions.
Based on static analysis hints.
📝 Proposed fix
-final class tvOSConfigurationProvider: ConfigurationProvider {
+final class TVOSConfigurationProvider: ConfigurationProvider {Also update the factory on Line 246:
- return tvOSConfigurationProvider()
+ return TVOSConfigurationProvider()Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 SwiftLint (0.57.0)
[Error] 130-130: Type name 'tvOSConfigurationProvider' should start with an uppercase character
(type_name)
🤖 Prompt for AI Agents
In @NetbirdKit/ConfigurationProvider.swift at line 130, Rename the type
tvOSConfigurationProvider to follow Swift conventions
(TVOSConfigurationProvider) and update all references and instantiations
accordingly; specifically, change the class declaration final class
tvOSConfigurationProvider: ConfigurationProvider to final class
TVOSConfigurationProvider: ConfigurationProvider and update the factory that
currently constructs tvOSConfigurationProvider to construct
TVOSConfigurationProvider (and adjust any other references, imports or tests) so
the project compiles with the new identifier.
| @discardableResult | ||
| func commit() -> Bool { | ||
| // Settings are written directly to config JSON, no separate commit needed | ||
| return true | ||
| } |
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.
commit() return value is misleading.
The method always returns true (line 162), even though the underlying JSON update operations could fail silently (see comments on lines 201-234). This violates the documented contract in the protocol (line 37: "Returns true on success, false on failure") and prevents callers from detecting persistence failures.
Either:
- Track and return actual success/failure of the most recent JSON update, or
- Update the protocol documentation to clarify that tvOS commit() always returns
truebecause updates are immediate
🤖 Prompt for AI Agents
In @NetbirdKit/ConfigurationProvider.swift around lines 159 - 163, The commit()
implementation always returns true which breaks the protocol contract; modify
ConfigurationProvider so commit() reflects real persistence outcome by tracking
the success of JSON write operations: introduce a private Bool (e.g.,
lastWriteSucceeded) that all methods which update the config JSON set to true on
successful write and false on failure, then have commit() return that value (and
reset it if appropriate); alternatively, if you prefer the tvOS behavior is
always immediate, update the protocol documentation comment for commit() to
state it always returns true on tvOS—choose one approach and apply it
consistently across the JSON-update methods and the commit() implementation.
- Use SDKROOT for libresolv.tbd instead of hardcoded SDK version - Add AppLogger warnings when config JSON updates fail
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.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In @NetBird.xcodeproj/project.pbxproj:
- Line 894: PeerCard.swift (the UI component) is incorrectly included in the
NetbirdNetworkExtension target; remove the build file entry that lists
"PeerCard.swift in Sources" from the NetbirdNetworkExtension target (or set the
file's target membership to only the UI app targets such as NetBird and NetBird
TV) so the network extension no longer compiles this SwiftUI/UI file.
- Line 1005: Replace the hardcoded tvOS deployment target 17.0 with 16.0 across
the project PBX entries: update TVOS_DEPLOYMENT_TARGET = 17.0 to
TVOS_DEPLOYMENT_TARGET = 16.0 for the NetBird TV Debug and Release
configurations and for the NetBirdTVNetworkExtension Debug and Release
configurations so the build supports tvOS 16.0+ as stated in the PR.
In @NetbirdKit/ConfigurationProvider.swift:
- Around line 175-177: The saveConfigJSON method currently discards the boolean
return from Preferences.saveConfigToUserDefaults, so failures are silent; modify
saveConfigJSON to capture the return value from
Preferences.saveConfigToUserDefaults(json), check if it is false, and then log
an error (or handle/retry as appropriate) using the existing logging mechanism
so callers know the save failed and in-memory vs persisted state may be
inconsistent; refer to saveConfigJSON and Preferences.saveConfigToUserDefaults
to locate and update the code.
🧹 Nitpick comments (1)
NetbirdKit/ConfigurationProvider.swift (1)
49-49: Optional: SwiftLint type naming convention.SwiftLint flags
iOSConfigurationProviderandtvOSConfigurationProviderfor not starting with an uppercase character. While technically correct per Swift naming conventions (PascalCase), the current naming is readable and reflects the platform prefixes (iOS/tvOS).You can either:
- Suppress the SwiftLint warning (reasonable for well-known platform names)
- Rename to
IOSConfigurationProviderandTVOSConfigurationProvider(less readable but compliant)Also applies to: 130-130
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
NetBird.xcodeproj/project.pbxprojNetbirdKit/ConfigurationProvider.swift
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-24T15:54:54.967Z
Learnt from: shuuri-labs
Repo: netbirdio/ios-client PR: 36
File: NetBird/Source/App/Views/TV/TVSettingsView.swift:611-617
Timestamp: 2025-12-24T15:54:54.967Z
Learning: On iOS, the pre-shared key (PSK) is stored in the SDK's config file, not in the Keychain. On tvOS, the config (including PSK) is stored in UserDefaults and transferred to the network extension via IPC as a single JSON blob, making Keychain integration architecturally more complex.
Applied to files:
NetbirdKit/ConfigurationProvider.swift
🧬 Code graph analysis (1)
NetbirdKit/ConfigurationProvider.swift (2)
NetBird/Source/App/ViewModels/MainViewModel.swift (4)
getRosenpassEnabled(327-329)setRosenpassEnabled(309-325)getRosenpassPermissive(331-333)setRosenpassPermissive(344-353)NetbirdKit/Preferences.swift (1)
loadConfigFromUserDefaults(107-109)
🪛 SwiftLint (0.57.0)
NetbirdKit/ConfigurationProvider.swift
[Error] 49-49: Type name 'iOSConfigurationProvider' should start with an uppercase character
(type_name)
[Error] 130-130: Type name 'tvOSConfigurationProvider' should start with an uppercase character
(type_name)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build iOS App
- GitHub Check: Build and Test
🔇 Additional comments (5)
NetbirdKit/ConfigurationProvider.swift (3)
17-43: LGTM: Clean protocol abstraction.The protocol provides a well-designed abstraction for platform-specific configuration management with clear property and method contracts.
47-122: LGTM: Solid iOS implementation with proper error handling.The iOS implementation correctly uses NetBirdSDKPreferences with appropriate error handling for read operations and clear commit/reload semantics.
250-258: LGTM: Factory correctly selects platform implementation.The factory pattern cleanly abstracts the platform-specific provider creation.
NetBird.xcodeproj/project.pbxproj (2)
324-325: [Your rewritten review comment text here]
[Exactly ONE classification tag]
102-103: Separate GoogleService-Info.plist files recommended for iOS app and network extension with different bundle identifiers.The same
GoogleService-Info.plistfile reference is being added to both the iOS app and iOS network extension targets:
- NetBird (iOS):
io.netbird.app- NetbirdNetworkExtension:
io.netbird.app.NetbirdNetworkExtensionFirebase typically requires separate configuration files for each bundle identifier. While the app functions without Firebase (it's optional per the README), using a shared configuration may cause analytics and crash reporting to be attributed incorrectly or fail silently when Firebase is configured.
Since
GoogleService-Info.plistis gitignored and developer-provided, ensure your Firebase project is configured to support both bundle identifiers in a single plist, or generate separate Firebase configuration files for each target.Likely an incorrect or invalid review comment.
| 50213A262A8D0A870031D993 /* NetworkChangeListener.swift in Sources */, | ||
| 50CD81502AD0355000CF830B /* PacketTunnelProviderSettingsManager.swift in Sources */, | ||
| 50003BCD2AFD3B2B00E5EB6B /* ClientState.swift in Sources */, | ||
| 50CD81B12AD5B94D00CF830B /* PeerCard.swift in Sources */, |
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.
Remove UI component from network extension target.
PeerCard.swift (likely a SwiftUI view component) is being compiled into the NetbirdNetworkExtension target. Network extensions run in the background without UI capability, so including UI components:
- Increases binary size unnecessarily
- May cause runtime issues if UI frameworks behave differently in extension contexts
- Violates separation of concerns
🧹 Recommended fix
Remove this build file entry or exclude PeerCard.swift from the NetbirdNetworkExtension target. The file should only be compiled into the main app targets (NetBird, NetBird TV) where UI is displayed.
🤖 Prompt for AI Agents
In @NetBird.xcodeproj/project.pbxproj at line 894, PeerCard.swift (the UI
component) is incorrectly included in the NetbirdNetworkExtension target; remove
the build file entry that lists "PeerCard.swift in Sources" from the
NetbirdNetworkExtension target (or set the file's target membership to only the
UI app targets such as NetBird and NetBird TV) so the network extension no
longer compiles this SwiftUI/UI file.
| SWIFT_VERSION = 5.0; | ||
| TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NetBird.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/NetBird"; | ||
| TARGETED_DEVICE_FAMILY = 3; | ||
| TVOS_DEPLOYMENT_TARGET = 17.0; |
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.
Inconsistency: tvOS deployment target is 17.0 but PR objectives state 16.0+ support.
The build configuration sets TVOS_DEPLOYMENT_TARGET = 17.0, but the PR description explicitly mentions "tvOS 16.0+" support. This discrepancy will prevent the app from running on tvOS 16.x devices.
📱 Proposed fix to align with stated requirements
Update all four occurrences of the tvOS deployment target to 16.0:
- TVOS_DEPLOYMENT_TARGET = 17.0;
+ TVOS_DEPLOYMENT_TARGET = 16.0;Apply this change to:
- Line 1005: NetBird TV Debug configuration
- Line 1043: NetBird TV Release configuration
- Line 1082: NetBirdTVNetworkExtension Debug configuration
- Line 1117: NetBirdTVNetworkExtension Release configuration
Also applies to: 1043-1043, 1082-1082, 1117-1117
🤖 Prompt for AI Agents
In @NetBird.xcodeproj/project.pbxproj at line 1005, Replace the hardcoded tvOS
deployment target 17.0 with 16.0 across the project PBX entries: update
TVOS_DEPLOYMENT_TARGET = 17.0 to TVOS_DEPLOYMENT_TARGET = 16.0 for the NetBird
TV Debug and Release configurations and for the NetBirdTVNetworkExtension Debug
and Release configurations so the build supports tvOS 16.0+ as stated in the PR.
| private func saveConfigJSON(_ json: String) { | ||
| _ = Preferences.saveConfigToUserDefaults(json) | ||
| } |
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.
Check and log the result of saveConfigToUserDefaults.
The return value from saveConfigToUserDefaults is discarded. If the save operation fails, the in-memory state and persisted state will diverge silently, leading to inconsistencies.
✅ Proposed fix
private func saveConfigJSON(_ json: String) {
- _ = Preferences.saveConfigToUserDefaults(json)
+ let success = Preferences.saveConfigToUserDefaults(json)
+ if !success {
+ AppLogger.shared.log("ConfigurationProvider: Failed to save config JSON to UserDefaults")
+ }
}🤖 Prompt for AI Agents
In @NetbirdKit/ConfigurationProvider.swift around lines 175 - 177, The
saveConfigJSON method currently discards the boolean return from
Preferences.saveConfigToUserDefaults, so failures are silent; modify
saveConfigJSON to capture the return value from
Preferences.saveConfigToUserDefaults(json), check if it is false, and then log
an error (or handle/retry as appropriate) using the existing logging mechanism
so callers know the save failed and in-memory vs persisted state may be
inconsistent; refer to saveConfigJSON and Preferences.saveConfigToUserDefaults
to locate and update the code.
reading and writing config fields
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.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @NetBird.xcodeproj/project.pbxproj:
- Around line 612-618: The TV app target is missing the Crashlytics dSYM upload
ShellScript build phase; add a build phase entry analogous to the iOS phase with
id 508BD8502AF153350055E415 into the TV target's buildPhases list (the block
currently containing 441C5AEA2EDF0DAE0055EEFC, 441C5AEB2EDF0DAE0055EEFC,
441C5AEC2EDF0DAE0055EEFC, 441C5B0B2EDF0DD20055EEFC, 44DCF5A82EDF45C10026078E)
and create the corresponding PBXShellScriptBuildPhase object that runs the
FirebaseCrashlytics dSYM upload script (matching the iOS phase contents and
settings) so crash reports from the TV target are properly symbolicated.
- Around line 783-796: The TV targets' PBXResourcesBuildPhase entries
(441C5AEC2EDF0DAE0055EEFC and 441C5AFB2EDF0DD20055EEFC) are empty but the TV app
references Lottie and Firebase; add the Lottie animation JSONs (button-*.json,
loading.json, logo_NetBird.json) and GoogleService-Info.plist to those Resources
build phases so they are bundled with the tvOS app, or alternatively guard the
usage sites (CustomLottieView.swift and its callers in MainView.swift) and any
Firebase initialization with #if os(tvOS) / runtime checks to avoid loading
missing resources.
🧹 Nitpick comments (3)
NetbirdKit/ConfigurationProvider.swift (2)
49-49: Consider SwiftLint-compliant naming.SwiftLint flags
iOSConfigurationProviderandtvOSConfigurationProviderfor not starting with uppercase. While the current naming mirrors Apple's platform naming conventions and is readable, you could consider alternatives likeIOSConfigurationProvider/TVOSConfigurationProvideror suppress the rule if preferred.Option 1: Rename to uppercase
-final class iOSConfigurationProvider: ConfigurationProvider { +final class IOSConfigurationProvider: ConfigurationProvider {-final class tvOSConfigurationProvider: ConfigurationProvider { +final class TVOSConfigurationProvider: ConfigurationProvider {Then update the factory accordingly.
Option 2: Suppress the rule
+// swiftlint:disable:next type_name final class iOSConfigurationProvider: ConfigurationProvider {+// swiftlint:disable:next type_name final class tvOSConfigurationProvider: ConfigurationProvider {Also applies to: 130-130
196-238: Consider consolidating duplicate JSON update logic.
updateJSONFieldandupdateJSONStringFieldshare nearly identical structure, differing only in the value type. A generic helper could reduce duplication.♻️ Proposed refactor using a generic helper
- private func updateJSONField(field: String, value: Bool) { + private func updateJSONField<T>(field: String, value: T) { guard var dict = parseConfigDict() else { AppLogger.shared.log("ConfigurationProvider: No config JSON available for updating '\(field)'") return } guard dict[field] != nil else { AppLogger.shared.log("ConfigurationProvider: Field '\(field)' not found in config JSON") return } dict[field] = value guard let data = try? JSONSerialization.data(withJSONObject: dict, options: [.sortedKeys]), let json = String(data: data, encoding: .utf8) else { AppLogger.shared.log("ConfigurationProvider: Failed to serialize config JSON") return } saveConfigJSON(json) } - - private func updateJSONStringField(field: String, value: String) { - guard var dict = parseConfigDict() else { - AppLogger.shared.log("ConfigurationProvider: No config JSON available for updating '\(field)'") - return - } - - guard dict[field] != nil else { - AppLogger.shared.log("ConfigurationProvider: Field '\(field)' not found in config JSON") - return - } - - dict[field] = value - - guard let data = try? JSONSerialization.data(withJSONObject: dict, options: [.sortedKeys]), - let json = String(data: data, encoding: .utf8) else { - AppLogger.shared.log("ConfigurationProvider: Failed to serialize config JSON") - return - } - - saveConfigJSON(json) - }Then update the setter at line 150:
- set { updateJSONStringField(field: "PreSharedKey", value: newValue) } + set { updateJSONField(field: "PreSharedKey", value: newValue) }NetBird.xcodeproj/project.pbxproj (1)
957-957: UI component included in network extension (pre-existing).
PeerCard.swiftis a SwiftUI view component included in the iOSNetbirdNetworkExtensionSources phase. Network extensions don't render UI, so this appears to be unnecessary. While this is likely a pre-existing issue, consider removing UI components from extension targets to reduce binary size.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
NetBird.xcodeproj/project.pbxprojNetbirdKit/ConfigurationProvider.swift
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-24T15:54:54.967Z
Learnt from: shuuri-labs
Repo: netbirdio/ios-client PR: 36
File: NetBird/Source/App/Views/TV/TVSettingsView.swift:611-617
Timestamp: 2025-12-24T15:54:54.967Z
Learning: On iOS, the pre-shared key (PSK) is stored in the SDK's config file, not in the Keychain. On tvOS, the config (including PSK) is stored in UserDefaults and transferred to the network extension via IPC as a single JSON blob, making Keychain integration architecturally more complex.
Applied to files:
NetbirdKit/ConfigurationProvider.swift
🧬 Code graph analysis (1)
NetbirdKit/ConfigurationProvider.swift (2)
NetBird/Source/App/ViewModels/MainViewModel.swift (4)
getRosenpassEnabled(327-329)setRosenpassEnabled(309-325)getRosenpassPermissive(331-333)setRosenpassPermissive(344-353)NetbirdKit/Preferences.swift (1)
loadConfigFromUserDefaults(107-109)
🪛 SwiftLint (0.57.0)
NetbirdKit/ConfigurationProvider.swift
[Error] 49-49: Type name 'iOSConfigurationProvider' should start with an uppercase character
(type_name)
[Error] 130-130: Type name 'tvOSConfigurationProvider' should start with an uppercase character
(type_name)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build and Test
🔇 Additional comments (12)
NetbirdKit/ConfigurationProvider.swift (5)
17-43: Well-designed protocol abstraction.The protocol cleanly separates platform-specific storage concerns while maintaining a consistent interface. The documentation clearly explains the different storage strategies for iOS (file-based) vs tvOS (UserDefaults + IPC).
59-72: iOS implementation looks solid.Error handling for reads logs failures and falls back to safe defaults (
false). Thecommit()properly propagates success/failure. UsingPreferences.newPreferences()inreload()ensures fresh state after configuration changes.Also applies to: 74-87, 106-115
202-205: Field existence check prevents adding new fields.The guard
dict[field] != nilwill silently fail if a new setting is added to the protocol but doesn't exist in an older stored config. This is safe defensively, but if future config schema changes are expected, consider inserting the field instead of failing.Verify this is the intended behavior: if a user has an older config without newer fields (e.g., a future setting), should the update silently fail, or should the field be added?
Also applies to: 224-227
159-167: Note:commit()always returnstruedespite potential write failures.The
updateJSONFieldmethods can fail (and log errors), but since writes happen immediately on property set, there's no way to propagate those failures throughcommit(). This is acceptable given the architecture (immediate writes vs. batched commit), but callers should be aware thatcommit()success doesn't guarantee all prior writes succeeded.
245-252: Factory implementation is clean.The
#elsebranch correctly defaults to tvOS since these are the only two Apple platforms targeted by this codebase.NetBird.xcodeproj/project.pbxproj (7)
6-6: Xcode version requirement note.
objectVersion = 70requires Xcode 16+. Ensure all team members have updated their Xcode installations to avoid project file incompatibility.
637-658: NetBirdTVNetworkExtension configuration looks appropriate.The TV network extension correctly excludes Firebase dependencies (which are included in the iOS extension). This is reasonable since:
- Network extensions have limited execution context
- Firebase may have tvOS compatibility concerns in extension targets
- Core VPN functionality (NetBirdSDK, NetworkExtension, libresolv) is properly included
888-940: TV target source files are properly configured.The Sources build phases for both TV targets include:
- Shared business logic (ViewModels, Models, Adapters)
- TV-specific UI views (TVMainView, TVSettingsView, TVAuthView, etc.)
- Core networking and configuration (ConfigurationProvider, Platform)
The iOS-specific UI components are correctly excluded from TV targets.
1079-1080: Swift 6 concurrency settings only on TV targets may cause issues with shared code.The TV targets set
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActorandSWIFT_APPROACHABLE_CONCURRENCY = YES, which are Swift 6 concurrency features. These are not present on the iOS targets.This could cause subtle behavioral differences or compilation issues when sharing ViewModels and other code between iOS and tvOS if the shared code doesn't account for these isolation defaults.
Consider aligning concurrency settings across platforms, or verify that shared code (
MainViewModel.swift,PeerViewModel.swift, etc.) compiles correctly with these settings.Also applies to: 1117-1118
609-658: TV targets are properly structured and integrated.The new tvOS targets are well-integrated:
- Proper target dependency from NetBird TV → NetBirdTVNetworkExtension
- Correct build phases (Sources, Frameworks, Resources, Embed Extensions)
- FileSystemSynchronizedRootGroup for TV directories enables automatic file syncing
- Swift Package dependencies correctly linked (Firebase, Lottie for app)
1053-1053: No action required. All TV entitlements files are properly configured with the requiredcom.apple.developer.networking.networkextension(packet-tunnel-provider) capability and matching App Group entitlements (group.io.netbird.app.tv) for both Debug and Release variants across all TV targets.
1084-1084: No action needed—deployment target is correct.The TVOS_DEPLOYMENT_TARGET = 17.0 setting is correct and intentional. The README.md and code already document tvOS 17.0+ as the minimum requirement. The Platform.swift file includes an availability check gating VPN support to tvOS 17.0+, confirming this is not a mistake but a deliberate design choice based on API requirements.
Likely an incorrect or invalid review comment.
| buildPhases = ( | ||
| 441C5AEA2EDF0DAE0055EEFC /* Sources */, | ||
| 441C5AEB2EDF0DAE0055EEFC /* Frameworks */, | ||
| 441C5AEC2EDF0DAE0055EEFC /* Resources */, | ||
| 441C5B0B2EDF0DD20055EEFC /* Embed Foundation Extensions */, | ||
| 44DCF5A82EDF45C10026078E /* Embed Frameworks */, | ||
| ); |
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.
Missing Crashlytics dSYM upload script for TV app.
The NetBird TV target includes FirebaseCrashlytics as a dependency (line 630) but lacks the shell script build phase for dSYM upload that exists in the iOS targets (lines 841-884). This will result in unsymbolicated crash reports in the Firebase console.
Add a ShellScript build phase similar to the iOS target's 508BD8502AF153350055E415 to enable proper crash symbolication.
🤖 Prompt for AI Agents
In @NetBird.xcodeproj/project.pbxproj around lines 612 - 618, The TV app target
is missing the Crashlytics dSYM upload ShellScript build phase; add a build
phase entry analogous to the iOS phase with id 508BD8502AF153350055E415 into the
TV target's buildPhases list (the block currently containing
441C5AEA2EDF0DAE0055EEFC, 441C5AEB2EDF0DAE0055EEFC, 441C5AEC2EDF0DAE0055EEFC,
441C5B0B2EDF0DD20055EEFC, 44DCF5A82EDF45C10026078E) and create the corresponding
PBXShellScriptBuildPhase object that runs the FirebaseCrashlytics dSYM upload
script (matching the iOS phase contents and settings) so crash reports from the
TV target are properly symbolicated.
| } | ||
| } | ||
| } | ||
| #endif |
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.
Seems like this is still happening, @shuuri-labs.
- Add detailed comment explaining tvOS UserDefaults sync limitation - Use AppLogger instead of print in Preferences.swift - Remove unnecessary #if os() checks in tvOS PacketTunnelProvider - Remove duplicate tvOS entitlement/plist files from NetBirdTV/ - Fix CI build destinations to use simulator
…SON parsing method
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.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @NetBirdTVNetworkExtension/PacketTunnelProvider.swift:
- Around line 627-671: In initializeLogging, remove the unnecessary stored
"success" variable and instead capture NetBirdSDKInitializeLog's Bool return
directly when calling it; ensure you check its return immediately and log the
error from the NSError pointer if it fails. For the FileHandle usage in the
existing fileExists branch, open the handle once, use a defer block to always
call fileHandle.closeFile(), and wrap the write operations in proper error
handling so any failure doesn't prevent the deferred close from running; also
handle the nil/optional cases for logURL consistently before attempting writes.
🧹 Nitpick comments (12)
NetBird/Source/App/Views/TV/TVPeersView.swift (3)
88-92: Transition animation may not trigger without explicit animation block.The
.transition(.move(edge: .trailing))is declared, but for SwiftUI transitions to animate, the state change that triggers the view appearance/disappearance needs to be wrapped inwithAnimation. Currently,selectedPeer = peerinTVPeerCard.onSelectis not animated.♻️ Wrap the conditional view in an animation block or animate the state change
Option 1 - Add animation modifier to the conditional:
// Right Side - Peer Details if let peer = selectedPeer { TVPeerDetailView(peer: peer) .frame(width: 500) .transition(.move(edge: .trailing)) } } + .animation(.easeInOut(duration: 0.25), value: selectedPeer?.id)Option 2 - Animate the state change in TVPeerCard:
- onSelect: { selectedPeer = peer } + onSelect: { withAnimation { selectedPeer = peer } }
119-172: Consider adding accessibility labels for VoiceOver support on tvOS.tvOS supports VoiceOver, and the peer cards would benefit from accessibility labels to describe the peer's status and connection type for users who rely on screen readers.
♻️ Example accessibility enhancement
} .buttonStyle(.plain) .focused($isFocused) + .accessibilityLabel("\(peer.fqdn), \(peer.ip), \(peer.connStatus)") + .accessibilityHint(peer.connStatus == "Connected" ? (peer.relayed ? "Connected via relay" : "Direct connection") : "") }
27-28: Extract string literal "Connected" to a constant to reduce brittleness.Comparing
extensionStateText == "Connected"repeats a magic string across multiple views. If the state representation changes inMainViewModel, these comparisons will silently fail. Consider extracting the connection state strings to constants or using the underlyingClientStateenum directly instead of converting to string.NetbirdKit/ConfigurationProvider.swift (2)
49-49: Consider renaming to satisfy SwiftLint type_name rule.SwiftLint flags
iOSConfigurationProviderandtvOSConfigurationProviderbecause type names should start with an uppercase character. While the current naming is clear for platform identification, you could use alternatives likeIOSConfigurationProvider/TVOSConfigurationProvideror suppress the rule if the team prefers the current style.Option 1: Rename types
-final class iOSConfigurationProvider: ConfigurationProvider { +final class IOSConfigurationProvider: ConfigurationProvider {-final class tvOSConfigurationProvider: ConfigurationProvider { +final class TVOSConfigurationProvider: ConfigurationProvider {Update the factory accordingly:
-return iOSConfigurationProvider() +return IOSConfigurationProvider()-return tvOSConfigurationProvider() +return TVOSConfigurationProvider()Option 2: Disable SwiftLint rule for these lines
+// swiftlint:disable:next type_name final class iOSConfigurationProvider: ConfigurationProvider {Also applies to: 130-130
196-216: Silent failure when field doesn't exist in config JSON.
updateJSONFieldlogs and returns early if the field doesn't exist in the config (line 202-205). This design prevents adding new fields but may silently fail if the config was initialized without expected fields. Consider whether this is the intended behavior or if it should create missing fields.If the intention is to only update existing fields, the current implementation is correct. If fields should be created when missing, consider:
Alternative: Allow creating new fields
- guard dict[field] != nil else { - AppLogger.shared.log("ConfigurationProvider: Field '\(field)' not found in config JSON") - return - } + // Note: This will create the field if it doesn't existNetbirdKit/Preferences.swift (2)
97-104:synchronize()is deprecated but may be intentional for IPC reliability.
UserDefaults.synchronize()has been deprecated since iOS 12. The system automatically syncs at appropriate times. However, given the tvOS IPC constraints mentioned in the PR objectives, you may be using it to force immediate persistence before IPC transfer.If the explicit sync is needed for IPC timing, consider adding a comment explaining why. Otherwise, these calls can be safely removed.
If removal is safe
defaults.set(configJSON, forKey: configJSONKey) - defaults.synchronize() return truedefaults.removeObject(forKey: configJSONKey) - defaults.synchronize() }Also applies to: 117-123
127-139: Consider adding error logging for restore failure.
restoreConfigFromUserDefaultsreturnsfalseon failure but doesn't log the error. This could make debugging difficult.Add error logging
do { try configJSON.write(toFile: path, atomically: false, encoding: .utf8) return true } catch { + AppLogger.shared.log("ERROR: Failed to restore config from UserDefaults: \(error.localizedDescription)") return false }NetBird/Source/App/ViewModels/MainViewModel.swift (1)
426-428: Consider removing@MainActor inredundancy.The
ViewModelclass is already marked@MainActor(line 46), soTask { @MainActor in ... }is redundant. A plainTask { ... }will inherit the actor context.Simplify Task
- Task { @MainActor in - self.networkExtensionAdapter.stop() + Task { + self.networkExtensionAdapter.stop() }NetBirdTVNetworkExtension/PacketTunnelProvider.swift (4)
76-84: Clarify the 2-second delay before login error.The delay before returning the "Login required" error seems arbitrary. Consider adding a comment explaining why this delay exists, or removing it if unnecessary.
if needsLogin { + // Delay allows the system to finish tunnel setup before we signal login is needed DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
139-143: Consider usingData(_:)for cleaner string conversion.SwiftLint flags multiple instances of
"string".data(using: .utf8). While this is safe for ASCII strings (UTF-8 encoding always succeeds), usingData("string".utf8)is cleaner and non-optional.Example fix for one occurrence
- completionHandler("true".data(using: .utf8)) + completionHandler(Data("true".utf8))This pattern applies to lines 139, 143, 247, 266, 275, 284, 304, 314, 321, 325, 349.
346-387: Consider a structured response for checkLoginComplete.The pipe-separated diagnostic string (
"result|isExecuting|loginRequired|configExists|stateExists|lastResult|lastError") is fragile to parse. Consider using JSON or a Codable struct for type safety.Alternative: Use Codable struct
struct LoginCheckResponse: Codable { let isComplete: Bool let isExecuting: Bool let loginRequired: Bool let configExists: Bool let stateExists: Bool let lastResult: String let lastError: String } // In checkLoginComplete: let response = LoginCheckResponse( isComplete: isComplete, isExecuting: isExecutingLogin, loginRequired: loginRequired, configExists: configExists, stateExists: stateExists, lastResult: lastResult, lastError: lastError ) let data = try? JSONEncoder().encode(response) completionHandler(data)
665-670:successresult fromNetBirdSDKInitializeLogis only checked for error logging.The
successvariable is set but never used to influence behavior beyond logging. If initialization failure should prevent further operations, consider early return or propagating the error.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
.github/workflows/build.ymlNetBird/Source/App/ViewModels/MainViewModel.swiftNetBird/Source/App/Views/TV/TVPeersView.swiftNetBirdTVNetworkExtension/PacketTunnelProvider.swiftNetbirdKit/ConfigurationProvider.swiftNetbirdKit/Preferences.swift
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/build.yml
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-24T15:54:45.482Z
Learnt from: shuuri-labs
Repo: netbirdio/ios-client PR: 36
File: NetBird/Source/App/Views/TV/TVSettingsView.swift:611-617
Timestamp: 2025-12-24T15:54:45.482Z
Learning: Platform-specific PSK storage guidance: For iOS builds, store the pre-shared key (PSK) in the SDK config file rather than in the Keychain. For tvOS builds, store the config (including the PSK) in UserDefaults and transfer it to the network extension via inter-process communication (IPC) as a single JSON blob. This pattern avoids fragile cross-process Keychain integrations on tvOS and should be reflected in TV-related Swift files (e.g., NetBird/Source/App/Views/TV/). When reviewing code, verify PSK-related storage follows this separation and that the tvOS flow uses a JSON blob over IPC instead of direct Keychain access.
Applied to files:
NetBird/Source/App/Views/TV/TVPeersView.swift
📚 Learning: 2025-12-24T15:54:54.967Z
Learnt from: shuuri-labs
Repo: netbirdio/ios-client PR: 36
File: NetBird/Source/App/Views/TV/TVSettingsView.swift:611-617
Timestamp: 2025-12-24T15:54:54.967Z
Learning: On iOS, the pre-shared key (PSK) is stored in the SDK's config file, not in the Keychain. On tvOS, the config (including PSK) is stored in UserDefaults and transferred to the network extension via IPC as a single JSON blob, making Keychain integration architecturally more complex.
Applied to files:
NetbirdKit/Preferences.swiftNetBird/Source/App/ViewModels/MainViewModel.swiftNetbirdKit/ConfigurationProvider.swift
🧬 Code graph analysis (4)
NetBird/Source/App/Views/TV/TVPeersView.swift (2)
NetBird/Source/App/Views/Components/TransparentGradientButton.swift (1)
body(22-53)NetBird/Source/App/Platform/Platform.swift (1)
size(45-47)
NetbirdKit/Preferences.swift (1)
NetbirdKit/AppLogger.swift (1)
log(88-98)
NetBird/Source/App/ViewModels/MainViewModel.swift (2)
NetBirdTVNetworkExtension/PacketTunnelProvider.swift (2)
onError(24-26)onSuccess(20-22)NetbirdKit/ConfigurationProvider.swift (5)
create(224-230)commit(106-115)commit(159-163)reload(117-120)reload(165-167)
NetbirdKit/ConfigurationProvider.swift (2)
NetBird/Source/App/ViewModels/MainViewModel.swift (4)
getRosenpassEnabled(327-329)setRosenpassEnabled(309-325)getRosenpassPermissive(331-333)setRosenpassPermissive(344-353)NetbirdKit/Preferences.swift (1)
loadConfigFromUserDefaults(107-109)
🪛 SwiftLint (0.57.0)
NetBirdTVNetworkExtension/PacketTunnelProvider.swift
[Warning] 139-139: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 143-143: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 247-247: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 266-266: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 275-275: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 284-284: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 304-304: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 314-314: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 321-321: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 321-321: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 325-325: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
[Warning] 349-349: Prefer non-optional Data(_:) initializer when converting String to Data
(non_optional_string_data_conversion)
NetbirdKit/ConfigurationProvider.swift
[Error] 49-49: Type name 'iOSConfigurationProvider' should start with an uppercase character
(type_name)
[Error] 130-130: Type name 'tvOSConfigurationProvider' should start with an uppercase character
(type_name)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and Test
- GitHub Check: Build iOS App
🔇 Additional comments (16)
NetBird/Source/App/Views/TV/TVPeersView.swift (3)
112-182: LGTM - Well-implemented tvOS peer card with proper focus handling.The focus state management, visual feedback (scale, border, background), and animation timing are appropriate for tvOS. The button style and focus binding pattern is correct.
185-223: LGTM - Clean detail panel implementation.The detail view appropriately displays peer information in a readable format for tvOS. The conditional routes display and consistent use of TV styling colors is well done.
260-284: Consistent focus handling pattern across TV components.The focus state management and visual feedback is consistent with TVPeerCard, which maintains a cohesive user experience across the tvOS interface.
NetbirdKit/ConfigurationProvider.swift (4)
17-43: LGTM! Clean protocol abstraction.The protocol provides a clear contract for platform-specific configuration handling. The separation of Rosenpass settings, pre-shared key management, and lifecycle methods is well-structured.
59-72: LGTM! Proper error handling in iOS getter.The
rosenpassEnabledgetter correctly handles SDK errors with a try-catch and falls back tofalse. The same pattern is applied consistently forrosenpassPermissive.
223-230: LGTM! Simple factory pattern.The factory correctly returns the appropriate platform-specific implementation using compile-time checks.
91-98: Review comment cannot be verified against the SDK method signature.While the code shows
getPreSharedKey(nil)without error handling (unlikegetRosenpassEnabledandgetRosenpassPermissivewhich use try/catch and pass error pointers), the actual SDK method signature from NetBirdSDK is not available in the repository headers. The method may legitimately return String directly without throwing errors, making nil an appropriate parameter. Without access to the NetBirdSDK source or generated Swift interface, the behavior cannot be confirmed as incorrect.NetbirdKit/Preferences.swift (2)
11-25: Excellent platform documentation.The doc comment clearly explains the architectural differences between iOS and tvOS configuration handling. This will help future maintainers understand why the code paths diverge.
30-51: LGTM! Platform-appropriate return types.The iOS version uses
preconditionFailurefor missing entitlements (a critical configuration error), while tvOS correctly returnsnilsince it doesn't use file-based SDK preferences.NetBird/Source/App/ViewModels/MainViewModel.swift (5)
19-43: LGTM! Clean listener bridge pattern.
SSOCheckListenerandSetupKeyErrListenercorrectly bridge the SDK's async callback pattern to Swift closures. The pattern matchesConfigInitSSOListenerin the tvOS extension.
143-160: LGTM! Improved connect() flow with logging and safety.The addition of logging, the 3-second
buttonLocktimeout, and the asyncTaskwrapper improve debuggability and prevent rapid reconnection attempts.
234-252: LGTM! tvOS-specific state text handling.Since tvOS doesn't use
CustomLottieView, directly updatingextensionStateTextis appropriate. Fetching routes on.connectedensures the Networks tab counter is accurate.
258-277: LGTM! Comprehensive cleanup on logout.
clearDetails()properly:
- Clears local UserDefaults (
ip,fqdn)- Removes config JSON from UserDefaults
- Resets
@Publishedproperties for UI consistency- Clears extension-local config on tvOS via IPC
This prevents stale credentials from persisting.
339-342: Good optimization: lazy Rosenpass settings loading.Deferring
loadRosenpassSettings()until the settings view is accessed avoids triggering expensive SDK initialization on app launch. The doc comment clearly explains the rationale.NetBirdTVNetworkExtension/PacketTunnelProvider.swift (2)
17-27: LGTM! SSO listener follows established pattern.
ConfigInitSSOListenermatches the bridge pattern used inMainViewModel.swiftfor consistent callback handling.
418-472: LGTM! Well-structured device auth flow.The
loginTVmethod correctly:
- Logs management URL source for debugging
- Uses
NSLockto track if URL was sent (thread-safe)- Only calls
completionHandler(nil)on error if URL wasn't already sent- Handles device code expiration gracefully
The
onSuccesscallback correctly doesn't call the completion handler again since the URL was already sent.
| func initializeLogging(loglevel: String) { | ||
| let fileManager = FileManager.default | ||
|
|
||
| let groupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: GlobalConstants.userPreferencesSuiteName) | ||
| let logURL = groupURL?.appendingPathComponent("logfile.log") | ||
|
|
||
| var error: NSError? | ||
| var success = false | ||
|
|
||
| let logMessage = "Starting new log file from TV extension" + "\n" | ||
|
|
||
| guard let logURLValid = logURL else { | ||
| print("Failed to get the log file URL.") | ||
| return | ||
| } | ||
|
|
||
| if fileManager.fileExists(atPath: logURLValid.path) { | ||
| if let fileHandle = try? FileHandle(forWritingTo: logURLValid) { | ||
| do { | ||
| try "".write(to: logURLValid, atomically: true, encoding: .utf8) | ||
| } catch { | ||
| print("Error handling the log file: \(error)") | ||
| } | ||
| if let data = logMessage.data(using: .utf8) { | ||
| fileHandle.write(data) | ||
| } | ||
| fileHandle.closeFile() | ||
| } else { | ||
| print("Failed to open the log file for writing.") | ||
| } | ||
| } else { | ||
| do { | ||
| try logMessage.write(to: logURLValid, atomically: true, encoding: .utf8) | ||
| } catch { | ||
| print("Failed to write to the log file: \(error.localizedDescription)") | ||
| } | ||
| } | ||
|
|
||
| if let logPath = logURL?.path { | ||
| success = NetBirdSDKInitializeLog(loglevel, logPath, &error) | ||
| } | ||
| if !success, let actualError = error { | ||
| print("Failed to initialize log: \(actualError.localizedDescription)") | ||
| } | ||
| } No newline at end of file |
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.
Unused success variable and potential file handle leak.
- The
successvariable on line 634 is assigned but the result isn't used meaningfully for control flow. - If
fileHandle.write(data)throws,fileHandle.closeFile()may not be called.
Suggested fix
var error: NSError?
- var success = false
let logMessage = "Starting new log file from TV extension" + "\n"For the file handle, consider using defer:
if let fileHandle = try? FileHandle(forWritingTo: logURLValid) {
+ defer { fileHandle.closeFile() }
do {
try "".write(to: logURLValid, atomically: true, encoding: .utf8)
} catch {
print("Error handling the log file: \(error)")
}
if let data = logMessage.data(using: .utf8) {
fileHandle.write(data)
}
- fileHandle.closeFile()
} else {🤖 Prompt for AI Agents
In @NetBirdTVNetworkExtension/PacketTunnelProvider.swift around lines 627 - 671,
In initializeLogging, remove the unnecessary stored "success" variable and
instead capture NetBirdSDKInitializeLog's Bool return directly when calling it;
ensure you check its return immediately and log the error from the NSError
pointer if it fails. For the FileHandle usage in the existing fileExists branch,
open the handle once, use a defer block to always call fileHandle.closeFile(),
and wrap the write operations in proper error handling so any failure doesn't
prevent the deferred close from running; also handle the nil/optional cases for
logURL consistently before attempting writes.
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.
Actionable comments posted: 0
🧹 Nitpick comments (1)
.github/workflows/build.yml (1)
93-105: Create tvOS build schemes and add them to CI.This PR introduces new tvOS targets (
NetBird TVapp andNetBirdTVNetworkExtensionextension) with significant platform-specific code, but the CI workflow doesn't build them. Additionally, the tvOS targets lack corresponding schemes in the Xcode project.To validate the new tvOS code in CI:
- Create schemes for
NetBird TVandNetBirdTVNetworkExtensionin Xcode (these will be added toxcshareddata/xcschemes/)- Add build steps to the workflow for these tvOS schemes with
tvosplatform destination (note: tvOS cannot run in simulator, so consider using a generic destination or skip simulator testing)
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.github/workflows/build.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and Test
- GitHub Check: Build iOS App
🔇 Additional comments (3)
.github/workflows/build.yml (3)
16-20: LGTM! Runner and Xcode updates for tvOS support.The upgrade to macos-15 and explicit Xcode 16.1 selection is appropriate for the tvOS 16.0+ support being added in this PR. Explicit Xcode selection ensures reproducible builds.
83-91: Destination change to iOS Simulator is appropriate for unsigned CI builds.Switching to
generic/platform=iOS Simulatoraligns with the disabled code signing (CODE_SIGNING_ALLOWED=NO). Simulator builds are sufficient for validating compilation without provisioning profiles.
36-36: Go 1.24 is available. This version was released on February 11, 2025 and is ready for use.Likely an incorrect or invalid review comment.
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.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @NetBird/Source/App/Views/TV/TVAuthView.swift:
- Around line 281-301: The checkError callback calls onErrorHandler but never
sets the view's errorMessage state, so the error UI never appears; inside the
DispatchQueue.main.async branch where errorMsg is non-nil (the closure passed
into checkError), assign the view's errorMessage state (e.g., self.errorMessage
= errorMsg) before invalidating the timer and invoking
onErrorHandler?(errorMsg), ensuring the UI reads the updated @State
errorMessage; keep the existing timer.invalidate() and onErrorHandler call order
after setting errorMessage.
🧹 Nitpick comments (3)
NetBird/Source/App/Views/TV/TVAuthView.swift (3)
214-231: Silent failure if QR code generation fails.If
filter.outputImageorcontext.createCGImagereturns nil, the user sees the loading spinner indefinitely with no feedback. Consider setting an error state or providing a fallback.Suggested improvement
private func generateQRCode() { let context = CIContext() let filter = CIFilter.qrCodeGenerator() filter.message = Data(loginURL.utf8) filter.correctionLevel = "M" - guard let outputImage = filter.outputImage else { return } + guard let outputImage = filter.outputImage else { + #if DEBUG + print("TVAuthView: Failed to generate QR code output image") + #endif + errorMessage = "Failed to generate QR code" + return + } // Scale up the QR code for better visibility let scale = 10.0 let transform = CGAffineTransform(scaleX: scale, y: scale) let scaledImage = outputImage.transformed(by: transform) - if let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) { + guard let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) else { + #if DEBUG + print("TVAuthView: Failed to create CGImage from QR code") + #endif + errorMessage = "Failed to render QR code" + return + } - qrCodeImage = UIImage(cgImage: cgImage) - } + qrCodeImage = UIImage(cgImage: cgImage) }
314-337: Initial check doesn't verify errors.The initial check (fired immediately on appear) only checks for completion, not errors. If an error state already exists, users will see "Waiting for sign-in..." for up to 2 seconds until the first timer tick. Consider also invoking
checkLoginErrorin the initial check for consistency with the timer callback logic.
370-381: Preview is functional but could include error state.The preview demonstrates the happy path. Consider adding an additional preview case that sets
errorMessageto verify the error UI renders correctly.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.github/workflows/build.ymlNetBird/Source/App/Views/TV/TVAuthView.swift
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/build.yml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-24T15:54:45.482Z
Learnt from: shuuri-labs
Repo: netbirdio/ios-client PR: 36
File: NetBird/Source/App/Views/TV/TVSettingsView.swift:611-617
Timestamp: 2025-12-24T15:54:45.482Z
Learning: Platform-specific PSK storage guidance: For iOS builds, store the pre-shared key (PSK) in the SDK config file rather than in the Keychain. For tvOS builds, store the config (including the PSK) in UserDefaults and transfer it to the network extension via inter-process communication (IPC) as a single JSON blob. This pattern avoids fragile cross-process Keychain integrations on tvOS and should be reflected in TV-related Swift files (e.g., NetBird/Source/App/Views/TV/). When reviewing code, verify PSK-related storage follows this separation and that the tvOS flow uses a JSON blob over IPC instead of direct Keychain access.
Applied to files:
NetBird/Source/App/Views/TV/TVAuthView.swift
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and Test
- GitHub Check: Build iOS App
🔇 Additional comments (2)
NetBird/Source/App/Views/TV/TVAuthView.swift (2)
19-52: Well-structured view API for device code flow.The public interface with
loginURL,userCode, and the various callbacks (onComplete,onError,checkLoginComplete,checkLoginError) provides good flexibility for integrating with different auth backends. The async callback pattern forcheckLoginComplete/checkLoginErroris appropriate for IPC-based status checks.
54-199: LGTM - Well-designed tvOS UI layout.The two-panel layout with QR code and device code is appropriate for the 10-foot experience. Good attention to cleanup: timer is invalidated in both
onDisappearand the cancel button action.
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.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @.github/workflows/build.yml:
- Around line 16-20: The workflow currently selects Xcode 26.1.1 but only builds
iOS targets; add steps that run xcodebuild (or the existing build step
duplicated) to build and test the tvOS schemes "NetBird TV" and
"NetBirdTVNetworkExtension" (use the same workspace/project and configuration
flags as the iOS steps, but set -scheme "NetBird TV" -sdk appletvos and -scheme
"NetBirdTVNetworkExtension" -sdk appletvos or the equivalent xcodebuild test
invocation) so the NetBird TV app and its NetworkExtension target are compiled
and validated in CI; ensure these steps run on the macos-15 runner after
selecting Xcode 26.1.1 and mirror any signing/scheme/environment setup used for
the iOS builds.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.github/workflows/build.yml.github/workflows/test.yml
🚧 Files skipped from review as they are similar to previous changes (1)
- .github/workflows/test.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build iOS App
- GitHub Check: Build and Test
🔇 Additional comments (1)
.github/workflows/build.yml (1)
79-105: tvOS schemes must be created in Xcode before CI build steps can be added.While tvOS targets ("NetBird TV" and "NetBirdTVNetworkExtension") exist in the project, the corresponding Xcode schemes have not been created. The current project only contains schemes for the iOS targets (NetBird and NetbirdNetworkExtension). Any attempt to build the suggested tvOS schemes in CI will fail.
To enable tvOS CI builds:
- Create the "NetBird TV" scheme in Xcode
- Create the "NetBirdTVNetworkExtension" scheme in Xcode
- Add the CI build steps after the schemes are committed to the repository
Likely an incorrect or invalid review comment.
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.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @.github/workflows/build.yml:
- Around line 19-20: The workflow step named "Select Xcode 26.1.1" is selecting
a non-existent Xcode path; update the xcode-select target to the installed
toolchain by changing the path from
/Applications/Xcode_26.1.1.app/Contents/Developer to
/Applications/Xcode_26.2.app/Contents/Developer (i.e., update the run line in
the step that currently runs `sudo xcode-select -s
/Applications/Xcode_26.1.1.app/Contents/Developer` to use Xcode_26.2).
In @.github/workflows/test.yml:
- Around line 19-20: The workflow step referencing a non-existent Xcode "26.1.1"
should be updated to a valid macOS Xcode version; change the step name "Select
Xcode 26.1.1" and the run command path to a real Xcode install (for example
rename to "Select Xcode 16.2" and use sudo xcode-select -s
/Applications/Xcode_16.2.app/Contents/Developer or the appropriate
/Applications/Xcode_16.app path for your runner).
In @build-go-lib.sh:
- Line 19: The gomobile bind invocation in build-go-lib.sh currently targets
only iOS; update the command that starts with "CGO_ENABLED=0 gomobile bind
-target=..." to use "-target=ios,tvos" (i.e., CGO_ENABLED=0 gomobile bind
-target=ios,tvos -bundleid=io.netbird.framework -ldflags="...") so tvOS
architectures are included, and ensure the project uses the NetBird gomobile
tvOS fork (github.com/netbirdio/gomobile-tvos-fork) as documented in the README.
🧹 Nitpick comments (2)
.github/workflows/build.yml (1)
72-110: Dummy GoogleService-Info.plist approach is reasonable for CI.Creating a placeholder plist with disabled Firebase features (
IS_ANALYTICS_ENABLED,IS_GCM_ENABLED, etc. set tofalse) is a good approach for CI builds that don't require actual Firebase functionality. This allows the build to succeed without exposing real credentials.Consider adding a comment in the workflow explaining why this dummy file is needed, to help future maintainers understand the purpose.
.github/workflows/test.yml (1)
50-88: Duplicate plist content across workflows.The dummy
GoogleService-Info.plistis identical to the one inbuild.yml. This is fine for now, but if it needs to change, you'll have to update both files.Consider extracting this to a shared script or a checked-in dummy file (e.g.,
GoogleService-Info.plist.ci-template) to avoid duplication and reduce maintenance burden.♻️ Optional: Extract to a shared template file
Create a file like
ci/GoogleService-Info.plist.templateand reference it in both workflows:- name: Create dummy GoogleService-Info.plist working-directory: ios-client - run: | - cat > GoogleService-Info.plist << 'EOF' - <?xml version="1.0" encoding="UTF-8"?> - ... - EOF + run: cp ci/GoogleService-Info.plist.template GoogleService-Info.plist
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.github/workflows/build.yml.github/workflows/test.ymlbuild-go-lib.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and Test
- GitHub Check: Build iOS App
🔇 Additional comments (3)
.github/workflows/build.yml (2)
16-16: LGTM on runner update.The macos-15 runner is appropriate for newer Xcode versions and tvOS support.
33-36: Go 1.24 is released and available—no action needed.Go 1.24 was released on February 11, 2025, and binaries are available from the official Go download pages. The specified version is current and appropriate.
.github/workflows/test.yml (1)
16-16: LGTM on runner update.Consistent with the build workflow.
| - name: Select Xcode 26.1.1 | ||
| run: sudo xcode-select -s /Applications/Xcode_26.1.1.app/Contents/Developer |
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.
🧩 Analysis chain
🌐 Web query:
Xcode versions available GitHub Actions macOS runner January 2026
💡 Result:
Short answer — what’s available and where to check (Jan 13, 2026):
- GitHub-hosted macOS runners provide different Xcode sets per image (macos-14, macos-15, macos-26 beta). See the runner-images release notes for each image for exact installed Xcode builds. [1][2].
- GitHub support policy in practice (affects what Xcode minors are kept on images): Xcode 15: all minor releases with full platform tools; Xcode 16: “last two” minor releases (oldest replaced as new minors ship). See the GitHub changelog / announcement. [3].
- Recent/deprecation notes: simulator runtimes for Xcode 16.3 and older on macos-15 were deprecated on Jan 12, 2026 (and related Xcode deprecation announcements appear in the runner-images releases). Check the image release notes for exact removal dates and which Xcode minor versions remain. [1][2].
If you want the precise Xcode version list for a specific runner image (e.g., macos-15 or macos-14) right now, tell me which image and I’ll fetch the exact installed Xcode builds from the image README/release page.
Sources: GitHub Actions runner-images releases and changelog. [1] GitHub runner-images releases (macOS image notes). [2] Recent macOS image release entries (macos-14 / macos-15). [3] GitHub Changelog announcement about Xcode 16.2 / Xcode support policy.
🌐 Web query:
GitHub Actions runner-images macos-15 Xcode versions installed 2026
💡 Result:
On the macos-15 GitHub Actions images (2026), available Xcode toolsets have been updated; notable items:
- Xcode 26.0 (beta) is included on macos-15 images (added in mid‑2025). [1]
- Xcode 16.3 (and its SDKs) was present on earlier macos-15 images (spring 2025). [2]
- Xcode 15.4 (and its simulator runtimes) was removed from macos-15 images (targeted May 29, 2025). If you depend on older platform SDKs (pre‑Xcode 16.3), you must install them manually or use macos-14 images. [3]
If you want the exact Xcode versions and SDKs on a specific macos-15 image version (for example image_version 20260105.0094.1), tell me which image tag/date and I’ll pull the precise Xcode table for that release.
Sources: [1] [actions/runner-images macos-15 20250616 release], [2] [actions/runner-images macos-15 20250331 release], [3] [issue: Xcode 15.4 removal].
🌐 Web query:
Xcode 26.1 release version macOS Apple 2026
💡 Result:
Xcode 26.1 (build 17B55) was released November 3, 2025. It requires macOS Sequoia 15.6 or later to install/run. [1][2]
Sources:
- Apple Developer release notes listing Xcode 26.1 (Nov 3, 2025). [1]
- Apple Xcode support page showing "macOS Sequoia 15.6 or later" as the minimum for Xcode 26.1. [2]
🌐 Web query:
Xcode 26.1.1 patch release Apple Developer
💡 Result:
Xcode 26.1.1 (build 17B100) was released November 11, 2025. It’s a patch update that includes coding‑intelligence/AI tooling fixes, Swift 6.2.1 compatibility, and multiple bug fixes across indexing, Instruments, Interface Builder, Swift packages, Xcode Cloud, and more. Download and full release notes are available from Apple’s developer pages. [1][2][3]
Sources:
- Apple Releases page (Xcode 26.1.1 — Nov 11, 2025). [1]
- Xcode support / releases listing (Xcode 26.1.1 entry). [2]
- Coverage summarizing fixes (9to5Mac). [3]
Would you like the direct download/release‑notes link?
🌐 Web query:
GitHub Actions macos-15 runner Xcode 26.1.1 installed January 2026
💡 Result:
Short answer: No — the macos-15 GitHub Actions runner in January 2026 has Xcode 26.2 installed (not Xcode 26.1.1). See the macos-15 image release notes showing Xcode 26.2 on the image (Image Version 20260112). [1] An issue requesting Xcode 26.1.1 was filed but not reflected in the January 2026 macOS images. [2]
Sources:
- actions/runner-images release notes (macos-15 image, 2026-01-12) — shows Xcode 26.2 installed. [1]
- Issue requesting Xcode 26.1.1 on actions/runner-images. [2]
Update Xcode version to 26.2 — version 26.1.1 is not available on the macos-15 runner.
The macos-15 GitHub Actions runner (as of January 2026) includes Xcode 26.2, not 26.1.1. The xcode-select step will fail because /Applications/Xcode_26.1.1.app does not exist on the runner. Update the version to 26.2 to match the installed toolchain.
🤖 Prompt for AI Agents
In @.github/workflows/build.yml around lines 19 - 20, The workflow step named
"Select Xcode 26.1.1" is selecting a non-existent Xcode path; update the
xcode-select target to the installed toolchain by changing the path from
/Applications/Xcode_26.1.1.app/Contents/Developer to
/Applications/Xcode_26.2.app/Contents/Developer (i.e., update the run line in
the step that currently runs `sudo xcode-select -s
/Applications/Xcode_26.1.1.app/Contents/Developer` to use Xcode_26.2).
| - name: Select Xcode 26.1.1 | ||
| run: sudo xcode-select -s /Applications/Xcode_26.1.1.app/Contents/Developer |
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.
Same Xcode version issue as build.yml.
The Xcode version 26.1.1 does not exist. This needs to be corrected to a valid version (likely 16.x).
🤖 Prompt for AI Agents
In @.github/workflows/test.yml around lines 19 - 20, The workflow step
referencing a non-existent Xcode "26.1.1" should be updated to a valid macOS
Xcode version; change the step name "Select Xcode 26.1.1" and the run command
path to a real Xcode install (for example rename to "Select Xcode 16.2" and use
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer or the
appropriate /Applications/Xcode_16.app path for your runner).
| cd $netbirdPath | ||
| gomobile init | ||
| CGO_ENABLED=0 gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=$version" -o $rn_app_path/NetBird/NetBirdSDK.xcframework $netbirdPath/client/ios/NetBirdSDK | ||
| CGO_ENABLED=0 gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=$version" -o $rn_app_path/NetBirdSDK.xcframework $netbirdPath/client/ios/NetBirdSDK |
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.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for tvOS references in the build configuration files
rg -i "tvos|appletv" --type=sh --type=gradle --type=yaml --type=yml --type=json -nRepository: netbirdio/ios-client
Length of output: 91
🏁 Script executed:
#!/bin/bash
# Check the full context of the build-go-lib.sh file to understand the tvOS requirement
fd "build-go-lib.sh" -x wc -l {}Repository: netbirdio/ios-client
Length of output: 84
🏁 Script executed:
#!/bin/bash
# Search for any mentions of tvOS in the entire repository structure
fd -e md -e txt -e sh -e yml -e yaml | xargs rg -l -i "tvos" 2>/dev/null || echo "No tvOS references found"Repository: netbirdio/ios-client
Length of output: 73
🌐 Web query:
gomobile bind tvOS target support -target=ios xcframework
💡 Result:
Short answer: gomobile bind does not officially support tvOS. The gomobile Apple targets are ios / iossimulator / macos / maccatalyst and the tool generates XCFrameworks for those; tvOS is not listed as a supported platform and several projects note that gomobile-built XCFrameworks do not include tvOS slices. [1][2][3]
Options / workarounds:
- Build a tvOS slice yourself (use cgo/clang + go toolchain to produce a tvOS binary or .a, then package into an XCFramework or add that slice into Xcode). Many projects use a custom cgo-based build for tvOS while using gomobile for iOS/macOS slices. [4][5]
- Check/patch golang/mobile or open an issue if you need upstream tvOS support (the gomobile sources and change logs show TODOs for watchOS/tvOS). [3][6]
References:
- gomobile command docs (supported Apple targets: ios, iossimulator, macos, maccatalyst). [1]
- Notes / READMEs from projects using gomobile that explicitly say gomobile does not support tvOS and that they use cgo for tvOS. [4][5]
- gomobile code/review notes showing Apple-target list and TODO for tvOS/watchOS. [3][6]
If you want, tell me whether you prefer (A) step-by-step instructions to build a tvOS slice with cgo and package an XCFramework, or (B) links and example scripts from projects that do this; I’ll provide the exact commands.
🏁 Script executed:
cat -n ./build-go-lib.shRepository: netbirdio/ios-client
Length of output: 663
🏁 Script executed:
grep -i "tvos" README.mdRepository: netbirdio/ios-client
Length of output: 982
Update gomobile bind target to include tvOS support.
The README explicitly documents that this project supports both iOS 14.0+ and tvOS 17.0+, and shows the correct build command: gomobile bind -target=ios,tvos. The current code only specifies -target=ios, which excludes tvOS architectures. Update line 19 to include the tvOS target:
-CGO_ENABLED=0 gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=$version" -o $rn_app_path/NetBirdSDK.xcframework $netbirdPath/client/ios/NetBirdSDK
+CGO_ENABLED=0 gomobile bind -target=ios,tvos -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=$version" -o $rn_app_path/NetBirdSDK.xcframework $netbirdPath/client/ios/NetBirdSDKNote: This requires the NetBird gomobile fork (https://github.com/netbirdio/gomobile-tvos-fork) as documented in the README, since the standard gomobile does not support tvOS targets.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| CGO_ENABLED=0 gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=$version" -o $rn_app_path/NetBirdSDK.xcframework $netbirdPath/client/ios/NetBirdSDK | |
| CGO_ENABLED=0 gomobile bind -target=ios,tvos -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=$version" -o $rn_app_path/NetBirdSDK.xcframework $netbirdPath/client/ios/NetBirdSDK |
🤖 Prompt for AI Agents
In @build-go-lib.sh at line 19, The gomobile bind invocation in build-go-lib.sh
currently targets only iOS; update the command that starts with "CGO_ENABLED=0
gomobile bind -target=..." to use "-target=ios,tvos" (i.e., CGO_ENABLED=0
gomobile bind -target=ios,tvos -bundleid=io.netbird.framework -ldflags="...") so
tvOS architectures are included, and ensure the project uses the NetBird
gomobile tvOS fork (github.com/netbirdio/gomobile-tvos-fork) as documented in
the README.
This commit introduces full tvOS support for the NetBird iOS client, enabling VPN connectivity on Apple TV devices.
Added NetBird TV app target with tvOS 16.0+ deployment
Created tvOS-specific UI using SwiftUI optimized for "10-foot experience"
Tab-based navigation: Connection, Peers, Networks, Settings
Large touch targets and text for Siri Remote navigation
TVMainView: Main tab navigation and connection status
TVConnectionView: Large connect/disconnect button with status display
TVPeersView: List of connected peers with connection details
TVNetworksView: Network routes selection and management
TVSettingsView: App settings and logout functionality
TVAuthView: QR code + device code authentication flow
Implemented OAuth device authorization flow for tvOS
Displays QR code that user scans with phone to authenticate
Shows user code as fallback for manual entry
Polls for authentication completion and auto-dismisses on success
tvOS has stricter sandbox restrictions than iOS:
UserDefaults-based Config Storage
Preloaded Config in Go SDK
Raw Syscall Tunnel FD Discovery
Added NetBirdTVNetworkExtension target
Separate PacketTunnelProvider.swift with tvOS-specific handling
Extensive logging for debugging via Console.app
Handles "LoginTV" message for device auth flow
Loads config from UserDefaults into SDK memory
isLoginRequired() now verifies session with management server
Previously only checked if config existed (caused post-restart failures)
Shows QR code re-auth flow when OAuth session expires
Added Platform.swift for iOS/tvOS conditional compilation
Shared code uses #if os(tvOS) / #if os(iOS) where needed
Common ViewModels work across both platforms
Summary by CodeRabbit
New Features
Improvements
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.