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

wovvmap_analytics

v0.0.8

Published

`wovvmap_analytics` is a small, framework-independent bridge between a map application and the WovvMap analytics backend.

Downloads

784

Readme

wovvmap_analytics

wovvmap_analytics is a small, framework-independent bridge between a map application and the WovvMap analytics backend.

The current fresh-start version supports:

  • session_started
  • session_heartbeat
  • session_ended
  • destination_selected
  • origin_selected
  • search_performed, search_result_selected, and search_abandoned
  • navigation journey lifecycle events
  • filter_selection_changed
  • spatial_heatmap_batch for bounded floor-wise map click and dwell cells
  • strongly typed application UI interactions such as ui_element_clicked
  • client_error_recorded and analytics_delivery_issue_reported for System Health

It does not store data locally and does not contain dashboard UI.

Load mall viewer configuration

The same package also acts as the typed read bridge for remotely managed viewer configuration. Call it once when a mall is loaded:

import { getMallViewerConfiguration } from 'wovvmap_analytics'

const configuration = await getMallViewerConfiguration({
  mallId: '87954445085211',
  environment: 'sit',
  apiVersion: 'v2',
})

const showZoomIn =
  configuration.templates.kioskClassic.actions.zoom_in

const defaultTheme = configuration.theme.themes.find(
  (theme) => theme.id === configuration.theme.defaultThemeId,
)
const lightTokens = defaultTheme?.modes.find(
  (definition) => definition.mode === 'light',
)?.tokens

The exact configuration key is environment:apiVersion:mallId, so matching SIT and production mall IDs never share settings accidentally. The response contains allowed/default templates, the complete allowed theme catalog with mode-wise ViewerThemeTokens, and a direct action-visibility record for every template. Theme IDs, labels and token JSON come from the backend; consumers do not need a duplicated static theme list or token file. Consumers should combine a remote action rule with runtime availability; for example, the Offers action still requires actual offer data.

The package validates the response, caches it for 60 seconds and revalidates with ETag. If the backend is temporarily unavailable, it uses the latest cached configuration or a safe built-in fallback. It does not persist configuration in IndexedDB or local storage.

Future layout control will extend the same versioned response with stable component IDs, layout slots and bounded design tokens. Integrations must not accept raw CSS or executable code from the backend.

Global application setup

For complete application analytics, create one client above the application's root navigation. This does not depend on the map WebView, so Login and other native/web screens can send events before a mall is opened:

import {
  AnalyticsProvider,
  backendProvider,
  createAnalytics,
} from 'wovvmap_analytics'

const analytics = createAnalytics({
  application: {
    id: 'nexus_one',
    name: 'Nexus One',
    version: '2.1.0',
  },
  contextProvider: async () => ({
    authenticationStatus: 'guest',
    externalUserId: null,
    userName: null,
    deviceId: 'stable-device-id',
    deviceIdType: 'native_device',
    devicePlatform: 'ios',
    deviceModel: 'iPhone',
    containerType: 'native_app',
  }),
  autoTrack: {
    errors: true,
  },
  providers: [backendProvider()],
})

export function AppRoot() {
  return (
    <AnalyticsProvider client={analytics}>
      <App />
    </AnalyticsProvider>
  )
}

The integrating application owns its identity and runtime context. Product names are therefore not hard-coded in package services.

Track semantic actions from any component through the provider:

const analytics = useAnalytics()

await analytics.track('ui_element_clicked', {
  screenId: 'login',
  routeName: 'Login',
  elementId: 'login_submit',
  elementLabel: 'Proceed',
  elementType: 'button',
  flowId: 'authentication.login',
  flowVariant: 'email_password',
  metadata: {
    loginMethod: 'email_password',
  },
})

Use flowId for the stable business flow and flowVariant when the same route/action has multiple paths, such as phone OTP and email/password. Never put passwords, tokens, phone numbers or email addresses in metadata.

System Health integration

Set autoTrack.errors: true on the application-level client to capture browser errors, unhandled promise rejections, and React Native global errors. The same application session and runtime context are attached automatically, so the dashboard can filter incidents by application, version, visitor, device, and container. Call analytics.stopAutoTracking() only when the application root is permanently unmounted.

Map integrations can also report an error with the currently active mall session:

await mapAnalytics.recordClientError({
  ...activeSessionContext,
  severity: 'error',
  category: 'javascript',
  errorName: error.name,
  errorCode: null,
  message: error.message,
  stack: error.stack ?? null,
})

