npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@feelrobotics/feel-apps-api

v2.0.0

Published

Real-time haptic feedback and device control library for Feel Robotics applications

Downloads

674

Readme

feel-apps-api

JavaScript/TypeScript library for real-time haptic feedback and device control in Feel Robotics applications.

Overview

feel-apps-api synchronizes video playback with haptic subtitle events and communicates with Feel haptic devices in real time over a Socket.IO connection to the Feel Exchange Center (FEC).

Installation

npm install @feelrobotics/feel-apps-api

Build

npm run build

The library outputs ESM, CommonJS, and IIFE bundles. The IIFE global is $feel.

| Endpoint | URL | |---|---| | API | api.feel-app.com | | Subtitles | api-subtitles.feel-app.com | | FEC | fec.feelme.com |

Quick Start

import { init, destroy, apps, subs } from '@feelrobotics/feel-apps-api'

// Initialize with all tokens
await init(feelSubsToken, fecToken, userId, roomName)

// Load subtitles for a video
await subs.load(videoId, subtitlesId, externalUserId)

// Wire up the HTML5 video element
video.addEventListener('play',       () => subs.play(video.currentTime))
video.addEventListener('timeupdate', () => subs.timeupdate(video.currentTime))
video.addEventListener('pause',      () => subs.stop())

// Clean up when done
destroy()

Modes

| Mode | Function | Use case | |---|---|---| | Full | init() | Web app with device control + haptic subtitles | | Slider | initSlider() | Direct intensity control only, no subtitle support |

API Reference

Top-level

init(feelSubsToken, fecToken, userId, roomName, tokenRefresh?): Promise<void>

Full initialization. Connects to FEC, starts device monitoring, and sets up subtitle playback once a device connects. The returned promise resolves when the FEC socket connects (not when a device joins).

initSlider(fecToken, userId, roomName, tokenRefresh?): Promise<void>

Device control without subtitle support. Use apps.playSubtitle(percent, 0, []) to send haptic intensity directly.

TokenRefreshOptions

Both init and initSlider accept an optional tokenRefresh object to keep the FEC connection alive past the token's 24 h TTL. The token is refreshed every 12 h.

import { init, type TokenRefreshOptions } from '@feelrobotics/feel-apps-api'

await init(feelSubsToken, fecToken, userId, roomName, {
  fetchToken: async () => {
    const res = await fetch('/api/refresh-fec-token')
    return (await res.json()).fec_token
  },
  onTokenError: (err) => {
    // Called when fetchToken fails — e.g. force logout or show an error
    console.error('Token refresh failed:', err)
  },
})

| Field | Type | Required | Description | |---|---|---|---| | fetchToken | () => string \| Promise<string> | yes | Returns a fresh FEC JWT | | onTokenError | (err: Error) => void | no | Called when fetchToken fails |

setServers(servers)

Override default API endpoints. Must be called before init.

setServers({ apps: 'https://...', subs: 'https://...' })

destroy()

Disconnect the socket and reset all internal state. Call this before re-initialising.

setDebug(enabled: boolean)

Enable or disable debug logging to the console.


apps module

apps.playSubtitle(percentValue, positionMsec, subtitles)

Send haptic data to connected devices.

  • Pass a non-empty subtitles array for subtitle-synced haptic playback.
  • Pass an empty array [] for direct intensity control (slider mode).
// Subtitle playback — called automatically by the subs module
apps.playSubtitle(75, 1200, subtitleEntries)

// Direct slider control
apps.playSubtitle(percent, 0, [])

apps.getMobileAppLaunchUrl(requestToken): string

Returns a deep link URL to launch the Feel mobile app for pairing.

const url = apps.getMobileAppLaunchUrl(tokens.authToken)
// "feelapp://authorize?token=..."

apps.status.subscribe(callback) / apps.status.unsubscribe(callback)

Listen for device connection status changes.

const onStatus = ({ online, devices }) => { ... }
apps.status.subscribe(onStatus)

// Clean up
apps.status.unsubscribe(onStatus)

apps.data.subscribe(callback) / apps.data.unsubscribe(callback)

Listen for haptic position messages sent by peer devices.

const onData = (percent, deviceName) => { ... }
apps.data.subscribe(onData)

// Clean up
apps.data.unsubscribe(onData)

subs module

subs.load(videoId, subtitlesId, externalUserId?, channel?, options?): Promise<void>

Fetch subtitle data for a video. Waits for a device to connect before loading if none is connected yet. Supports AbortSignal for cancellation.

const controller = new AbortController()
await subs.load(videoId, subtitleId, userId, '', { signal: controller.signal })

// Cancel a pending load
controller.abort()

subs.play(currentPosSec)

Start subtitle playback from the given video position (in seconds).

subs.timeupdate(currentPosSec)

Update the current video position. Call on every timeupdate event. Handles seek detection and buffering automatically.

subs.stop()

Stop playback and reset the device to neutral intensity.

subs.onSubtitleEvent(callback) / subs.offSubtitleEvent(callback)

Subscribe to subtitle fire events. The callback receives percentValue (0–100).

const onHaptic = (percent) => setIntensity(percent)
subs.onSubtitleEvent(onHaptic)

// Clean up
subs.offSubtitleEvent(onHaptic)

Development

npm run dev        # watch mode
npm test           # run tests
npm run lint       # lint with Biome
npm run check      # lint + format check
npm run check:fix  # auto-fix lint and format issues

License

MIT © Feel Robotics