PlatformGrantDelegate
Platform-specific delegate for grant operations.
Permission Types:
AppGrant: Built-in permissions (CAMERA, LOCATION, etc.)
RawPermission: Custom platform-specific permissions
Implementations:
Android (
androidMain): Uses ActivityCompat, ContextCompat, Activity Result APIiOS (
iosMain): Uses AVFoundation, CoreLocation, CoreBluetooth, EventKit, etc.
iOS Platform Grant Delegate — Production-ready implementation.
Dispatches permission operations to dedicated PermissionHandler instances, each of which owns exactly the native framework imports it needs. This ensures that unused permission frameworks are not statically linked into the app binary, preventing Apple App Store rejections caused by undeclared usage description keys.
See docs/ios/APPLE_FRAMEWORK_LINKING_ISSUE.md for the full architectural rationale.
Supported permissions (17 total):
Camera / Microphone — AVFoundation
Gallery / Storage — Photos
Location (WhenInUse / Always) — CoreLocation
Notification — UserNotifications
Contacts — Contacts
Calendar — EventKit
Motion — CoreMotion
Bluetooth — CoreBluetooth (via BluetoothManagerDelegate)
Schedule Exact Alarm — GRANTED unless the app declares NSAlarmKitUsageDescription; see ExactAlarmHandler for why AlarmKit (iOS 26+) cannot be answered from here
Thread Safety:
mutexMap is protected by mapsMutex
statusCacheMap uses per-entry locking via mapsMutex
All synchronous iOS framework calls dispatch to the main thread via runOnMain
Notification status is checked via its own async path to avoid nested dispatch
JVM desktop delegate — deliberately minimal. grant-core has no macOS/Windows framework dependency and never will; every GrantPermission resolves through DesktopPermissionHandlerRegistry, and a lookup miss reports GrantStatus.DENIED_ALWAYS with a logged reason rather than a fabricated GrantStatus.GRANTED. The grant-desktop module (opt-in) is what actually populates the registry with a real macOS TCC bridge — see DesktopPermissionHandlerRegistry's KDoc and ROADMAP.md v2.6.0.
store is unused here for the same reason it's unused on iOS/web: the OS's own privacy database is the durable "have we asked before" record.
Browser Platform Grant Delegate — shared by the js and wasmJs targets via webMain.
Backed by the real browser consent APIs — getUserMedia, Notification.requestPermission, Geolocation.getCurrentPosition — never a stub. store is unused: the browser itself is the durable "have we asked before" record (a denied getUserMedia call stays denied across page loads without any help from Grant), so there is nothing for a local store to add here.
Only four AppGrant values have a real browser equivalent: AppGrant.CAMERA, AppGrant.MICROPHONE, AppGrant.LOCATION, AppGrant.NOTIFICATION. Every other value — gallery, contacts, calendar, bluetooth, motion, and the rest — has no standard Web Platform API to back it. Those resolve to GrantStatus.DENIED_ALWAYS with a logged reason, matching the existing convention (hasInfoPlistKey on iOS) of failing safe and loud rather than fabricating a status the platform cannot actually provide. RawPermission follows the same rule: there is no generic "request an arbitrary permission" primitive in a browser.
Functions
Opens the app's settings page in iOS Settings. Logs a warning if the Settings URL cannot be resolved (e.g., App Extensions, App Clips).
Opens System Settings' Privacy & Security pane. Unlike the browser target, macOS does have a real, documented URL scheme for this (x-apple.systempreferences:com.apple.preference.security) — but only grant-desktop can invoke it (it needs NSWorkspace, an AppKit/macOS-only API grant-core's jvmMain does not and should not depend on, since this module also has to compile on any JVM, not just macOS). Delegates to DesktopPermissionHandlerRegistry.settingsOpener when one is registered; logs instead of silently doing nothing when none is.
No standard way for a web page to open the browser's own site-permission settings — unlike a native OS Settings app, there is no cross-browser URL or API for this. Logs instead of silently doing nothing, so an integrator sees why nothing happened rather than filing a "openSettings() is broken on web" bug.