fix(macos): reliable Local Network permission denial detection and re-check
- Replace broad POSIX error checks (EACCES/EPERM/ENETDOWN) with the canonical kDNSServiceErr_PolicyDenied DNS error in the NWBrowser state handler, matching the pattern used by Expo, Pulse, Strongbox, and WLED. Detect denial in both .failed and .waiting states. - Add checkLocalNetworkAccess(host:port:) — a read-only NWConnection probe (Sequel-Ace pattern) that checks NWPath.unsatisfiedReason == .localNetworkDenied without triggering a new system prompt. Useful for confirming denial against a specific destination before attempting to connect. - In _onConnect, after the prompt resolves to Denied, confirm with checkLocalNetworkAccess against the target host. If confirmed, abort the connect attempt and show a non-modal snackbar with an 'Open System Settings' action that deep-links to Privacy_LocalNetwork. Previously the app would proceed to connect, fail with PermissionDenied, and surface a redundant in-app modal. - Drop the now-orphaned _openIosAppSettings helper and _iosPlatformChannel constant (the only caller was the removed in-app permission dialog). - Add unit tests for checkLocalNetworkAccess covering outbound MethodCall arguments and state parsing for Granted/Denied. Trace: SRS-300.
This commit is contained in:
@@ -301,6 +301,61 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'SWE4-UV / SRS-300: checkLocalNetworkAccess() emits outbound '
|
||||
'checkLocalNetworkAccess MethodCall with host and port arguments',
|
||||
() async {
|
||||
final svc = MacOSPermissionsService(channel: channel)..start();
|
||||
|
||||
outgoingResponder = (call) async {
|
||||
if (call.method == methodCheckLocalNetworkAccess) {
|
||||
return 'Denied';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
final result = await svc.checkLocalNetworkAccess(
|
||||
host: '192.168.1.42',
|
||||
port: 9987,
|
||||
);
|
||||
|
||||
final calls = outgoingCalls
|
||||
.where((c) => c.method == methodCheckLocalNetworkAccess)
|
||||
.toList();
|
||||
expect(calls, hasLength(1));
|
||||
final args = calls.single.arguments as Map;
|
||||
expect(args['host'], '192.168.1.42');
|
||||
expect(args['port'], 9987);
|
||||
expect(result, MacOSLocalNetworkState.denied);
|
||||
expect(svc.localNetworkState.value, MacOSLocalNetworkState.denied);
|
||||
svc.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'SWE4-UV / SRS-300: checkLocalNetworkAccess() parses Granted and updates '
|
||||
'localNetworkState',
|
||||
() async {
|
||||
final svc = MacOSPermissionsService(channel: channel)..start();
|
||||
|
||||
outgoingResponder = (call) async {
|
||||
if (call.method == methodCheckLocalNetworkAccess) {
|
||||
return 'Granted';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
final result = await svc.checkLocalNetworkAccess(
|
||||
host: 'ts.example.com',
|
||||
port: 9987,
|
||||
);
|
||||
|
||||
expect(result, MacOSLocalNetworkState.granted);
|
||||
expect(svc.localNetworkState.value, MacOSLocalNetworkState.granted);
|
||||
svc.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'SWE4-UV / SysRS-166: requestNotifications() emits outbound '
|
||||
'requestNotifications MethodCall; returns the platform response',
|
||||
|
||||
Reference in New Issue
Block a user