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

@superapp_men/content-provider

v1.8.0

Published

Offline content provider for SuperApp partner applications via iframe/Capacitor

Readme

@superapp_men/content-provider

Bridge package for Bewize / SuperApp partner applications to request offline assets from the SuperApp host via postMessage.

Partner apps run inside an iframe managed by the SuperApp. This package provides typed clients for five categories of assets:

| Client | Function | What it returns | |--------|----------|-----------------| | ContentProviderClient | getContent() | The student's grade-specific content.json | | MediaProviderClient | getMedia(path) / getMediaObjectUrl(path) | Downloaded audio / video / image files | | BackgroundProviderClient | getBackground(name) | Shared WebP background images | | SoundProviderClient | getSound(name) | Shared sound-design WAV files | | RiveProviderClient | getRive() / getRiveBuffers() / getRiveUrls() | Rive WASM runtime files (rive.wasm + rive_fallback.wasm) | | VideoProviderClient | getVideo(path) / downloadVideo(path) | Palier video status + local playable URI |


Installation

npm install @superapp_men/content-provider

ContentProviderClient

Retrieves the pre-downloaded content.json for the authenticated student's grade.

import { ContentProviderClient } from "@superapp_men/content-provider";

const client = new ContentProviderClient({ partnerCode: "bewize" });

try {
  const content = await client.getContent();
  console.log(content); // parsed content.json object
} finally {
  client.destroy();
}

Config

| Option | Type | Default | Description | |--------|------|---------|-------------| | partnerCode | string | required | Your partner code (e.g. "bewize") | | timeout | number | 15000 | Request timeout in ms | | debug | boolean | false | Enable console logs |


MediaProviderClient

Retrieves downloaded media files (audio, video, images) stored in the SuperApp's data directory at partner-apps/{partnerCode}/media/.

import { MediaProviderClient } from "@superapp_men/content-provider";

const media = new MediaProviderClient({ partnerCode: "bewize" });

// Option A — get base64 data + MIME type
const { data, mimeType, encoding } = await media.getMedia("consignes/00e5b2a4-98c8-409e-9b68-d8eb397d4c78.mp3");

// Option B — get a ready-to-use object URL (recommended for <audio> / <video> / <img>)
const { url, mimeType, revoke } = await media.getMediaObjectUrl("consignes/00e5b2a4-98c8-409e-9b68-d8eb397d4c78.mp3");

const audio = new Audio(url);
audio.play();

// Release the object URL when no longer needed
audio.addEventListener("ended", revoke);

media.destroy();

mediaPath format

Pass the relative path exactly as listed in the manifest media.audio.files[] array:

"consignes/00e5b2a4-98c8-409e-9b68-d8eb397d4c78.mp3"
"questions/159903a1-803d-46ee-9f34-365010d378f3.mp3"

The same function works for future video and image files — the MIME type is auto-detected from the extension.

Supported MIME types

| Extension | MIME type | |-----------|-----------| | .mp3 | audio/mpeg | | .m4a | audio/mp4 | | .ogg | audio/ogg | | .wav | audio/wav | | .mp4 | video/mp4 | | .webm | video/webm | | .png | image/png | | .jpg / .jpeg | image/jpeg | | .gif | image/gif | | .webp | image/webp | | .svg | image/svg+xml |

Config

| Option | Type | Default | Description | |--------|------|---------|-------------| | partnerCode | string | required | Your partner code | | timeout | number | 30000 | Request timeout in ms (files can be large) | | debug | boolean | false | Enable console logs |


BackgroundProviderClient

Retrieves shared WebP background images hosted by the SuperApp. Returns a ready-to-use base64 data URL.

import { BackgroundProviderClient } from "@superapp_men/content-provider";
import type { BackgroundName } from "@superapp_men/content-provider";

const bg = new BackgroundProviderClient();

const { url } = await bg.getBackground("champs");

// Use as CSS background
element.style.backgroundImage = `url(${url})`;

// Or as <img> src
imgElement.src = url;

bg.destroy();

