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

revnix-capacitor

v0.2.0

Published

Revnix subscriptions and entitlements for Capacitor.

Readme

revnix-capacitor

Revnix subscriptions and entitlements for Capacitor — the same client contract as every Revnix SDK, one implementation per platform:

Until the native SDKs ship to CocoaPods/SPM and Maven Central, their 0.2.0 sources are vendored inside this plugin (ios/…/Revnix/, android/…/com/revnix/) so it builds standalone. Fix native bugs upstream, then re-copy.

What every platform guarantees: network-first entitlements with an offline cache flagged stale, deliberate rejections (401/403/404/409) never masked by that cache, one fetch per screen of gates (soft TTL + coalescing), a durable purchase retry queue drained idempotently on every launch, and read-your-writes unlock via waitForEntitlements(seq).

Quick start

import { Revnix } from 'revnix-capacitor';

await Revnix.configure({
  apiKey: 'rvx_pk_live_…',                    // publishable key only
  baseUrl: 'https://your-deployment.convex.site',
});

// Gate. Never rejects; unknown or unreachable means locked.
const { entitled } = await Revnix.isEntitled({ entitlementId: 'pro' });

// Paywall. The resolve identifies the customer so a running A/B experiment
// can serve a sticky variant — `placement.experiment` names it (or is null).
const placement = await Revnix.resolvePlacement({ placementKey: 'main_paywall' });
await Revnix.logPaywallShown({ placementKey: 'main_paywall' });

// After a store purchase completes (StoreKit 2 / Play Billing):
const result = await Revnix.registerPurchase({
  source: 'apple',
  token: originalTransactionId,
  productId,
  transactionId,
  signedTransactionInfo: jws,               // the proof path
});
await Revnix.waitForEntitlements({ seq: result.seq }); // read-your-writes unlock

Install

To use npm

npm install revnix-capacitor

To use yarn

yarn add revnix-capacitor

Sync native files

npx cap sync

API

Failure taxonomy: rejected calls carry code (network · timeout · rate_limited · server · bad_response · auth · not_found · purchase_blocked · invalid) and data.isRetryable. Deliberate rejections (401/403/404/409) are never masked by a cache — a kill-switch must stay a kill-switch.

configure(...)

configure(options: ConfigureOptions) => Promise<void>

Configure once at startup. Native platforms drain the purchase retry queue and report the install as launch chores (both idempotent).

| Param | Type | | ------------- | ------------------------------------------------------------- | | options | ConfigureOptions |


getCustomerId()

getCustomerId() => Promise<{ customerId: string; }>

Anonymous id, minted and persisted natively on first use.

Returns: Promise<{ customerId: string; }>


logout()

logout() => Promise<{ customerId: string; }>

Mint a fresh anonymous identity. Call at sign-out, or the next user inherits the previous one's cached entitlements.

Returns: Promise<{ customerId: string; }>


getEntitlements()

getEntitlements() => Promise<CustomerEntitlements>

Network-first entitlement read. Transient failures serve the cache flagged stale; deliberate rejections (401/403/404/409) reject.

Returns: Promise<CustomerEntitlements>


getCachedEntitlements()

getCachedEntitlements() => Promise<{ snapshot: CustomerEntitlements | null; }>

Last cached snapshot with the offline policy applied; snapshot: null when this customer never had a live read. Never touches the network.

Returns: Promise<{ snapshot: CustomerEntitlements | null; }>


isEntitled(...)

isEntitled(options: { entitlementId: string; }) => Promise<{ entitled: boolean; }>

Gate helper — never rejects. Unknown or unreachable resolves false, so gates fail closed.

| Param | Type | | ------------- | --------------------------------------- | | options | { entitlementId: string; } |

Returns: Promise<{ entitled: boolean; }>


waitForEntitlements(...)

waitForEntitlements(options: { seq: number; }) => Promise<CustomerEntitlements>

Read-your-writes: poll until the ledger reflects seq. Returns the last read (rather than rejecting) if the schedule runs out.

| Param | Type | | ------------- | ----------------------------- | | options | { seq: number; } |

Returns: Promise<CustomerEntitlements>


registerPurchase(...)

registerPurchase(options: RegisterPurchaseInput) => Promise<RegisterPurchaseResult>

Register a store purchase. On a transient failure the claim is queued durably and the call rejects — it is never lost.

| Param | Type | | ------------- | ----------------------------------------------------------------------- | | options | RegisterPurchaseInput |

