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

@lumra/plugins

v0.2.4

Published

Lumra built-in plugins (analytics, paywall gate, autoplay)

Readme

@lumra/plugins

Official plugins for the Lumra media player — analytics, paywall gating, chapters, VAST ads, heatmap, and LUT colour grading.

Install

npm install @lumra/plugins @lumra/core

analyticsPlugin

Fires milestone beacons at 25%, 50%, 75%, and 100% watched. Optionally POSTs to your analytics endpoint.

import { analyticsPlugin } from '@lumra/plugins'

player.use(analyticsPlugin({
  resourceId: 'video-123',
  endpoint:   '/api/analytics',        // your backend receives the milestone POST
  onMilestone: (pct) => console.log(`${pct}% watched`),
}))

What your endpoint receives — POST /api/analytics:

{ "resourceId": "video-123", "milestone": 50, "currentTime": 142.3, "duration": 284.6 }

paywallGatePlugin

Blocks playback after a free preview period and fires a callback to show your paywall UI.

import { paywallGatePlugin } from '@lumra/plugins'

player.use(paywallGatePlugin({
  previewDuration: 30,
  onBlock: () => showPaywallOverlay(),
}))

chaptersPlugin — Premium

The free version of chapters (passing a chapters array directly to the player) gives you seek bar markers and click-to-jump. That's all most people need — no plugin or license required.

The premium chaptersPlugin adds one extra thing: an onChapterChange(chapter, index) JavaScript callback that fires in your code every time the video crosses a chapter boundary during playback. Use this when you need to react to chapter changes — e.g. highlight the current chapter in a sidebar, log which chapters viewers reach in your analytics, show a pop-up as each chapter starts.

import { chaptersPlugin } from '@lumra/plugins'

player.use(chaptersPlugin({
  licenseKey:      'LUMRA-CHAP-XXXXXXXX-XXXXXXXX',
  verifyEndpoint:  'https://lumra.reelfoundry.au/api/verify',
  verifyPublicKey: `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9S3mIScMh6vOGHCm48RQ
7VvdALKg7foE8hKK2OQF4LlIzZyxrNDkv1hklcPU62hf50qrmOBqC8h3KbIt8Tm+
kz4kJMQgwt93bALcAXfIWNClMUPQoJUryDB29cHjCi3VqFcVohkyQfk7Q6D+32wY
9SYidvDU4nFWONqCgzz3Tx55jYi+yTeM7SajlGpfFhwL733DHE5fkoPfrWzrHnNX
nb1X9xs76ZbHhbD1obJIxdpcFrNfZV7eGR0qmAaJIVAL1GF8q3rrn/blhrJNTAPS
WJzNwEuOJoa5lsAG7SSs4mNTxpwOPk1wPvXirBlITjNoVw4BHf1C6kkGaXhuBLcU
LwIDAQAB
-----END PUBLIC KEY-----`,
  chapters: [
    { at: 0,   title: 'Intro' },
    { at: 60,  title: 'Act 1' },
    { at: 180, title: 'Climax' },
  ],
  onChapterChange: (chapter, index) => {
    // Fires each time the viewer enters a new chapter
    if (chapter) console.log('Now in:', chapter.title)  // update your UI here
  },
}))

To programmatically skip to a chapter: player.seek(chapter.at)


adPlugin — Premium

VAST 2.0/3.0 pre-roll, mid-roll, and post-roll ads. Parses VAST XML, fires impression and tracking beacons automatically. Requires a license key.

import { adPlugin } from '@lumra/plugins'

