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

anear-js-api

v2.3.1

Published

Javascript Developer API for Anear Apps

Downloads

2,145

Readme

Anear JavaScript API (JSAPI)

The Anear JavaScript API is a runtime SDK that enables app developers to create real-time interactive events without needing to understand the underlying complexity of Ably.io interactions, event lifecycle management, and participant/spectator coordination.

Overview

The anear-js-api is the server-side Node.js runtime for Anear applications. It manages real-time communication via Ably.io, coordinates participant presence, handles event lifecycle, and renders dynamic UI templates. App developers write XState state machines that define their application logic, and the JSAPI handles all the infrastructure concerns.

Key Philosophy

The Anear platform is designed for hyperlocal, face-to-face interaction. Anear Apps are experiences designed for in-person participants who share the same physical space. The core principle is to leverage technology to enhance, not replace, real-world social dynamics. Participants are typically in the same room, fostering an environment of direct, face-to-face interaction.

System Purpose

The anear-js-api abstracts away:

  • Ably.io channel management and messaging
  • Event lifecycle state transitions (createdannounceliveclosed)
  • Participant presence tracking and coordination
  • Display rendering and template compilation
  • Asset management (CSS, images) and CDN uploads
  • Timeout orchestration for allParticipants action windows
  • Reconnection handling and error recovery

Architecture

State Machine Hierarchy