Returns: Promise<RegisterPurchaseResult>


retryPendingPurchases()

retryPendingPurchases() => Promise<{ delivered: number; }>

Drain the persistent retry queue. Safe on every launch/foreground — the server dedupes on the purchase key. (Web reports delivered: 0; revnix-react drains internally without a count.)

Returns: Promise<{ delivered: number; }>


pendingPurchaseCount()

pendingPurchaseCount() => Promise<{ count: number; }>

How many registrations are still queued. (Web always reports 0.)

Returns: Promise<{ count: number; }>


resolvePlacement(...)

resolvePlacement(options: { placementKey: string; }) => Promise<PlacementResolution>

Resolve a placement to its offering + remote paywall config, with an offline fallback to the last successful resolution.

| Param | Type | | ------------- | -------------------------------------- | | options | { placementKey: string; } |

Returns: Promise<PlacementResolution>


registerInstall(...)

registerInstall(options?: { platform?: string | undefined; appVersion?: string | undefined; } | undefined) => Promise<void>

Fire-and-forget install beacon; recorded once per customer id. (Web: handled automatically by configure.)

| Param | Type | | ------------- | -------------------------------------------------------- | | options | { platform?: string; appVersion?: string; } |


logPaywallShown(...)

logPaywallShown(options?: { placementKey?: string | undefined; paywallId?: string | undefined; } | undefined) => Promise<void>

Fire-and-forget paywall impression. Call when the paywall becomes visible, not when you start loading it.

| Param | Type | | ------------- | ----------------------------------------------------------- | | options | { placementKey?: string; paywallId?: string; } |


setAttributes(...)

setAttributes(options: { attributes: Record<string, string | number | null>; }) => Promise<void>

Set attributes on the current customer. Attributes are what A/B-test audiences target — set country, app_version, locale, or any custom key you want to segment on. null deletes a key.

Rejects, unlike the fire-and-forget beacons: the next placement resolve may depend on these. email and username are reserved (secret key only), and an attribute your backend already set cannot be changed from a device.

| Param | Type | | ------------- | -------------------------------------------------------------------------------------------------- | | options | { attributes: Record<string, string | number | null>; } |


addListener('diagnostic', ...)

addListener(eventName: 'diagnostic', listenerFunc: (diagnostic: RevnixDiagnostic) => void) => Promise<PluginListenerHandle>

Background failures the SDK swallowed (queue drains, telemetry).

| Param | Type | | ------------------ | -------------------------------------------------------------------------------------- | | eventName | 'diagnostic' | | listenerFunc | (diagnostic: RevnixDiagnostic) => void |

Returns: Promise<PluginListenerHandle>


removeAllListeners()

removeAllListeners() => Promise<void>

Interfaces

ConfigureOptions

| Prop | Type | Description | | -------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | apiKey | string | Publishable key (rvx_pk_live_… / rvx_pk_test_…). The key fixes app + environment server-side. Secret keys must never ship in a binary — identify/alias are server-proxied by design and not part of this plugin. | | baseUrl | string | e.g. https://your-deployment.convex.site | | timeoutMs | number | Per-request timeout in ms. Default 10 000. | | entitlementsTtlMs | number | Soft TTL on entitlement reads in ms. Default 30 000; 0 = always fetch. | | offlineMaxCacheAgeMs | number | Snapshots older than this serve as all-inactive. Default 14 days. |

CustomerEntitlements

| Prop | Type | Description | | ------------------ | -------------------------- | ------------------------------------------------------ | | customerId | string | | | cursor | number | Ledger position this read reflects (read-your-writes). | | entitlements | Entitlement[] | | | stale | boolean | True when served from the offline cache. | | fetchedAt | number | Unix ms this snapshot was fetched. |

Entitlement

| Prop | Type | Description | | ------------------- | -------------------------------- | --------------------------------------------------------------- | | entitlementId | string | | | isActive | boolean | | | expiresAt | number | Unix ms; absent for a lifetime purchase or an open-ended grant. | | sources | EntitlementSource[] | |

EntitlementSource

| Prop | Type | | --------------- | -------------------- | | kind | string | | key | string | | isActive | boolean | | expiresAt | number |

RegisterPurchaseResult

| Prop | Type | Description | | -------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------ | | eventId | string | | | seq | number | Ledger position — pass to waitForEntitlements to unlock. | | duplicate | boolean | | | customerId | string | The id to use going forward. The SDK adopts it automatically. | | transferred | boolean | | | ownedByOtherCustomer | boolean | | | refused | string | | | restored | boolean | | | provisional | boolean | Recorded without store proof — live but time-boxed until the store confirms. Expect true for Google device claims. |