player.use(adPlugin({
  licenseKey:      'LUMRA-ADS-XXXXXXXX-XXXXXXXX',
  verifyEndpoint:  'https://lumra.reelfoundry.au/api/verify',
  verifyPublicKey: `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9S3mIScMh6vOGHCm48RQ
7VvdALKg7foE8hKK2OQF4LlIzZyxrNDkv1hklcPU62hf50qrmOBqC8h3KbIt8Tm+
kz4kJMQgwt93bALcAXfIWNClMUPQoJUryDB29cHjCi3VqFcVohkyQfk7Q6D+32wY
9SYidvDU4nFWONqCgzz3Tx55jYi+yTeM7SajlGpfFhwL733DHE5fkoPfrWzrHnNX
nb1X9xs76ZbHhbD1obJIxdpcFrNfZV7eGR0qmAaJIVAL1GF8q3rrn/blhrJNTAPS
WJzNwEuOJoa5lsAG7SSs4mNTxpwOPk1wPvXirBlITjNoVw4BHf1C6kkGaXhuBLcU
LwIDAQAB
-----END PUBLIC KEY-----`,
  preRoll: {
    vast:      'https://pubads.g.doubleclick.net/...',  // VAST tag URL
    skipAfter: 5,    // show Skip button after 5s (0 = not skippable)
  },
  midRoll: [
    { at: 120, vast: 'https://...', skipAfter: 0 },
  ],
  postRoll: {
    src: 'https://example.com/ads/post.mp4',
    skipAfter: 0,
  },
  onAdStart: (info) => console.log('Ad started:', info.phase),
  onAdEnd:   () => console.log('Ad ended'),
  onAdSkip:  () => console.log('Ad skipped'),
  // Optional: shown in the in-player "Get a license" banner when key is invalid
  purchaseUrl: 'https://your-site.com/purchase',
}))

heatmapPlugin — Premium

YouTube-style engagement heatmap on the seek bar. Your backend serves the view-count data; the plugin normalises it and renders it. Requires a license key.

import { heatmapPlugin } from '@lumra/plugins'

player.use(heatmapPlugin({
  licenseKey:      'LUMRA-HEAT-XXXXXXXX-XXXXXXXX',
  verifyEndpoint:  'https://lumra.reelfoundry.au/api/verify',
  verifyPublicKey: `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9S3mIScMh6vOGHCm48RQ
7VvdALKg7foE8hKK2OQF4LlIzZyxrNDkv1hklcPU62hf50qrmOBqC8h3KbIt8Tm+
kz4kJMQgwt93bALcAXfIWNClMUPQoJUryDB29cHjCi3VqFcVohkyQfk7Q6D+32wY
9SYidvDU4nFWONqCgzz3Tx55jYi+yTeM7SajlGpfFhwL733DHE5fkoPfrWzrHnNX
nb1X9xs76ZbHhbD1obJIxdpcFrNfZV7eGR0qmAaJIVAL1GF8q3rrn/blhrJNTAPS
WJzNwEuOJoa5lsAG7SSs4mNTxpwOPk1wPvXirBlITjNoVw4BHf1C6kkGaXhuBLcU
LwIDAQAB
-----END PUBLIC KEY-----`,
  data:   'https://yourapp.com/api/heatmap/video-123',
  onData: (normalised) => renderHeatmap(normalised),  // 0–1 array, one value per segment
}))

What your endpoint must return — GET /api/heatmap/:id:

{ "data": [120, 450, 890, 670, 230, 90] }

Each number is a view count for one time segment (one value per 5 seconds works well). The plugin divides by the maximum to produce a 0–1 overlay. Build this data by incrementing a counter per segment in your analytics webhook.


lutPlugin — Premium

Real-time WebGL colour grading via .cube LUT files. Requires a license key.

import { lutPlugin } from '@lumra/plugins'
import type { LutPluginHandle } from '@lumra/plugins'

