Activity and voice
Declare activity to know whether Fluxer is focused, shown and in use, and voice to know whether the user is in a voice call and in which channel. Both are read-only and need FluxPlugs 0.5.0-beta.1 or later.
Follow activity
Section titled “Follow activity”cleanup.push( await api.activity.onChange( (state) => { const inUse = state.focused && state.visible && !state.idle; api.logger.debug("Activity", { inUse }); }, { idleAfterSeconds: 600 }, ),);ActivityState has three flags:
| Flag | True when |
|---|---|
| focused | Fluxer’s window has focus |
| visible | Fluxer’s window is shown: not minimized or hidden to the tray |
| idle | No key press, click, scroll, touch or mouse movement in Fluxer for idleAfterSeconds |
idleAfterSeconds is a whole number from 60 to 3600 and defaults to 300. Each call and subscription has its own, so two plugins can wait different times. activity.current(options?) answers once; onChange calls the handler with the state now, then after every change. While your handler is busy, only the newest waiting state is delivered.
Focusing Fluxer’s window counts as input. Input inside plugin frames, including your own view, isn’t seen, so a user working in a plugin view can turn idle.
Follow voice
Section titled “Follow voice”cleanup.push( await api.voice.onChange((voice) => { if (voice.state === "connected" && voice.channelId) api.logger.info("In a call", { channel: voice.channelId, community: voice.guildId ?? "direct messages", }); }),);VoiceState has state (disconnected, connecting or connected) and, while in a channel, channelId and guildId. guildId is missing for calls in direct messages. voice.current() answers once; onChange works like the activity one.
FluxPlugs reads the voice connection panel Fluxer shows above the user area. While Fluxer’s layout hides that area, as in the full-window call view or the mobile layout, the last known state stands. voice_unavailable means the runtime can’t read voice on this page; skip the feature.
Measure time
Section titled “Measure time”Timers slow down while Fluxer is in the background, and stop while the computer sleeps. Don’t add up timer ticks: note Date.now() when a state starts and add the difference when it ends, or on a regular tick. Cap a single step, so a long gap after sleep doesn’t count as use.
let activeSince: number | undefined;let activeMs = 0;cleanup.push( await api.activity.onChange((state) => { const now = Date.now(); if (activeSince !== undefined) activeMs += Math.min(now - activeSince, 60_000); activeSince = state.focused && state.visible && !state.idle ? now : undefined; api.logger.debug("Active so far", { seconds: Math.round(activeMs / 1000) }); }),);When idle turns on, the user already stopped idleAfterSeconds ago; subtract that wait if it shouldn’t count.
Privacy
Section titled “Privacy”Plugins get the three activity flags, never timestamps, keys or what was clicked. FluxPlugs listens for input with passive listeners on Fluxer’s window and keeps only when the last input happened. Voice gives ids only: no names, no other people in the call and no audio.
Both still tell a plugin when the user is at their computer and which calls they join. With network, a plugin could send that to its listed hosts, and the enable prompt warns about it. Keep such data on the computer unless the feature needs it, and say so in your description.
| Error | Meaning |
|---|---|
| voice_unavailable | The runtime can’t read the voice connection on this page |
| capability_denied:activity | The manifest doesn’t declare activity, or it isn’t confirmed |
| capability_denied:voice | The manifest doesn’t declare voice, or it isn’t confirmed |