Available backgrounds

| Name | Description | |------|-------------| | "arbre" | Tree scene | | "champs" | Green countryside fields | | "decor_souk" | Traditional souk decoration | | "douar" | Rural village scene | | "embarcadere" | Embarcadère / harbour scene | | "msid" | Mosque / msid scene | | "oliveraie" | Olive grove | | "puis" | Well / water scene |

Config

| Option | Type | Default | Description | |--------|------|---------|-------------| | timeout | number | 10000 | Request timeout in ms | | debug | boolean | false | Enable console logs |


SoundProviderClient

Retrieves shared sound-design WAV files hosted by the SuperApp (button clicks, drag/drop, feedback stings, menu music, etc.). Returns a ready-to-use base64 data URL, the same way BackgroundProviderClient does for images.

import { SoundProviderClient } from "@superapp_men/content-provider";
import type { SoundName } from "@superapp_men/content-provider";

const sounds = new SoundProviderClient();

try {
  const { url } = await sounds.getSound("drop");
  const audio = new Audio(url);
  await audio.play();
} catch (error) {
  // Unknown sound name, SuperApp error, or timeout all land here.
  console.error("Could not load sound:", error);
} finally {
  sounds.destroy();
}

Function signature

getSound(name: SoundName): Promise<{ url: string; mimeType: string }>

| Param | Type | Description | |-------|------|-------------| | name | SoundName | Sound file name, without extension (see the full list below) |

Returns { url: string; mimeType: string } — url is a data:audio/wav;base64,... URL, mimeType is always "audio/wav".

Throws an Error when:

  • the requested name is not one of the sounds the SuperApp serves (statusCode: 404, message Unknown sound: "<name>"),
  • the SuperApp fails to read the file (statusCode: 500), or
  • no response arrives before timeout elapses (default 10000ms).

In every case the promise rejects — it never resolves with a partial/empty result, so a single try/catch around getSound() is enough to handle all failure modes.

Available sounds

| Name | |------| | "Loader marche compagnon" | | "cloudInOut" | | "deroulement panneau" | | "drag" | | "drop" | | "feedback KO-002" | | "feedback OK-001" | | "feedback OK-002" | | "feedback OK-003" | | "music_01" | | "music_02" | | "music_02_variation1-001" | | "music_02_variation1-002" | | "scroll menu ile-001" | | "son appuie bouton-001" | | "son bounce bouton-001" | | "son burn plein ecran" | | "son d'apparition consigne-001" | | "son fermeture panneau exercice" | | "son ouverture panneau exercice" | | "son progression bar-001" | | "son selection champ-001" | | "son selection element parmi autre-001" | | "sons etoiles-002" | | "splash screen" |

Config

| Option | Type | Default | Description | |--------|------|---------|-------------| | timeout | number | 10000 | Request timeout in ms | | debug | boolean | false | Enable console logs |


RiveProviderClient

Retrieves both Rive WASM runtime files (rive.wasm and rive_fallback.wasm) from the SuperApp's static assets via postMessage. Call this once before mounting any Rive component so the runtime is ready.

import { RiveProviderClient } from "@superapp_men/content-provider";
import { RuntimeLoader } from "@rive-app/canvas";

const riveProvider = new RiveProviderClient({ partnerCode: "bewize" });

// Option A — object URLs (recommended)
// Pass primaryUrl to RuntimeLoader before mounting any Rive component.
// Call revoke() once the runtime has finished loading to free memory.
const { primaryUrl, fallbackUrl, revoke } = await riveProvider.getRiveUrls();
RuntimeLoader.setWasmUrl(primaryUrl);
// ... mount Rive components ...
revoke(); // free both object URLs

// Option B — ArrayBuffers
// Use when you need direct binary access to the WASM files.
const { primary, fallback } = await riveProvider.getRiveBuffers();
const primaryUrl2 = URL.createObjectURL(new Blob([primary], { type: "application/wasm" }));
RuntimeLoader.setWasmUrl(primaryUrl2);