RegisterPurchaseInput

| Prop | Type | Description | | --------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------- | | source | RevnixStoreKind | | | token | string | Apple: originalTransactionId · Google: purchaseToken. | | productId | string | | | transactionId | string | Google: this is the purchaseToken too (the established rule). | | occurredAt | number | | | expiresAt | number | | | signedTransactionInfo | string | StoreKit 2 JWS — the proof path; claims without it are provisional. |

PlacementResolution

| Prop | Type | Description | | ------------------ | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | status | string | | | placementKey | string | | | revision | number | Published catalog revision this resolution came from. | | offering | PlacementOffering | | | paywall | PlacementPaywall | Remote paywall render contract — your app renders it in v1. Absent when the placement has no paywall attached (the wire sends null; native bridges omit the key). | | experiment | PlacementExperiment | null | Sticky experiment assignment for this customer; null when no running experiment covers the placement (older servers omit the key — every bridge normalizes that to null). |

PlacementOffering

| Prop | Type | | ----------------- | ------------------------------- | | offeringId | string | | displayName | string | | packages | PlacementPackage[] |

PlacementPackage

| Prop | Type | | --------------- | ------------------- | | packageId | string | | productId | string |

PlacementPaywall

| Prop | Type | | --------------- | ------------------------------------------------------- | | paywallId | string | | name | string | | config | PaywallConfig |

PaywallConfig

Remote paywall design attached to a placement. Render contract — the app draws this with its own components; prices still come from the store (StoreKit / Play Billing) so the display never disagrees with the charge.

| Prop | Type | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | template | 'focus' | 'feature-list' | 'minimal' | 'hero' | 'timeline' | 'plans' | 'feature-grid' | 'offer' | 'reveal' | Layout — the screen structure to render. The dashboard's template gallery is presets over these layouts; the first three are the original templates and render exactly as before. | | mode | 'dark' | 'light' | Color scheme the paywall renders in. Absent (legacy config) = dark. | | headline | string | | | subheadline | string | | | features | PaywallFeature[] | | | ctaLabel | string | | | highlightPackageId | string | packageId of the visually highlighted package. | | badgeText | string | Badge on the highlighted package, e.g. "SAVE 17%". | | accent | string | Accent hex like "#6478ff"; fall back to the app theme when absent. | | heroImageUrl | string | Hero image URL rendered above the headline in place of the icon tile. | | review | PaywallReview | | | offer | PaywallOffer | | | footer | PaywallFooter | |

PaywallFeature

| Prop | Type | | ----------------- | ------------------- | | icon | string | | title | string | | description | string |

PaywallReview

Social proof, dashboard-configured. Any layout renders the pieces that are set: stars/quote card above the packages, count under the CTA.

| Prop | Type | Description | | ------------ | ------------------- | ------------------------------------------------- | | rating | number | 0–5; rendered as a star row. | | quote | string | | | author | string | | | count | string | e.g. "Join 2M+ users" — small line under the CTA. |

PaywallOffer

Win-back/offer presentation: anchor price struck through on the highlighted package, urgency line above the CTA. Any layout.

| Prop | Type | | ------------------------ | ------------------- | | strikethroughPrice | string | | urgencyText | string |

PaywallFooter

Footer links, dashboard-configured. Absent (legacy config) = show all three. When a URL is set open it directly; otherwise run your own terms/privacy handler.

| Prop | Type | | ----------------- | -------------------- | | showRestore | boolean | | showTerms | boolean | | showPrivacy | boolean | | termsUrl | string | | privacyUrl | string |

PlacementExperiment

A/B experiment assignment (REV-219). The served offering/paywall are already the assigned variant's — this is attribution metadata, not something the app needs to branch on.

| Prop | Type | | --------------- | ------------------- | | key | string | | variantId | string |

PluginListenerHandle

| Prop | Type | | ------------ | ----------------------------------------- | | remove | () => Promise<void> |

RevnixDiagnostic

A background failure the SDK swallowed (queue drains, telemetry beacons).

| Prop | Type | | ------------- | ------------------- | | op | string | | message | string |

Type Aliases

RevnixStoreKind

Wire-identical to the other Revnix SDKs (openapi.yaml is the source).

'apple' | 'google'

Record

Construct a type with a set of properties K of type T

{ [P in K]: T; }