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

@nolag/react-native

v1.0.0

Published

React Native bindings for the NoLag real-time messaging SDK

Readme

@nolag/react-native

React Native bindings for @nolag/js-sdk.

This is a convenience layer, not a required one. As of @nolag/js-sdk 1.12.0 the core SDK works on React Native by itself. What this package adds is the platform behaviour you would otherwise have to write yourself, and probably get wrong on iOS.

npm install @nolag/react-native @nolag/js-sdk

What it does

| | Without this package | With it | |---|---|---| | TextEncoder/TextDecoder | msgpack constructs both at module scope; missing globals throw on import, before your code runs | polyfilled if absent, untouched if present | | App background/foreground | no signal at all: the core defaults to the Page Visibility API, which React Native does not have | AppState wired to the SDK's lifecycle adapter | | Token expiry during suspend | JS timers do not fire reliably while suspended, so a scheduled refresh can be skipped and the held token is already dead | rechecked on every foreground | | Network flap | reconnect backoff grows to 30s and keeps waiting after the radio returns | NetInfo collapses the backoff and retries immediately | | Cold start | a round trip to your backend to mint a token, on the critical path | cached until shortly before exp |

Usage

import { NoLag } from '@nolag/react-native';

const client = NoLag(async () => {
  const res = await fetch('https://your-api.example/nolag-token');
  return (await res.json()).token;
});

await client.connect();

It returns a genuine NoLagSocket, so it drops straight into the app SDKs:

import { NoLag } from '@nolag/react-native';
import { NoLagChat } from '@nolag/chat';

const client = NoLag(tokenProvider);
const chat = new NoLagChat({ client, username: 'Alice' });

await client.connect();
await chat.ready();

Never ship a long-lived token

Pass a TokenProvider, not a token string. An actor access token embedded in an app bundle is extractable from the IPA or APK by anyone who downloads your app. The provider should call your backend, which mints a short-lived client token (JWT) using a project signing key that never leaves your server.

Network reachability

NetInfo is not a dependency of this package, optional or otherwise. Metro resolves require() statically and fails the build on an unresolved module, so the usual try/catch-around-an-optional-import trick does not work as it does under Node. Pass the module in instead:

import NetInfo from '@react-native-community/netinfo';

const client = NoLag(tokenProvider, { netInfo: NetInfo });

Omit it and the client simply has no reachability signal: it still reconnects, just on the normal backoff schedule.

This is why the package pulls in no native modules of its own, and why it works in Expo Go.

Token caching

Skip the token round trip on warm launches. Storage is injected for the same Metro reason as NetInfo.

import { MMKV } from 'react-native-mmkv';
import { NoLag, createCachedTokenProvider, fromMMKV } from
'@nolag/react-native';

const storage = new MMKV();

const client = NoLag(
  createCachedTokenProvider({
    provider: fetchTokenFromYourBackend,
    storage: fromMMKV(storage),
  })
);

AsyncStorage satisfies the TokenStore contract directly, no adapter needed:

import AsyncStorage from '@react-native-async-storage/async-storage';

createCachedTokenProvider({ provider, storage: AsyncStorage });

Opaque (non-JWT) tokens are never cached: without an exp claim there is no safe way to know when to stop using one. Storage failures are non-fatal, so a locked keychain or a full disk degrades to an uncached fetch rather than a failed connection.

Disconnecting while backgrounded

const client = NoLag(tokenProvider, { disconnectOnHidden: true });

Off by default. Note that iOS reports inactive for transient states (app switcher, incoming call, Control Centre, biometric prompt); those are ignored, so only a real background drops the socket.

Cleaning up

disconnect() deliberately keeps the AppState and NetInfo subscriptions alive, because a backgrounded client has to be able to come back. When the client itself is going away, call destroy():

useEffect(() => {
  const client = NoLag(tokenProvider);
  client.connect();
  return () => client.destroy();
}, []);

Options

Everything NoLagOptions accepts, plus:

| Option | Type | Default | |---|---|---| | netInfo | NetInfoModule | none (no reachability signal) | | lifecycle | LifecycleAdapter \| null | AppState adapter; null disables | | network | NetworkAdapter \| null | from netInfo if given; null disables |

lifecycle and network take precedence over netInfo when set explicitly.

Advanced: bringing your own adapters

