NAP-MOTION / PR #124
Shell-Mediated Device Motion and Orientation
Full specification from the fetched upstream revision ↗. The document below is rendered from its original Markdown. Follow the latest source on GitHub ↗.
NAP-MOTION
Shell-Mediated Device Motion and Orientation
draft
NAP ID: NAP-MOTION
Domain: motion
Depends: none.
Web binding (NIP-5D): window.napplet.motion; domain presence signals availability.
Description
NAP-MOTION gives napplets device motion and orientation through one shell-mediated contract that behaves the same in every shell. Accelerometer, gyroscope and magnetometer readings belong to the device, not to one napplet. A napplet that reads them natively can infer what the user types or taps in another napplet, fingerprint the device and, from compass heading, narrow the user's location. Under this NAP the runtime reads the sensors and, where the host allows, denies napplets native access. The shell asks the user before a napplet receives motion, then delivers live samples only to a granted napplet that has input focus. Every subscribed napplet still sees availability, permission and focus status.
API Surface
| Operation | Parameters | Result | Wire |
|---|---|---|---|
requestPermission |
absolute (boolean, optional) |
MotionPermissionResult |
motion.permission / motion.permission.result |
state |
none | MotionState |
latest motion.state |
latest |
none | MotionSample or null |
latest motion.sample |
onState |
handler for MotionState |
Subscription handle |
motion.state |
onSample |
handler for MotionSample |
Subscription handle |
motion.sample |
Schemas
MotionPermission accepts "prompt", "granted" or "denied".
MotionUnavailableReason accepts "blocked" or "unavailable".
MotionState fields:
| Field | Required | Type | Notes |
|---|---|---|---|
available |
yes | boolean | false when the runtime cannot read motion sensors. |
reason |
no | MotionUnavailableReason |
Present only when available is false. "blocked" means host policy denied the runtime; "unavailable" means the host has no motion sensors or API. |
permission |
yes | MotionPermission |
This napplet's grant. "prompt" means the user has not decided. |
absolute |
yes | boolean | true when this napplet holds an absolute orientation grant and the host can supply it. |
focused |
yes | boolean | true when this napplet currently receives samples. |
frequency |
yes | number | Samples per second the shell delivers while focused is true. 0 when no samples flow. |
MotionPermissionResult fields:
| Field | Required | Type | Notes |
|---|---|---|---|
permission |
yes | MotionPermission |
Outcome of the request. Never "prompt" unless error is present. |
absolute |
yes | boolean | true when absolute orientation is granted. |
error |
no | text | Present when the request could not be processed. |
Vector3 fields, in metres per second squared:
| Field | Required | Type | Notes |
|---|---|---|---|
x |
yes | number or null |
Device x axis. null when the host cannot measure it. |
y |
yes | number or null |
Device y axis. |
z |
yes | number or null |
Device z axis. |
RotationRate fields, in degrees per second:
| Field | Required | Type | Notes |
|---|---|---|---|
alpha |
yes | number or null |
Rotation around the z axis. |
beta |
yes | number or null |
Rotation around the x axis. |
gamma |
yes | number or null |
Rotation around the y axis. |
Orientation fields, in degrees:
| Field | Required | Type | Notes |
|---|---|---|---|
alpha |
yes | number or null |
0 to 360. Around the z axis. |
beta |
yes | number or null |
-180 to 180. Around the x axis. |
gamma |
yes | number or null |
-90 to 90. Around the y axis. |
absolute |
yes | boolean | true when the frame is Earth-referenced (compass north). false when the frame is arbitrary. |
MotionSample fields:
| Field | Required | Type | Notes |
|---|---|---|---|
timestamp |
yes | number | Runtime time of the reading, in milliseconds. |
interval |
yes | number | Milliseconds between delivered samples. |
acceleration |
yes | Vector3 or null |
Linear acceleration without gravity. |
accelerationIncludingGravity |
yes | Vector3 or null |
Acceleration including gravity. |
rotationRate |
yes | RotationRate or null |
Gyroscope reading. |
orientation |
yes | Orientation or null |
Absolute only when MotionState.absolute is true. |
Axes, units and ranges follow W3C DeviceOrientation Event.
requestPermission(absolute) asks the shell for a grant. With absolute
true it also asks for absolute orientation. It resolves with the outcome. It
returns an existing decision without prompting.
state() returns the latest MotionState. Before the first snapshot it
returns available true, permission "prompt", absolute false,
focused false and frequency 0. It never waits for the wire.
latest() returns the latest MotionSample, or null when none has
arrived or focused is false.
onState(handler) and onSample(handler) call handler with each new
MotionState or MotionSample and return a Subscription handle. Closing the
handle removes the local handler.
The runtime-provided binding sends motion.subscribe before or on first use.
It MAY subscribe when the domain is installed. It MAY send motion.unsubscribe
when the napplet no longer needs motion.
Wire Protocol
motion.* messages use the NIP-5D wire format ({ "type": "domain.action", ...payload }).
| Type | Direction | Payload fields |
|---|---|---|
motion.subscribe |
napplet -> shell | frequency? |
motion.unsubscribe |
napplet -> shell | none |
motion.permission |
napplet -> shell | id, absolute? |
motion.permission.result |
shell -> napplet | id, permission, absolute, error? |
motion.state |
shell -> napplet | available, reason?, permission, absolute, focused, frequency |
motion.sample |
shell -> napplet | timestamp, interval, acceleration, accelerationIncludingGravity, rotationRate, orientation |
Key design notes:
motion.permissionis the only request/result pair. It correlates byid.motion.subscribe,motion.unsubscribe,motion.stateandmotion.sampleare fire-and-forget.frequencyonmotion.subscribeis the requested samples per second. The shell treats it as an upper bound and reports the delivered rate inmotion.state.motion.stateis a complete snapshot, sent right aftermotion.subscribeand then whenever the napplet's view changes.- A napplet without a grant or without focus receives no
motion.sample. Unlike NAP-GAMEPAD's neutral snapshots, the shell sends no zero samples: zero motion is a reading, and sending it would fabricate data.
Examples
Subscribe before any grant:
-> { "type": "motion.subscribe", "frequency": 60 }
<- { "type": "motion.state", "available": true, "permission": "prompt",
"absolute": false, "focused": false, "frequency": 0 }
Request a grant with absolute orientation; the user allows relative only:
-> { "type": "motion.permission", "id": "m1", "absolute": true }
<- { "type": "motion.permission.result", "id": "m1", "permission": "granted", "absolute": false }
<- { "type": "motion.state", "available": true, "permission": "granted",
"absolute": false, "focused": true, "frequency": 60 }
<- { "type": "motion.sample", "timestamp": 18235.4, "interval": 16.7,
"acceleration": { "x": 0.1, "y": -0.2, "z": 0.0 },
"accelerationIncludingGravity": { "x": 0.1, "y": -0.2, "z": 9.8 },
"rotationRate": { "alpha": 1.5, "beta": -0.3, "gamma": 0.0 },
"orientation": { "alpha": 112.4, "beta": 3.1, "gamma": -1.2, "absolute": false } }
Focus moves to another napplet; samples stop:
<- { "type": "motion.state", "available": true, "permission": "granted",
"absolute": false, "focused": false, "frequency": 0 }
The user denies:
-> { "type": "motion.permission", "id": "m2" }
<- { "type": "motion.permission.result", "id": "m2", "permission": "denied", "absolute": false }
Host policy denies sensor access to the runtime:
<- { "type": "motion.state", "available": false, "reason": "blocked",
"permission": "prompt", "absolute": false, "focused": false, "frequency": 0 }
Stop receiving motion:
-> { "type": "motion.unsubscribe" }
Error Handling
motion.permission.result carries error when the shell cannot process the
request, for example a malformed payload or available false; permission
then reports the current grant. Unavailability is reported in state through
available and reason. The shell silently ignores malformed or excessive
subscription messages.
Shell Behavior
- A shell that offers
motionMUST implement this whole contract, whether or not its host can deny native sensors to napplets. Presence of the domain guarantees the operations, schemas and gating in this spec. It does not guarantee isolation from native APIs. - The runtime MUST apply every mechanism its host offers to deny native motion and orientation APIs to napplets, including mechanisms the host may not enforce. Where denial holds, the runtime MUST be the only reader of motion sensors on their behalf.
- Native isolation is best effort. A shell MAY offer
motionon a host that cannot deny native access, and MAY tell the user that napplets there can read sensors outside its grants. - The shell MUST respond to every
motion.permissionwith amotion.permission.resultcarrying the sameid. - The shell MUST NOT prompt the user except in response to
motion.permission. A prompt MUST identify the napplet by its verified identity. - Absolute orientation MUST require a grant distinct from relative motion. The user MAY grant relative motion and refuse absolute orientation.
- The shell MUST key grants to the verified napplet identity. It MAY persist
them under its own policy and MUST let the user revoke them. On revocation
the shell MUST send a new
motion.stateand stop samples. - After a denial the shell SHOULD NOT prompt again until the user changes the
decision. It SHOULD rate-limit
motion.permission. - The shell MUST send
motion.sampleonly to a napplet that holds a grant, holds input focus, and runs while the host surface is visible and focused. - When any of those conditions ends, the shell MUST stop samples at once and
send
motion.statewithfocusedfalseandfrequency0. - The shell MUST send
orientationwithabsolutetrueonly to a napplet holding an absolute grant. Otherwise it MUST send relative orientation ornull. - The shell MUST cap the sample rate and SHOULD NOT exceed 60 samples per
second. It MUST report the delivered rate in
frequency. - The shell SHOULD quantize values to no finer than 0.1 m/s², 0.1 deg/s and
0.1 degrees, as W3C DeviceOrientation recommends. It SHOULD coarsen absolute
alphafurther. - The shell MUST send a
motion.statesnapshot after eachmotion.subscribe, then whenever that napplet's view changes. It SHOULD NOT resend an unchanged snapshot. - The shell MUST report
availablefalsewith areasonwhen the runtime itself cannot read motion sensors. - The shell MUST stop sending
motion.stateandmotion.sampleaftermotion.unsubscribeand when the napplet's session ends. - The shell SHOULD stop reading sensors when no napplet is eligible for samples.
- The shell MAY enforce ACL checks on
motioncapabilities.
Napplet Guidance
- A napplet SHOULD call
requestPermissionfrom a user action, and SHOULD explain why it needs motion before calling it. - A napplet SHOULD treat
focusedfalseas a pause. It SHOULD NOT integrate acceleration or rotation across a gap in samples. - A napplet SHOULD request
absoluteonly when it needs compass heading. - A napplet SHOULD keep pointer and keyboard controls.
- A napplet SHOULD use
motioninstead of native sensor APIs. Native access is denied in some shells and open in others;motionbehaves the same wherever it is present. - The domain is optional. A napplet SHOULD NOT declare
motionas required unless it cannot run without motion.
Web Projection Guidance
This section is projection guidance only. The contract above does not change.
- The shell denies native sensors to napplet frames with the Permissions-Policy
features
accelerometer,gyroscopeandmagnetometer, for exampleallow="accelerometer 'none'; gyroscope 'none'; magnetometer 'none'"on each nappletiframe. CSP cannot govern sensors. - Where the engine enforces these features,
devicemotion,deviceorientationanddeviceorientationabsolutestop firing in the frame without any error, and Generic Sensor classes (Accelerometer,Gyroscope,AbsoluteOrientationSensor, …) report an error onstart(). - Where the engine does not enforce these features, native events still reach
the napplet's realm, and no binding replacement can block them. The shell
still offers
motionwith the full contract; only isolation from native reads is lost. The shell SHOULD set the features on every engine anyway, so isolation starts working wherever enforcement lands. - A binding MAY then replace the event APIs with ones served from
motion.*. Existing libraries then keep working unchanged. Such a replacement SHOULD:- replace
DeviceMotionEvent.requestPermissionandDeviceOrientationEvent.requestPermissionwith operations that sendmotion.permissionand resolve to"granted"or"denied", keeping the native property attributes, name, length and receiver check; - dispatch
devicemotionanddeviceorientationonwindowfor eachmotion.sample, built with the nativeDeviceMotionEventandDeviceOrientationEventconstructors soon*handlers and listeners both work; - dispatch
deviceorientationabsoluteonly whenMotionState.absoluteistrue; - never cause a prompt except through the replaced
requestPermission; and - leave the native APIs untouched when they are not denied.
- replace
- Dispatched events report
isTrustedfalse. Code that rejects untrusted events will not see them. - Generic Sensor classes stay denied. The binding does not replace them.
- Hosts that require a user gesture for sensors, such as iOS Safari, accept the shell's own prompt as that gesture. The napplet's gesture does not need to cross the frame boundary.
Security Considerations
- Motion data is a side channel on user input. Accelerometer and gyroscope readings can reveal taps, keystrokes and PINs entered elsewhere, and can pick up nearby speech. Focus gating, the user grant, the rate cap and quantization all bound this channel.
- Factory calibration error in motion sensors can identify a device across sites. Quantization SHOULD be coarse enough to mask it.
- Absolute orientation exposes compass heading, which aids location inference. It requires its own grant, and the shell SHOULD coarsen it.
- Isolation from native reads depends on the host. Where the runtime denies
every native sensor path, this domain's gating is the only way a napplet reads
motion. Where the host cannot deny them, the gating governs only
motionitself, and native reads bypass it. - This domain grants no device commands. Haptics, batched raw readings, ambient light, proximity and geolocation are out of scope.
Implementations
- None yet.
Changelog
e43dd4f- Introduced NAP-MOTION for grant- and focus-scoped, shell-mediated device motion and orientation samples.e78fbd3- Made the contract hold in every shell that offersmotion; native denial is required where the host supports it, but no longer gates offering the domain.41b78f5- Required shells to apply every available native-denial mechanism while treating native isolation as best effort.