Use reportAnalyticsDeliveryIssue when a host owns retry/offline delivery and knows that an analytics request failed or later recovered. The backend also records malformed and rejected /api/analytics/events requests by itself. Health tracking is best-effort and must never replace the host application's normal error handler.

One typed event pipeline

All built-in events are delivered to one backend endpoint:

POST /api/analytics/events

The endpoint is shared, but event payloads are not merged into one loose object. eventType is the discriminator for a strongly typed event map:

type WovvMapAnalyticsEventMap = {
  session_started: MallSessionEvent
  session_heartbeat: MallSessionHeartbeatEvent
  session_ended: MallSessionEndedEvent
  origin_selected: OriginSelectionEvent
  destination_selected: DestinationSelectionEvent
  filter_selection_changed: FilterSelectionEvent
  spatial_heatmap_batch: SpatialHeatmapBatchEvent
  client_error_recorded: ClientErrorRecordedEvent
  analytics_delivery_issue_reported: AnalyticsDeliveryIssueReportedEvent
  // Navigation lifecycle event types are registered here as well.
}

Each domain keeps its own controller, service, validator, and database read model. The backend event registry selects the correct domain processor without a growing if/else chain. Adding an event means:

  1. define its event name and payload type;
  2. implement its domain validation and persistence;
  3. register its processor;
  4. expose a clear package method for application developers.

The public endpoint and transport do not change when another event is added.

Track spatial heatmap activity

Use trackSpatialHeatmapBatch for already-aggregated map grid cells. Do not send every pointer move. The integration should sample movement, combine nearby coordinates, and send no more than 50 cells per request:

await analytics.trackSpatialHeatmapBatch({
  sessionId: session.sessionId,
  surface: 'map',
  cells: [
    {
      cellKey: '3:21:17:map',
      floorIndex: 3,
      floorName: 'Ground Floor',
      gridX: 21,
      gridY: 17,
      mapX: 2580,
      mapY: 2100,
      normalizedX: 0.52,
      normalizedY: 0.47,
      clickCount: 1,
      dwellTimeMs: 750,
      sampleCount: 3,
      nodePointKey: null,
    },
  ],
})

mapX and mapY must use the unscaled map coordinate system, not camera or screen pixels. normalizedX and normalizedY are diagnostic viewport values between 0 and 1. The WovVMap core integration performs this batching and coordinate conversion automatically, including when the viewer runs inside a WebView or iframe. The existing mall session supplies application, user, device, container and mall identity; the host must not send a duplicate event.

Installation

npm install wovvmap_analytics

Start a mall session

Create the analytics client once in your integration service. The backend URL is centrally managed inside the package:

import { createWovvMapAnalytics } from 'wovvmap_analytics'

const analytics = createWovvMapAnalytics()

Call startMallSession after the selected mall has loaded successfully:

const session = await analytics.startMallSession({
  applicationSessionId: analytics.applicationSessionId,
  clientInstanceId: 'viewer-instance-id',
  mallId: '87954445085211',
  mallApiVersion: 'v2',
  mallEnvironment: 'sit',
  authenticationStatus: 'authenticated',
  externalUserId: 'user-123',
  userName: 'Alex',
  deviceId: 'stable-device-id',
  deviceIdType: 'native_device',
  devicePlatform: 'ios',
  deviceModel: 'iPhone',
  sourceApplicationId: 'nexus_one',
  sourceApplicationName: 'Nexus One',
  sourceApplicationVersion: '1.0.0',
  containerType: 'webview',
})

applicationSessionId is the parent application lifecycle. Keep it unchanged while the host application remains open. Every map visit still receives its own sessionId, allowing one application session to contain multiple mall visits without losing login-to-map correlation.

While the map is visible, confirm that the session is still alive. The WovVMap core integration sends this every 30 seconds:

await analytics.heartbeatMallSession({
  sessionId: session.sessionId,
})

Keep only one heartbeat timer per active session. Pause it while the viewer is hidden/backgrounded and stop it before ending the session. A heartbeat updates the existing session row; it does not create a separate analytics row.

Keep the returned sessionId and end the same session when its owning map screen closes, loses focus, changes mall, or moves to the background:

await analytics.endMallSession({
  sessionId: session.sessionId,
  endReason: 'screen_unfocused',
})

Supported end reasons are:

  • screen_unfocused
  • app_backgrounded
  • mall_changed
  • identity_changed
  • page_closed
  • user_logout
  • inactivity_timeout
  • session_replaced
  • manual