const lut = lutPlugin({
  licenseKey:      'LUMRA-LUT-XXXXXXXX-XXXXXXXX',
  verifyEndpoint:  'https://lumra.reelfoundry.au/api/verify',
  verifyPublicKey: `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9S3mIScMh6vOGHCm48RQ
7VvdALKg7foE8hKK2OQF4LlIzZyxrNDkv1hklcPU62hf50qrmOBqC8h3KbIt8Tm+
kz4kJMQgwt93bALcAXfIWNClMUPQoJUryDB29cHjCi3VqFcVohkyQfk7Q6D+32wY
9SYidvDU4nFWONqCgzz3Tx55jYi+yTeM7SajlGpfFhwL733DHE5fkoPfrWzrHnNX
nb1X9xs76ZbHhbD1obJIxdpcFrNfZV7eGR0qmAaJIVAL1GF8q3rrn/blhrJNTAPS
WJzNwEuOJoa5lsAG7SSs4mNTxpwOPk1wPvXirBlITjNoVw4BHf1C6kkGaXhuBLcU
LwIDAQAB
-----END PUBLIC KEY-----`,
  url:         '/luts/cinematic.cube',
  intensity:   0.85,
  // Optional: shown in the in-player "Get a license" banner when key is invalid
  purchaseUrl: 'https://your-site.com/purchase',
}) as LutPluginHandle

player.use(lut)

// Hot-swap at runtime
await lut.setLut('/luts/vintage.cube')
lut.setIntensity(0.5)  // 0 = original, 1 = fully graded

About verifyPublicKey

verifyPublicKey is optional on all premium plugins. Without it, the plugin still calls the license server and validates your key — it just skips the extra step of cryptographically verifying the server's JWT response locally in the browser.

The public key is the same for all customers — it's Lumra's license server key, shown in the examples above. Copy it as-is.

| | Without verifyPublicKey | With verifyPublicKey | |---|---|---| | Server validates key | Yes | Yes | | Browser verifies JWT signature | No | Yes | | Security | Good | Best |


Hiding your license key (proxy mode)

When using Lumra in a CDN/plain-HTML page your licenseKey is visible in the page source. Use a server-side proxy to keep it out of the browser entirely.

Frontend — no key in browser code:

<script>
Lumra.createPlayer('#player', {
  lut: {
    // No licenseKey here — the proxy adds it server-side
    verifyEndpoint: '/api/lumra-verify',
    url: '/luts/cinematic.cube',
  },
})
</script>

Your proxy endpoint (Node.js / Express):

app.post('/api/lumra-verify', async (req, res) => {
  const resp = await fetch('https://lumra.reelfoundry.au/api/verify', {
    method:  'POST',
    headers: { 'Content-Type': 'application/json' },
    body:    JSON.stringify({ ...req.body, key: process.env.LUMRA_LICENSE_KEY }),
  })
  res.json(await resp.json())
})

PHP proxy:

<?php // /api/lumra-verify.php
$body = json_decode(file_get_contents('php://input'), true) ?? [];
$body['key'] = getenv('LUMRA_LICENSE_KEY');
$ch = curl_init('https://lumra.reelfoundry.au/api/verify');
curl_setopt_array($ch, [
  CURLOPT_POST           => true,
  CURLOPT_POSTFIELDS     => json_encode($body),
  CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
  CURLOPT_RETURNTRANSFER => true,
]);
header('Content-Type: application/json');
echo curl_exec($ch);

Next.js API Route:

// app/api/lumra-verify/route.ts
export async function POST(req: Request) {
  const body = await req.json()
  const resp = await fetch('https://lumra.reelfoundry.au/api/verify', {
    method:  'POST',
    headers: { 'Content-Type': 'application/json' },
    body:    JSON.stringify({ ...body, key: process.env.LUMRA_LICENSE_KEY }),
  })
  return Response.json(await resp.json())
}

The browser only ever sends product + origin to your proxy. The real key lives in process.env and never touches the client.


Getting a license key

Purchase a license at lumra.reelfoundry.au/purchase. Premium plugins (chaptersPlugin, adPlugin, heatmapPlugin, lutPlugin) each require their own key.

Use the demo key (LUMRA-LUT-DEMO0001-DEMO0001, LUMRA-ADS-DEMO0001-DEMO0001, etc.) for local development — demo keys only work on localhost and will show a purchase banner on any public domain.


© 2026 Reel Foundry AU. All rights reserved.
MIT License — see LICENSE