craft.permissions.check and .request are typed, callable, never throw, and answer "undetermined" for everything but notifications.
In dist/native/runtime/craft-bridge.js (0.2.663):
craft.permissions = Object.assign({}, craft.permissions, {
check: permission => permission === 'notifications' ? craft.push.getPermissionStatus() : Promise.resolve('undetermined'),
request: permission => permission === 'notifications' ? craft.push.requestPermission() : Promise.resolve('undetermined'),
openSettings: () => craft.push.openSettings().then(() => {}),
})Three separate problems, in order of how much they cost:
1. It answers confidently and wrongly. A screen asking await craft.permissions.check('location') gets 'undetermined' whether location is granted, denied or never asked. Nothing throws and nothing warns, so the only way to find out is to put a build on a device and watch it behave as though a granted permission were not.
This is worse than a missing API. A missing one fails loudly at the call site; this one returns a plausible value, and 'undetermined' is the single answer most likely to send a screen down an "ask the user" path that then does nothing.
2. Object.assign means it overrides a real implementation rather than standing in for one. The stub is the last argument, so it wins over anything already on craft.permissions. If a host grows a Permissions module tomorrow, this line silently discards it — the shim outlives the gap it was filling.
3. It only exists under the push capability. The whole block sits inside if (capabilities.has('push')), so an app that does not declare push has no craft.permissions at all, and one that does gets the stub for location, camera and the rest. Neither is a property of permissions.
Why this is hard to find from the outside
iOS does not refuse startUpdatingLocation when location is denied. It accepts the call and then delivers nothing. So a recording screen that asked permissions.check('location'), believed 'undetermined', and started anyway got active: true back from the host and sat on a running clock with a distance stuck at zero — a screen that looks like it is working. Measured on a device: forty-three seconds of "moving", not one sample.
We have since had to write "do not trust this" into two screens' comments, and the real check is now "did any fix arrive", which is a behavioural proxy for a question the API claims to answer.
What would help, in preference order
- Implement it against a host Permissions module. craft-native has none either (no
"Permissions"case inCraftNativeActions.swiftas of 0.0.293), so this is two halves of one gap; happy to file the other half there if useful. - Failing that, reject for permissions the bridge cannot answer, so a caller finds out at the call site rather than on a device.
- At minimum, stop the
Object.assignfrom clobbering a host implementation, and move the block out from under the push capability.
A craft.capabilities()-style answer for "can this host answer permission questions at all" would also let a screen branch honestly instead of guessing.
Versions
stx 0.2.663, craft-native 0.0.293, iOS 26.5 simulator and device.