The backend closes the existing session row and stores endedAt, durationMs, endReason, and the end event ID. Repeated end requests are idempotent and do not create duplicate session rows. inactivity_timeout and session_replaced are normally assigned by the backend.

If a browser, iframe, or WebView disappears without sending session_ended, the backend expires it after the configured timeout. Starting a new session with the same clientInstanceId also closes the previous instance without affecting another tab or WebView.

Track a destination selection

Call trackDestinationSelection only after the viewer has resolved the selected destination node and has an active mall session:

await analytics.trackDestinationSelection({
  sessionId: session.sessionId,
  destinationNodeId: 'node-101',
  destinationNodeIds: ['node-101'],
  destinationName: 'Example Store',
  amenityId: null,
  brandId: 'brand-10',
  categoryCode: 'fashion',
  subCategoryCode: 'menswear',
  floorIndex: 1,
  floorName: 'First Floor',
  selectionSource: 'search',
})

For grouped stores, pass every location in destinationNodeIds and the displayed location first as destinationNodeId. interactionId is optional; the package creates it automatically. An iframe or WebView host should provide one stable interaction ID through the bridge when a host-side action selects the destination. The backend uses it to make retries idempotent.

Supported selection sources are map, search, category, subcategory, amenity, favorite, offer, offer_with_category, offer_with_subcategory, deeplink, direction, ai_assistant, bridge, and unknown.

For an amenity selection, pass its stable ID and use the amenity source:

amenityId: 'amenity-10',
selectionSource: 'amenity',

Track an origin selection

Use trackOriginSelection for a meaningful start-point selection. Do not call it for history restoration or other internal state synchronization:

await analytics.trackOriginSelection({
  sessionId: session.sessionId,
  originNodeId: 'entrance-node-1',
  originNodeIds: ['entrance-node-1'],
  originName: 'Main Entrance',
  amenityId: null,
  brandId: null,
  categoryCode: null,
  subCategoryCode: null,
  floorIndex: 0,
  floorName: 'Ground Floor',
  selectionSource: 'default_location',
  isDefaultLocation: true,
})

Supported sources are direction_search, default_location, deeplink, amenity, ai_assistant, bridge, swap, map, and unknown. amenityId is required when the source is amenity. A default location can also contain an amenity ID. The backend counts the same default origin once per session and keeps one current assignment per mall and device.

Track a navigation journey

Use one stable journeyId for a single direction request. Increment sequenceNumber for every meaningful lifecycle change and repeat the immutable origin, destination, floor, and route snapshot on each event.

await analytics.trackNavigationJourneyEvent({
  sessionId: session.sessionId,
  journeyId: 'journey-123',
  sourceSearchId: null,
  sourceOriginSearchId: null,
  sourceDestinationSearchId: null,
  simulationRunId: null,
  simulationRunNumber: null,
  sequenceNumber: 1,
  eventType: 'direction_requested',
  originNodeId: 'entrance-1',
  originName: 'Main Entrance',
  originFloorIndex: 0,
  originFloorName: 'Ground Floor',
  destinationNodeId: 'store-101',
  destinationName: 'Example Store',
  destinationFloorIndex: 1,
  destinationFloorName: 'First Floor',
  pathType: 'default',
  fallbackUsed: null,
  routeDistanceMeters: null,
  estimatedDurationSeconds: null,
  stepCount: 0,
  floorTransitionCount: 0,
  progress: 0,
  currentStepIndex: null,
  currentFloorIndex: 0,
  failureReason: null,
  endReason: null,
})

Supported lifecycle events are:

  • direction_requested, route_generated, and route_failed;
  • simulation_started, simulation_resumed, and simulation_paused;
  • simulation_progress_milestone and simulation_completed;
  • simulation_abandoned and step_by_step_opened;
  • route_replaced and route_cleared.

Send progress milestones at meaningful boundaries such as 25%, 50%, and 75%. Do not send animation-frame or every-position events. The backend keeps an append-only event timeline and updates one current summary row per journey. Retries are idempotent by eventId and by journeyId + sequenceNumber.

One journey may contain multiple simulation attempts. Create a new simulationRunId and increment simulationRunNumber only when Play starts a new attempt. Pause, resume, progress, completion, abandonment, and step-list events for that attempt reuse the same run identity. Route-level events use null for both fields. Clearing the route ends the journey; generating the same route after that clear creates a new journeyId.

When the WovvMap viewer core is used, its integration owns this lifecycle tracking. An iframe or WebView host should only issue navigation commands and must not call the analytics backend again for the same action.