// Option C — raw base64 objects
// Low-level access; prefer getRiveUrls() or getRiveBuffers() in practice.
const { primary: p, fallback: f } = await riveProvider.getRive();
// p.data / f.data are base64 strings; p.mimeType === "application/wasm"

riveProvider.destroy();

Methods

| Method | Returns | Description | |--------|---------|-------------| | getRive() | { primary: RiveWasmFile, fallback: RiveWasmFile } | Both WASM files as raw base64 objects | | getRiveBuffers() | { primary: ArrayBuffer, fallback: ArrayBuffer } | Both WASM files decoded to ArrayBuffers | | getRiveUrls() | { primaryUrl: string, fallbackUrl: string, revoke: () => void } | Both WASM files as temporary object URLs |

Config

| Option | Type | Default | Description | |--------|------|---------|-------------| | partnerCode | string | required | Your partner code (e.g. "bewize") | | timeout | number | 30000 | Request timeout in ms | | debug | boolean | false | Enable console logs |

SuperApp host paths

The SuperApp serves both files from its own static assets — no bundle extraction needed on the partner side:

/rive-wasm/rive.wasm           → returned as primary
/rive-wasm/rive_fallback.wasm  → returned as fallback

VideoProviderClient

Retrieves palier videos managed by the SuperApp's offline video download manager. Videos are defined in the partner manifest under media.videos.byPalier and stored locally at partner-apps/{partnerCode}/videos/palier{N}/.

The SuperApp preloads videos automatically after the ASER test (current learning week first, then following weeks). This client lets the partner query a video's status and explicitly request downloads.

getVideo() blocks until the outcome is final. If the video is missing and the device is online, the SuperApp downloads it (joining an already-in-progress download rather than starting a duplicate) and the call only resolves once that download finishes or fails. A partner showing a loading screen gets the real video in the same call — no polling loop required.

import { VideoProviderClient } from "@superapp_men/content-provider";

const videos = new VideoProviderClient({ partnerCode: "bewize" });

const status = await videos.getVideo("palier3/v1p3.mp4"); // or just "v1p3.mp4" — waits for the download

if (status.available) {
  // Case 1 — downloaded (either it already existed, or the download you just
  // waited on succeeded): play it directly
  videoElement.src = status.localUri!;
} else if (status.error === "VIDEO_NOT_DOWNLOADED_AND_OFFLINE") {
  // Case 2 — not downloaded AND offline: show "unavailable offline" UI
} else if (status.error) {
  // Case 3 — download was attempted but failed (e.g. "DOWNLOAD_FAILED"):
  // show an error state; the partner may retry by calling getVideo() again
}

// Explicitly ask the SuperApp to download a video (same shared download
// manager as the automatic preload — duplicates are impossible). Same
// blocking behavior as getVideo().
await videos.downloadVideo("palier3/v1p3.mp4");

videos.destroy();

VideoStatusBody

Every response separates two independent facts — a missing local file and a missing connection are never merged into one generic field:

| Field | Type | Meaning | |-------|------|---------| | available | boolean | True only when downloaded and ready to play | | downloaded | boolean | Does the file exist in local storage? | | downloading | boolean | Internal signal only — getVideo()/downloadVideo() never return this as true; it can only appear in the SuperApp's own debug tooling | | hasInternet | boolean | Is the device currently online? | | localUri | string \| null | WebView-playable URI when downloaded | | error | string \| null | "VIDEO_NOT_DOWNLOADED_AND_OFFLINE", "VIDEO_NOT_IN_MANIFEST", "DOWNLOAD_FAILED" (or a more specific code), or null |

Response examples

Downloaded (either it already existed, or getVideo() waited for a fresh download to succeed):

{ "available": true, "downloaded": true, "downloading": false, "hasInternet": true, "localUri": "...", "error": null }

Unavailable — missing + offline (returned immediately, nothing to wait for):

{ "available": false, "downloaded": false, "downloading": false, "hasInternet": false, "localUri": null, "error": "VIDEO_NOT_DOWNLOADED_AND_OFFLINE" }