NoLag() builds both adapters for you, so most apps never need this section. Reach for it when your app-state signal does not come from AppState, when you use something other than NetInfo, or when you want the same decisions the SDK makes available to your own reconnect logic.

Lifecycle

import { createAppStateLifecycleAdapter, mapAppState } from
"@nolag/react-native";

// Equivalent to the default, but with your own AppState-shaped source. Useful
// for tests, or when app state comes from somewhere other than React Native.
const lifecycle = createAppStateLifecycleAdapter(myAppStateLike);
const client = NoLag(tokenProvider, { lifecycle });

createAppStateLifecycleAdapter(appState?) takes anything with addEventListener, and defaults to React Native's AppState.

mapAppState(status) returns the lifecycle state for an AppState status, or null for statuses that should not move the client. That null is the whole point: iOS reports "inactive" for the app switcher, an incoming call, Control Centre and the biometric prompt. Treating those as background would tear the socket down every time someone swipes down on Control Centre, so only a real "background" counts. Use it directly if you are making the same distinction elsewhere in your app.

Reachability adapter

import NetInfo from "@react-native-community/netinfo";
import { createNetInfoNetworkAdapter, isReachable } from "@nolag/react-native";

const network = createNetInfoNetworkAdapter(NetInfo);
const client = NoLag(tokenProvider, { network });

// The same judgement, for your own retry logic:
NetInfo.addEventListener((state) => {
  if (isReachable(state)) retryMyOwnThing();
});

isReachable(state) is deliberately optimistic. isInternetReachable is null while NetInfo is still probing, and treating that as unreachable would swallow the signal on exactly the transition that matters, the radio coming back. So connected-but-unknown counts as reachable. A wasted retry costs one connection attempt; a missed signal costs up to thirty seconds of backoff.

NetInfoModule and NetInfoState are exported too, describing just the slice of NetInfo this package touches, so you can type a wrapper of your own without depending on NetInfo's types.

Passing netInfo and building the adapter yourself are equivalent. The reason NetInfo is injected at all rather than imported is that Metro resolves require() statically and fails the build on an unresolved module, so a try/catch around an optional import does not work the way it does under Node. Injecting it keeps this package free of native modules, which is also what lets it run in Expo Go.

Token helpers

import { decodeJwtExp } from "@nolag/react-native";

const expiresAt = decodeJwtExp(token); // unix seconds, or null

decodeJwtExp(token) reads the exp claim without verifying the signature, and returns null for opaque tokens or anything that does not parse. It is what createCachedTokenProvider uses to decide when a cached token is spent, and it is exported so you can make the same call, for example to refresh something else on the same schedule.

To persist tokens somewhere other than AsyncStorage or MMKV, implement TokenStore, which is three methods and may be sync or async:

import type { TokenStore } from "@nolag/react-native";

const store: TokenStore = {
  getItem: (key) => secureStorage.read(key),      // string | null, or a promise
  setItem: (key, value) => secureStorage.write(key, value),
  removeItem: (key) => secureStorage.delete(key),
};

fromMMKV(mmkv) takes anything matching the exported MmkvLike shape (getString, set, delete), so a real react-native-mmkv instance drops straight in. It is worth preferring at launch because it is synchronous and keeps the cached token read off the async path.

createCachedTokenProvider also takes key (default "nolag.client-token"), skewSeconds (default 60, how early to treat a token as spent so it cannot expire mid-connect) and now for tests.

WebRTC is not supported yet

WebRTCManager is not exported here, and the core SDK's React Native build omits it too. Its Node path does a bare require of the wrtc package, which Metro collects statically. allowOptionalDependencies is on under @expo/metro-config but off under bare @react-native/metro-config, so shipping it would bundle fine on Expo and fail the build on bare React Native.

Voice and video on React Native need react-native-webrtc, which is separate work. Everything else in the SDK is available.

Requirements

  • React Native >= 0.71
  • @nolag/js-sdk >= 1.12.0 (peer dependency)

@nolag/js-sdk must be a peer, resolving to exactly one copy in your tree. NoLagSocket has private fields, so TypeScript compares it close to nominally: two copies make passing a client into an app SDK fail to typecheck with a thoroughly unhelpful error. If that happens, add a resolutions (Yarn) or overrides (npm) entry pinning a single version.

License

MIT