When a session ends while a simulation is active or paused, the backend adds a system simulation_abandoned timeline event. A generated route that was never started is finalized as not_started. Completed journeys remain completed.

Track search intent

Use one searchId for the complete lifecycle of a stable query. Send the performed event after the UI debounce has settled, then reuse its ID when the visitor selects a result or closes the search without selecting anything:

const search = await analytics.trackSearchPerformed({
  sessionId: session.sessionId,
  queryText: 'coffee',
  resultCount: 4,
  inputSource: 'main_search',
  intentType: 'store',
  topResult: {
    nodeId: 'store-101',
    name: 'Coffee House',
    brandId: 'brand-10',
    categoryCode: 'food',
    amenityId: null,
  },
})

await analytics.trackSearchResultSelected({
  sessionId: session.sessionId,
  searchId: search.searchId,
  resultPosition: 1,
  selectedResult: {
    nodeId: 'store-101',
    name: 'Coffee House',
    brandId: 'brand-10',
    categoryCode: 'food',
    amenityId: null,
  },
})

Supported input sources are main_search, origin, destination, assistant, and other. Supported intent types are store, brand, category, amenity, general, and unresolved.

When selected search results create a direction request, pass their IDs as sourceOriginSearchId and sourceDestinationSearchId. This links both start and end-point searches to the same journey without a dashboard-side join. The viewer consumes each attribution after the next matching route, so later manual routes to the same nodes are not incorrectly counted as search conversions. sourceSearchId remains temporarily available for older destination-only integrations.

Do not send an event for every keystroke. The integrating UI should debounce input and suppress unchanged normalized queries. Only user-originated input changes should call trackSearchPerformed; values filled from URL parameters, default locations, or store selections must not be reported as searches merely because they appear in the input. The backend stores one transaction summary per search plus an append-only event timeline; retries are idempotent by eventId.

If the search input is rendered by a native app or iframe host, do not call this package from both the host and viewer. Send the typed search lifecycle through wovvmap-webview-bridge's single typed analyticsEvent channel; the viewer will attach its active mall/session context and make the backend call.

The package sends this backend-ready contract:

interface MallSessionEvent {
  schemaVersion: '1.1'
  eventId: string
  eventType: 'session_started'
  sessionId: string
  clientInstanceId: string
  mallId: string
  mallApiVersion: 'v1' | 'v2'
  mallEnvironment: 'sit' | 'prod'
  authenticationStatus: 'guest' | 'authenticated'
  externalUserId: string | null
  userName: string | null
  deviceId: string
  deviceIdType:
    | 'browser_installation'
    | 'native_device'
    | 'managed_device'
    | 'kiosk_configured'
  devicePlatform:
    | 'android'
    | 'ios'
    | 'windows'
    | 'macos'
    | 'linux'
    | 'web'
    | 'kiosk'
  deviceModel: string | null
  sourceApplicationId: string
  sourceApplicationName: string
  sourceApplicationVersion: string | null
  containerType: 'browser' | 'webview' | 'iframe' | 'kiosk'
  occurredAt: string
}

eventId, sessionId, and occurredAt are created by the package. The host application supplies the mall, identity, device, and source application context. A host should provide a stable clientInstanceId for one browser tab, iframe, or WebView across reloads. For guest sessions, pass externalUserId: null and userName: null.

Integration responsibility

The host application should:

  • create one analytics client;
  • call the event only from the common successful mall-load flow;
  • provide a stable device/installation ID appropriate for its platform;
  • identify the host with a stable application ID and readable name;
  • maintain one stable client-instance ID per tab, iframe, or WebView;
  • send one heartbeat every 30 seconds while that viewer is active;
  • stop the heartbeat before sending session_ended;
  • handle network errors without breaking the map experience.

The package sends all analytics events directly to the analytics backend. It does not store sessions locally, render dashboard UI, or send a second copy through the native/iframe host.

Multi-application reporting

sourceApplicationId must be a stable machine-readable product ID and sourceApplicationName must be its readable label. Do not hard-code a single client or product inside a reusable service.

Examples:

| Product branch | Application ID | Application name | | --- | --- | --- | | Nexus One | nexus_one | Nexus One | | OKEN | oken | OKEN | | Oberoi | oberoi | Oberoi |

Sessions store this application context once. Origin, destination, journey, and future UI events inherit the canonical context from the session when their read-model rows are written. This supports combined reports and application, version, environment, user, device, container, mall, and date filters without client-side joins.

The OKEN multi-product application resolves this identity through AppAnalyticsContextService; adding a product belongs in the central application analytics configuration, not in the service.