Download failed — missing + online, but the download itself errored:

{ "available": false, "downloaded": false, "downloading": false, "hasInternet": true, "localUri": null, "error": "DOWNLOAD_FAILED" }

videoPath format

Pass either the full relative path or just the file name as listed in the manifest media.videos.byPalier:

"palier3/v1p3.mp4"   → direct palier lookup
"v1p3.mp4"           → resolved by searching every palier

Config

| Option | Type | Default | Description | |--------|------|---------|-------------| | partnerCode | string | required | Your partner code (e.g. "bewize") | | timeout | number | 120000 | Request timeout in ms — must cover a full video download, not just a status check | | debug | boolean | false | Enable console logs |


SuperApp integration

If you are implementing the SuperApp host side, import from the /superapp subpath to get the handler types:

import {
  ContentMessageType,
  MediaMessageType,
  BackgroundMessageType,
  SoundMessageType,
  RiveMessageType,
  VideoMessageType,
} from "@superapp_men/content-provider/superapp";

import type {
  ContentRequestMessage,
  MediaRequestMessage,
  BackgroundRequestMessage,
  SoundRequestMessage,
  RiveRequestMessage,
  VideoRequestMessage,
  VideoStatusBody,
} from "@superapp_men/content-provider/superapp";

The SuperApp's IframeCommunicationHandler handles all message types:

| Message type | Handler | Filesystem path | |-------------|---------|-----------------| | content:request | Reads content.json | partner-apps/{code}/content/current/content.json | | media:request | Reads media file as base64 | partner-apps/{code}/media/{path} | | background:request | Fetches WebP from static assets | /shared_backgrounds/{name}.webp | | sound:request | Fetches WAV from static assets | /shared_sound_design/{name}.wav | | rive:request | Fetches both WASM runtime files | /rive-wasm/rive.wasm + /rive-wasm/rive_fallback.wasm | | video:request | Video status; auto-downloads when missing + online | partner-apps/{code}/videos/{path} | | video:download | Explicit download request (manifest-validated) | partner-apps/{code}/videos/{path} |


Message protocol

All messages follow the same pattern:

Request (partner → SuperApp):

{
  "type": "content:request",
  "requestId": "1751234567890-abc123",
  "payload": { "partnerCode": "bewize" },
  "timestamp": 1751234567890
}

Response (SuperApp → partner):

{
  "type": "content:response",
  "requestId": "1751234567890-abc123",
  "statusCode": 200,
  "body": { "content": { ... } },
  "timestamp": 1751234567891
}

Rive request (partner → SuperApp):

{
  "type": "rive:request",
  "requestId": "1751234567890-abc123",
  "payload": { "partnerCode": "bewize" },
  "timestamp": 1751234567890
}

Rive response (SuperApp → partner):

{
  "type": "rive:response",
  "requestId": "1751234567890-abc123",
  "statusCode": 200,
  "body": {
    "primary":  { "data": "<base64>", "mimeType": "application/wasm", "encoding": "base64" },
    "fallback": { "data": "<base64>", "mimeType": "application/wasm", "encoding": "base64" }
  },
  "timestamp": 1751234567891
}

Video request (partner → SuperApp):

{
  "type": "video:request",
  "requestId": "1751234567890-abc123",
  "payload": { "partnerCode": "bewize", "videoPath": "palier3/v1p3.mp4" },
  "timestamp": 1751234567890
}

Video response (SuperApp → partner):

{
  "type": "video:response",
  "requestId": "1751234567890-abc123",
  "statusCode": 200,
  "body": {
    "available": true,
    "downloaded": true,
    "downloading": false,
    "hasInternet": true,
    "localUri": "http://localhost/_capacitor_file_/.../videos/palier3/v1p3.mp4",
    "error": null
  },
  "timestamp": 1751234567891
}

video:download / video:download-response follow the same shape with the explicit-download semantics.

Error responses use statusCode 400 / 404 / 500 and body: { "error": "...", "detail": "..." }.


License

MIT — SuperApp Team