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

@yaps/support-core

v2.0.0

Published

Platform-agnostic client core for Support Yaps: API client, realtime socket, outbox, message store, appearance and push helpers.

Readme

@yaps/support-core

The platform-agnostic client behind the Support Yaps React Native SDK and web widget. It has no DOM or React Native imports; platforms plug in storage, fetch, a WebSocket constructor, file reading, URL and settings opening through a PlatformAdapter. App developers use @yaps/support-react-native or @yaps/support-web, not this package directly.

What's inside, following protocol/PROTOCOL.md:

| Module | | |---|---| | ApiClient | Every /v1/sdk/* call, the error model (SupportError with code, status, retryAfter), retries with 1 s, 2 s, 4 s… backoff capped at 60 s, Retry-After on 429, appearance with If-None-Match. | | SocketClient | hello with the last seq on every open (resume), 25 s pings with a pong timeout, reconnect at 1, 2, 4, 8, 16, 30 s ±20 %, refused after three failed opens (polling fallback). Unknown frames are ignored. | | Outbox | ULID client IDs, persist-then-send, one message at a time in order, retry with backoff, failed after 5 attempts or a permanent 4xx, dedupe by client ID. | | MessageStore | Merges by id, orders by seq, keeps client-ID placeholders until the server confirms them (rows keep a stable key), delivery and read state. | | SupportClient | The session: registration, identity and metadata sync (only changed fields are sent), paywalls and screens, the chat lifecycle, typing throttling, read receipts, unread count, uploads, cards, track, push routing, logout and deletion. | | Helpers | displayText (translation rule), linkSegments (http(s) links in message text), parseSupportPush, resolveAppearance (remote > local > default), PaywallRegistry (availability with timeouts), validateScreens, t() strings in 26 languages, attachment checks and the downscale plan. |

Using it on a new platform

import { SupportClient, MemoryStorage } from "@yaps/support-core";

const client = new SupportClient({
  appId: "app_123",
  platform: {
    platform: "web",
    sdk: "my-platform/1.0.0",
    device: () => ({ language: "en" }),
    systemLanguage: () => "en-GB",
    storage: new MemoryStorage(),
    WebSocket,                                  // optional: omit to poll
    readAttachment: async (a) => blobFor(a),    // bytes to upload
    openUrl: (url) => open(url),
    openSettings: () => openAppSettings(),     // optional: omit where there is none (the web)
    copyText: async (text) => copy(text),       // optional: resolves false when it couldn't
  },
});
client.start();
const detach = client.attach();                 // a chat view is on screen
client.subscribe((state) => render(state));     // items, connection, typing…
client.send("Hello");

Cards

Cards arrive resolved for the device (PROTOCOL.md §5). cardState(card, now) is a pure helper both UIs share; client.cardState(message) calls it with the paywall registry for offer cards, the registered screens and onScreen for screen link cards, and openSettings for settings link cards.

| kind | Card | Render | |---|---|---| | available | any | The button works. Code cards: Copy always, Redeem only when url is set. | | installed | app | "You already have it", no button. | | unavailable | review, offer, link | "No longer available", disabled button. | | unavailable + redeemOn | code | Another platform: the code with Copy and redeemHint(lang, redeemOn) ("Redeem on iPhone"), no Redeem. | | expired | code | Past expiresAt: code struck through, "Expired", no buttons. | | text | unknown type, app with no store link here, code without a code | The message text only. |

  • client.openCard(message): the main button. Review opens url, offer calls onPaywall, app promo and code open url through openUrl. Link cards open url through openUrl, the app's settings page through openSettings, or close the SDK-presented chat (closeChat) and call onScreen(screenId). Emits reviewOpened, paywallOpened, appPromoOpened, codeOpened or linkOpened and sends POST /v1/sdk/actions/:messageId {"action":"open"}.
  • client.copyCode(message): Copy on a code card. Puts the code on the clipboard with copyText, emits codeCopied, sends {"action":"copy"} and resolves whether the clipboard took it (on false, let the user copy by hand). Expired codes can't be copied.
  • cardView(card, state, lang): what a card shows (main button label, Copy, the status line, the spelled-out code label), so both UIs render the same thing.
  • spellCode("STUDENT20") → "S T U D E N T 2 0" for accessibility labels.

| Event | Fields | |---|---| | appPromoOpened | messageId, url, promotedAppId? | | codeCopied | messageId, code, campaignId? | | codeOpened | messageId, code, url, campaignId? | | linkOpened | messageId, target (url, app_settings or screen), url?, screenId?, linkId? |

Link cards

{ "type": "link", "buttonLabel", "target", "url"?, "screenId"?, "linkId"? } draws like an offer card: the message text, then one button. The button is available when the server says available and:

  • url: the URL is https:, mailto:, tel: or sms: (isAllowedLinkUrl); anything else is refused whatever the server sent.
  • app_settings: the platform has openSettings (never on the web).
  • screen: screenId is in client.screens and client.onScreen is set.

client.screens = [{ id: "edit_profile", title: "Edit profile" }] registers the host app's screens (≤ 50, id 1–64 characters of [A-Za-z0-9_.-], title ≤ 60; invalid and duplicate entries are dropped with a warning). They're reported in PATCH /v1/sdk/device screens after registration and whenever they change, like paywalls. Unlike paywalls there's no availability callback.

Links in message text

linkSegments(text) splits message text into plain runs and http(s) links (no trailing punctuation or closing bracket, no Markdown or HTML; email addresses stay text). Both UIs use it, with the same pattern as the Android SDK.

Built-in strings for the cards ("Get it", "Redeem", "Copy", "Copied", "You already have it", "Expired", "Redeem on iPhone / Android / the web", "Select the code to copy it") ship in all 26 languages.

Tests

npm test -w @yaps/support-core

The suite decodes every file in protocol/fixtures/ and drives the socket with a fake WebSocket (reconnect, resume, ping timeouts, refusal) and the client with a fake server.

The package builds to dist/ with tsc; scripts/inline-protocol.mjs copies the protocol type declarations into dist/_protocol so the published package doesn't depend on the private @support-yaps/protocol workspace.