AnearCoreServiceMachine (Root)
├── AnearEventMachine (Per Event)
│   └── AppEventMachine (Developer's App Logic)

Core Components

AnearCoreServiceMachine

  • Purpose: Highest-level parent state machine managing realtime messaging and event lifecycle
  • Responsibilities:
    • Initialize Ably.io realtime messaging
    • Manage multiple concurrent events via anearEventMachines object
    • Handle app data fetching and asset uploads (CSS, images)
    • Load and compile PUG templates
    • Listen for CREATE_EVENT messages from backend via Ably REST API
  • Key States: waitForContextUpdatefetchAppDataWithRetryinitRealtimeMessagingwaitAnearEventLifecycleCommand

AnearEventMachine (AEM)

  • Purpose: Manages individual event lifecycle and participant coordination
  • Responsibilities:
    • Handle event lifecycle states: createdannounceliveclosed
    • Route messages between participants and app logic
    • Manage participant presence (enter/leave/exit)
    • Coordinate display rendering for all channels
    • Handle allParticipants timeout orchestration
  • Key States: registerCreatoreventCreatedannouncelivecloseEvent

Breaking vNext: APM Removed

  • Participant-machine orchestration is removed from JSAPI.
  • AEM now manages participant private channel lifecycle and private display publishing directly.
  • JSAPI no longer emits PARTICIPANT_TIMEOUT; participant-local timeout UX is owned by browser runtime (<anear-timeout>).

Separation of Concerns: JSAPI vs. AppM

A key architectural principle is the clear separation of concerns between the Anear JSAPI (the "engine") and the App Event Machine (the "application").

JSAPI (AEM): The Engine Room

The JSAPI is responsible for all the underlying infrastructure and communication mechanics. Its concerns are purely technical:

  • Event Lifecycle & State: Manages the low-level state transitions of an event (createdannounceliveclosed) and the lifecycle of participants.
  • Real-time Communication: Handles all Ably channel setup, messaging, presence events, and connection state.
  • Orderly Operations: Ensures smooth, reliable startup and shutdown of all services and participant connections.
  • Notifier: The JSAPI acts as a notifier. It informs the AppM of important events (PARTICIPANT_ENTER, PARTICIPANT_DISCONNECT, ACTION, ACTIONS_TIMEOUT) but does not decide what these events mean for the application.

AppM: The Application & User Experience

The AppM developer is responsible for everything related to the specific event's logic and what the user sees:

  • Game/Event Logic: Controls the flow, rules, and state of the interactive event.
  • User Experience (UX/UI): Defines what the user sees at every stage via meta display properties in its state nodes. All display content is the AppM's responsibility.
  • Decision Maker: The AppM is the decision-maker. When the JSAPI notifies it of an event (e.g., an ACTION/ACTIONS_TIMEOUT), the AppM decides what to do. Should the game end? Should the participant be removed? Should the state change? That is application-level logic.

This strict separation allows AppM developers to focus entirely on their application logic without needing to manage the complexities of real-time infrastructure.

Getting Started

Installation

npm install anear-js-api

Basic Setup

// index.js
const { AnearService } = require('anear-js-api')
const MachineFactory = require('./StateMachine')

// Starts and instantiates the service
AnearService(MachineFactory)

Required Environment Variables

ANEARAPP_API_KEY=your_api_key
ANEARAPP_API_SECRET=your_api_secret
ANEARAPP_API_VERSION=v1
ANEARAPP_API_URL=http://api.lvh.me:3001/developer  # Optional, for local development

Local Dev Quickstart (no Docker)

To keep development behavior close to today:

  1. Keep runtime asset publishing enabled (default):
unset ANEARAPP_SKIP_RUNTIME_ASSET_SYNC
  1. Start your app process normally:
npm install
node index.js
  1. If your ANAPI environment is not Rails development, set:
export ANEARAPP_SINGLE_LATEST_VERSION=true

This preserves a single mutable latest app version for iterative development.

Developer API Authentication

anear-js-api communicates with the ANAPI Developer API (/developer/v1/* endpoints) using JWT authentication:

  1. Challenge (credentials → JWT): On startup, exchanges developer credentials (api_key, secret) for a short-lived JWT via POST /developer/v1/sessions
  2. Authenticated requests: All subsequent requests use Authorization: Bearer <auth_token>
  3. Expiry: JWT expiry is enforced by ANAPI (configured via developer_api_token_expiration_interval_hours)

Event Lifecycle

Anear events follow a specific lifecycle managed by the Anear Event Machine (AEM):

  1. Created: Event is created in the AEM but not yet visible. No participants can join yet.
  2. Announced: Developer calls anearEvent.announceEvent(). Event becomes visible to potential participants. Critical: This step is required for participants to join!
  3. Live: Developer calls anearEvent.startEvent(). Event is actively running.
  4. Complete/Cancelled: Developer calls anearEvent.closeEvent() or anearEvent.cancelEvent(). Critical: You MUST explicitly call one of these methods before your AppM reaches a final state.

Your AppM state names are completely independent of these AEM states. You can start your AppM in any state you want - the AEM lifecycle runs in parallel.

Channel Architecture

Ably.io Channels

  • eventChannel: Event control messages
  • actionsChannel: Participant presence events + ACTION clicks
  • participantsDisplayChannel: Group display messages for all participants
  • spectatorsDisplayChannel: Display messages for all spectators
  • privateChannel: Individual participant private displays (per participant)

Message Flow

  1. Presence Events: Ably presence API → actionsChannel → XState events
  2. Action Events: Participant clicks → actionsChannel → App logic
  3. Display Events: App state transitions → AppMachineTransition → Channel rendering

Action Payload Tokenization

JSAPI tokenizes anear-action payloads for participant-private displays to prevent client-side payload inspection/tampering.

Render-time behavior

  • App templates still use action(...) in Pug.
  • For private participant renders, DisplayEventProcessor replaces action JSON with short opaque tokens.
  • JSAPI stores an ephemeral lookup map per participant and render cycle:
    • participantActionLookup[participantId][token] = { APP_EVENT: payload }
  • The lookup map is replaced on each render cycle.

Action-ingest behavior (AEM)

When an ACTION is received on the actions channel:

  1. AEM tries token resolution from participantActionLookup.
  2. If token lookup fails, AEM accepts JSON payloads marked as trusted ABR client payloads (__anearClient: true, forms also include __anearForm: true).
  3. Any unrecognized/unmarked payload is dropped and not forwarded to AppM.

Forms

anear-form submissions remain JSON-based (they carry user-entered values). ABR marks form payloads with __anearForm: true and __anearClient: true; AEM strips these markers before forwarding payload to AppM.

Security model

  • Tokenized button actions remove app-specific payload details from client-visible markup.
  • Forged/replayed unknown tokens are rejected by default.
  • AppM guards are still required for domain correctness (turn validity, ownership checks, ranges, etc.).

Participant Presence Events

The JSAPI handles participant lifecycle through presence events:

| Event | When It Fires | AppM Receives | |-------|---------------|---------------| | PARTICIPANT_ENTER | New participant joins | HOST_ENTER or PARTICIPANT_ENTER (role-specific) | | PARTICIPANT_RECONNECT | Participant rejoins after disconnect | HOST_RECONNECT or PARTICIPANT_RECONNECT | | PARTICIPANT_EXIT | Participant permanently leaves | HOST_EXIT or PARTICIPANT_EXIT | | PARTICIPANT_DISCONNECT | Temporary connection loss | HOST_DISCONNECT or PARTICIPANT_DISCONNECT | | ACTION | Participant performs an action | Custom event (e.g., MOVE, ANSWER) |

Key Point: The AEM uses role-specific events (HOST_* vs PARTICIPANT_*) based on whether the user is the event creator/host. Your AppM never needs to check isHost - it just reacts to the specific events it receives.

Display Rendering

Meta Properties

App developers control what participants see using XState meta properties:

meta: {
  // Legacy formats (still supported)
  eachParticipant: 'TemplateName',           // String template name
  allParticipants: 'TemplateName',           // String template name
  spectators: 'TemplateName',                 // String template name
  
  // Object format (timeout metadata is legacy; prefer <anear-timeout> in PUG for participant-local UX)
  eachParticipant: { 
    view: 'TemplateName', 
    timeout: (appContext, participantId) => 30000,
    props: { message: 'Your turn!' }
  },
  
  // Selective participant rendering (function)
  eachParticipant: (appContext, event) => {
    const participantId = event.participantId
    if (participantId) {
      return [{ participantId, view: 'ThanksScreen', timeout: null }]
    }
    return []
  },
  
  // Host-specific display
  host: { view: 'HostScreen', props: { questionCount: 10 } }
}

Template Context

PUG templates receive rich context including:

  • app: Application XState context
  • participants: All participants map
  • participant: Individual participant data
  • meta: Display metadata (state, event, timeout, viewer)
  • props: Custom data from the meta block
  • PUG helpers: cdnImg(), action() for interactive elements

Timeout Management

Participant-local Timeouts

Participant-local timeout UX is handled by browser runtime (<anear-timeout>). JSAPI does not emit PARTICIPANT_TIMEOUT; timeout-driven actions should arrive as normal ACTION payloads (for example ACTION_TIMEOUT if your app uses that event name).

//- Active player's controls wrapped with browser-owned timeout UX
anear-timeout(duration='30000')
  anear-action(payload=action("MOVE")) Move

Group Timeouts

For coordinated actions where all participants must respond:

meta: {
  allParticipants: { view: 'QuestionScreen', timeout: 20000 }
},
on: {
  ANSWER: [
    {
      guard: ({ event }) => event.finalAction === true,
      actions: ['handleAllResponded']
    },
    {
      actions: ['handleIndividualResponse']
    }
  ],
  ACTIONS_TIMEOUT: {
    actions: ['handleTimeout']
  }
}

Asset Management

The JSAPI automatically handles asset management:

  1. CSS Upload: All CSS files in assets/css/ are uploaded to CloudFront
  2. Image Upload: All images in assets/images/ are uploaded to CloudFront
  3. Template Preloading: All PUG templates are preloaded and cached
  4. Hash-based dedupe: Asset uploads are decided by SHA-256 content hash comparison against S3 metadata (not file timestamps)

CI-Managed Assets Mode

For containerized deployments, you can publish assets during CI and skip runtime asset sync:

ANEARAPP_SKIP_RUNTIME_ASSET_SYNC=true

When this variable is enabled, AnearCoreServiceMachine skips image/font/CSS upload states and proceeds directly to template compilation and event lifecycle listening.

CI helpers are available under:

  • anear-js-api/lib/ci/publishAssets.js
  • anear-js-api/lib/ci/registerAppVersion.js

Versioned Lifecycle Channels

ACSM subscribes to versioned lifecycle channels derived from ANAPI app metadata:

anear:<appId>:v:<appVersion>:e

ANAPI must return latest_app_version in developer app payloads for startup channel resolution.

Asset Structure

assets/
├── css/
│   └── app.css
├── fonts/
│   └── YourCustomFont.ttf
└── images/
    ├── Background.png
    └── ...

The anearEvent Object

The anearEvent object is passed to your MachineFactory and provides access to Anear's core functionality:

// Event lifecycle management
await anearEvent.announceEvent()  // Transition to announced
await anearEvent.startEvent()     // Transition to live
await anearEvent.closeEvent()     // Transition to complete
await anearEvent.cancelEvent()    // Transition to cancelled

// allParticipants timeout management
anearEvent.cancelAllParticipantsTimeout()  // Cancel active allParticipants timeout orchestration

XState Integration

State Machine Factory Pattern

const MachineFactory = anearEvent => {
  const expandedConfig = {
    predictableActionArguments: true,
    ...Config
  }
  const machine = createMachine(
    expandedConfig,
    Functions(anearEvent)
  )
  return machine.withContext(Context)
}

Context Structure

const Context = {
  C: Object.freeze(Constants), // Available in templates
  // Game-specific state
  playerIds: {},
  gameState: 'WAITING',
  // ... other state
}

Event Termination

Critical: You must explicitly call closeEvent() or cancelEvent() before your AppM reaches a final state:

gameOver: {
  entry: ['saveGameStats', 'closeEvent'],
  type: 'final'
}

gameAborted: {
  entry: ['logAbortReason', 'cancelEvent'],
  type: 'final'
}

If your AppM reaches a final state without calling a termination method, the AEM will log an error and forcibly cancel the event.

RENDERED Event Synchronization

The RENDERED event is helpful for synchronizing display rendering with state transitions. It ensures that displays are fully processed before the AppM proceeds to the next state.

Use RENDERED for:

  • Display-only states with no user interaction
  • Start/end states in event flows
  • States where participants need time to see important displays

Skip RENDERED for:

  • Interactive states that will transition naturally
  • States waiting on user actions or timeouts

Key Benefits

  1. Abstraction: Hides Ably.io complexity from app developers
  2. Declarative: XState meta properties define display behavior
  3. Flexible: Rich context system enables dynamic content
  4. Scalable: Handles multiple concurrent events efficiently
  5. Reliable: Built-in reconnection and timeout management
  6. Consistent: XState-based architecture throughout

Documentation

For detailed information, see:

Example Projects

  • anear-hsm-test: Tic-Tac-Toe game demonstrating standard patterns
  • sparkpoll: Q&A app with selective participant rendering
  • neonbluff: Dice game with complex timeout management
  • perimeter-chat: Chat room demonstrating open-house events
  • event-chat: Embeddable chat widget for child apps

License

GPL-3.0-or-later