Skip to content

feat: (Capacitor 9) access local network Api Level 37 - #8624

Closed
fabiomartino wants to merge 4 commits into
ionic-team:nextfrom
fabiomartino:feat/ACCESS_LOCAL_NETWORK-9
Closed

fabiomartino wants to merge 4 commits into
ionic-team:nextfrom
fabiomartino:feat/ACCESS_LOCAL_NETWORK-9

Conversation

@fabiomartino

@fabiomartino fabiomartino commented Sep 23, 2026 •

Copy link
Copy Markdown

Starting in Android 17 (API 37), local-network addresses are blocked by
default for apps targeting SDK 37+, with ACCESS_LOCAL_NETWORK as a runtime
permission. Without framework support, apps using CapacitorHttp against
LAN devices fail with timeouts on API 37+.

This change:

  • declares android.permission.ACCESS_LOCAL_NETWORK in the core library
    manifest (merged into every app, safely ignored on older SDKs);
  • adds a version-aware PermissionHelper gate (isPermissionEnforced, with
    string literal + literal API 37 constant so the compile SDK can stay
    below 37);
  • reports the permission as GRANTED on SDKs below 37 and only runs the
    manifest check / DENIED caching on enforced SDKs, in both
    Bridge.validatePermissions() and Bridge.getPermissionStates();
  • adds a first-party LocalNetwork plugin exposing the standard
    checkPermissions()/requestPermissions() flow from JavaScript, with no
    app-side native code required;
  • adds unit tests covering the API 37 boundary in PermissionHelper, the
    Bridge permission state/validation behavior, and the LocalNetwork plugin
    registration and pre-37 granted flow.

iOS

iOS intentionally ships no native implementation for this plugin: the system
does not expose a runtime permission API for local-network access, so
checkPermissions()/requestPermissions() go through the web fallback and
report granted. The one-time prompt is driven by
NSLocalNetworkUsageDescription in Info.plist the first time the app
communicates with a LAN device.

Branch targeting

Targeted at next (9.x): the ACCESS_LOCAL_NETWORK capability is a new
platform feature tied to API 37+, so the plugin lands in the pre-release
line. The stable 8.5.2 patch line carries the grant-behavior fix separately.

Closes #8613

@fabiomartino
fabiomartino marked this pull request as ready for review September 24, 2026 07:37
@fabiomartino fabiomartino changed the title Feat/access local network 9 feat: Cap 9 - access local network Sep 26, 2026
@fabiomartino fabiomartino changed the title feat: Cap 9 - access local network feat(cap9): access local network Sep 26, 2026
@moose4lord

Copy link
Copy Markdown

Fabio, I implemented you PR in my dev env and everything seemed to work ok, except when I added the code to check local network permission:

if (permState.localNetwork === 'granted') {
  const response = await CapacitorHttp.get(options);
}

I got this error after running 'ionic capacitor sync android --prod':
Property 'localNetwork' comes from an index signature, so it must be accessed with ['localNetwork'].

Not sure what that means, but this tweak fixed it:

if (permState['localNetwork'] === 'granted') {
  const response = await CapacitorHttp.get(options);
}

I'll leave it up to you to decide if this is a bug that needs to be fixed, or if using the permState['localNetwork'] syntax is proper.

The PR introduced an index-signature PermissionResult type, which
breaks dot access (TS4111) for consumers compiling with the
noPropertyAccessFromIndexSignature option. Declare the localNetwork
property explicitly on a dedicated LocalNetworkPermissions interface
and drop the open index-signature type from the public API.
@fabiomartino

fabiomartino commented Sep 28, 2026 •

Copy link
Copy Markdown
Author

Fabio, I implemented you PR in my dev env and everything seemed to work ok, except when I added the code to check local network permission:

if (permState.localNetwork === 'granted') {
  const response = await CapacitorHttp.get(options);
}

I got this error after running 'ionic capacitor sync android --prod': Property 'localNetwork' comes from an index signature, so it must be accessed with ['localNetwork'].

Not sure what that means, but this tweak fixed it:

if (permState['localNetwork'] === 'granted') {
  const response = await CapacitorHttp.get(options);
}

I'll leave it up to you to decide if this is a bug that needs to be fixed, or if using the permState['localNetwork'] syntax is proper.

Nice catch, and thanks for testing it on device.
That error is a TypeScript strict-mode behavior, not a runtime bug: Property 'localNetwork' comes from an index signature (TS4111) comes from noPropertyAccessFromIndexSignature, which is enabled by default under strict. In the first version the plugin returned a generic { [key: string]: PermissionState }, and an index signature can't prove localNetwork exists statically, so strict consumers require bracket access.
At runtime both forms are identical, which is why permState['localNetwork'] works.

You don't need to keep the workaround: we already included the proper fix in this PR (commit 1be88f7, "fix(core): return a closed LocalNetworkPermissions type"). The plugin now returns a LocalNetworkPermissions interface with an explicit localNetwork: PermissionState property, so permState.localNetwork compiles under any tsconfig, no brackets needed.
Just pull the latest branch.

@moose4lord

Copy link
Copy Markdown

Your latest commit fixed the "Property 'localNetwork' comes from an index signature, so it must be accessed with ['localNetwork']" issue for me. Now the "permState.localNetwork" syntax works fine. Thanks for your help.

Any idea how to get one of the maintainers to evaluate your PR and hopefully merge it?

@moose4lord

This comment was marked as abuse.

@fabiomartino fabiomartino changed the title feat(cap9): access local network feat: (Capacitor 9) access local network Api Level 37 Oct 9, 2026
@eric-horodyski

Copy link
Copy Markdown
Member

Thanks for the contribution! We are doing investigation into this before committing this PR as an overall solution.

@eric-horodyski

Copy link
Copy Markdown
Member

Hi @fabiomartino, thank you for the feature and pull request. We’re going to have to respectfully decline adding this as a core plugin bundled within Capacitor, for a few reasons:

  1. Plugins that we bundle within the main Capacitor repository either are difficult to implement outside of it (HTTP), or cover a use case large enough that developers would rarely want to replace or fork the code.
  2. Adding a new plugin this close to the Capacitor 9 launch would put an undue burden on our team, adding scope we can’t yet account for.
  3. The change adds a permission declaration to the core manifest, which merges into every app that installs Capacitor, including the many apps that never access the local network. That’s a broader footprint than most users would want.

I do think this is excellent ground for a standalone plugin, and I’d love to see your work become a community-maintained plugin. I’d be glad to help open a space for it at https://github.com/capacitor-community. How does that sound?

@moose4lord

Copy link
Copy Markdown

That's disappointing. I thought Fabio's fix was well thought out. I think it would be a good idea to mention this issue in the Capacitor 9 release notes, otherwise people are going to be scratching their heads when they target SDK 37 and local network access stops working.

Fabio, thanks for your help. if you decide to not go the capacitor-community route, I'd be interested to hear your thoughts about implementing a fix outside of Capacitor.

@eric-horodyski

Copy link
Copy Markdown
Member

@moose4lord, completely understand. We’ll document this in the Capacitor 9 upgrade guide, including the steps for apps that target SDK 37.

I still recommend a standalone plugin for this. This change touches the Bridge’s permission validation and state code, which every plugin that declares permissions relies on, and it special-cases one permission by API level. I’d like to see whether that logic can live in the plugin, or be expressed as a general per-permission rule rather than a hard-coded branch in core.

@fabiomartino, thanks again for this work. I’m happy to help in any way I can, whether that’s reviewing a community plugin or talking through an approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants