OS Service Hook for the FiveM NUI transport.
core: true only — exported from @mica/sdk/core, not @mica/sdk. A core: false
add-on runs in a sandboxed iframe with no NUI to bridge to; its build swaps fetchNui for
the postMessage twin in sdk/host/iframe/fetchNui.ts instead of this hook.
This used to return the imported functions directly, which made destructuring at module scope quietly wrong:
const { fetchNui } = useNuiBridge(); // captures the transport as it is *right now*
That snapshot is taken when the module is imported. A test that later installs a spy on the transport never sees it, so the store goes on talking to the real mock registry while the test asserts against its own stubs — and the failure reads as wrong fixture data rather than as a stale binding. It cost two passes to recognise while migrating Blabber's store, which is a good sign it would cost an add-on author more.
A wrapper makes the wrapper the stable thing and re-reads the module binding on every call. So destructuring is now safe, at module scope or anywhere else — which is the point: the ergonomic move and the correct move should be the same one. Telling every app "do not destructure this" would be a rule waiting to be broken, and the SDK is exactly the layer that should absorb that rather than export it.
The cost is one function call per transport call, against a round trip to the game.
@mica/sdk/core— the surface only acore: trueapp may import.useNuiBridgeis the raw transport: any registered NUI callback, by name. An add-on with it can reach everything every other permission guards, so it is not on@mica/sdk—boundary.test.tsrefuses it to anythingcore: false. The specifier is unresolvable out-of-tree for a Store-installed bundle, and since MICA-16 Step 4 that is also a runtime refusal: acore: falsebundle runs in a sandboxed iframe with no NUI at all, only thepostMessageprotocol insdk/host/iframe/. In-process,useNuiBridgeis gated only by the staticcore:-only import check; it has no host-protocol permission row and is notguarded().useCaptureZoomBoostis here for the same reason, not because it needs the transport: it is a lever on the shell's own rendering with no legitimate reason for a sandboxed add-on to pull it.NowPlayingCardis the third kind: not dangerous, not general. It is the transport for the phone's own player, drawn by the Music app and by the notification shade, and it is the reason Music iscore: trueat all — the player is hardware. Putting it on the public@mica/sdkwould make a screen-sized component with one subject into an API commitment to every add-on; putting it here keeps it reachable by the two things that draw it. It grants nothing on its own: it is presentational, and the stores it renders from areuseMusic()'s, which is permission-gated as it always was.useCapabilities(MICA-169) is the server'srequiresanswer, for the phone's own catalog to explain an app it would refuse. Harmless to disclose, but with no add-on asking for it —useCapabilities.tshas the reasoning and why it has no facet.The seed-theming set —
DEFAULT_SEED,sanitizeSeed,seedFromRgbString,buildSchemes,cssVarBlock,backgroundForScheme— is the fourth kind, and it is here for cost rather than for danger. MICA-187 decided where it publishes, with the number from MICA-181 in hand:buildSchemesdrags@material/material-color-utilitiesinto whatever bundle can reach it, about 21 kB gzipped, and Rollup does not shake it back out of acore: falseadd-on that never calls it.@mica/sdk/corehas no alias invite.addon.config.tsat all, so nothing published here can enter an add-on bundle — MICA-187 rebuilt all four add-ons before and after this export landed and the bundles were byte-identical. The only consumer in the tree,NowPlayingCard, is already on this entry point, so a core app deriving a scheme from a different seed than the phone's pays for an engine the shell has loaded anyway. An add-on theming itself to the phone gets the current scheme throughuseTheme().schemeStore, which is what it actually needs; deriving one from an arbitrary seed stays off@mica/sdkuntil a real add-on asks and the 21 kB is weighed against it.host/seam/theme.tskeeps the longer account.