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

@smartico/react-native

v0.0.3

Published

Smartico SDK for React Native — the WSAPI surface (mini-games, missions, tournaments, store, inbox, …) plus connection lifecycle, automatic identify, server pushes, deep links, engagement popups and push-notification support, over a React Native WebSocket

Readme

@smartico/react-native

Smartico SDK for React Native. Exposes the same api.* surface as the browser SDK (_smartico.api.*) — mini-games, missions, tournaments, store, inbox, raffles, clans, leaderboards… — plus connection lifecycle, automatic identify, and server pushes. Pure JavaScript: it uses React Native's global WebSocket, so there are no native modules to link.

Install

npm install @smartico/react-native

@smartico/public-api is pulled in automatically as a dependency.

Quick start

import { Smartico } from '@smartico/react-native';

// One call sets everything up. Identification is automatic: the SDK calls your
// `getUser` after connecting and on every reconnect — no manual identify().
Smartico.init('<your-label-key>', {
  brandKey: '<your-brand-key>',
  getUser: async () => {
    if (!isLoggedIn()) return null;            // stay anonymous until logged in
    const { extUserId, hash } = await fetchSmarticoCreds(); // from YOUR backend
    return { extUserId, hash };
  },
});

// Call any WSAPI method:
const games = await Smartico.api.getMiniGames();
const missions = await Smartico.api.getMissions();

// React to live server pushes:
Smartico.on('props_change', (data) => {
  setPoints(data.props.ach_points_balance);
});

The auth hash

getUser returns { extUserId, hash }. The hash authenticates the user and must be generated on your backend (it needs the label's secret, which must never ship in the app). Your app fetches it over HTTPS and hands it to the SDK — the SDK only forwards it. The hash has an expiry, which is why getUser is a function: the SDK calls it again on reconnect to get a fresh one.

API

| Member | Description | |--------|-------------| | Smartico.init(labelKey, opts) | Connect. opts: brandKey, getUser, deviceId?, requestTimeoutMs?, reconnect?, debug? (connection lifecycle logs), traceFrames? (with debug: dump every socket frame — very chatty), wsUrl? / WebSocketImpl? (custom endpoint / Node, e.g. the ws package for smoke tests). | | Smartico.api.* | The full WSAPI (all data methods): getMiniGames, getMissions, getTournamentsList, getStoreItems, getInboxMessages, getRaffles, getClans, getLeaderBoard, … | | Smartico.on(event, cb) / off(event, cb) | Subscribe to server pushes. event: 'props_change', 'engagement', 'reload_achievements', 'show_spin', 'jp_win', 'prize_drop_win', … or a raw ClassId. The callback gets the full message (e.g. for props_change, points are in data.props). | | Smartico.getPublicProps() | Snapshot of the user's public properties (points, level, inbox count, …), accumulated from identify + props_change pushes. | | Smartico.event(type, payload?) | Send a client event, e.g. event('client_action', { action: 'opened_shop' }). | | Smartico.logout(payload?) | Log the current user out (also clears the popup queue and dedupe). | | Smartico.raw | The same data methods without the 30-second response cache. The cache is not invalidated by server pushes in this SDK — for push-driven refreshes and fast-polling live UIs (e.g. mission progress while the user plays), call through raw. | | Smartico.sendRaw(msg) | Forward a pre-built protocol message to the socket verbatim — needed when hosting wrapper pages (SEND_TO_SOCKET, bcid 6). | | Smartico.changeUsername(name) | Change the user's public display name (persists server-side). | | Smartico.getLabelSetting(key) | Public label settings received on connect (CDN URLs, feature flags, …). | | Smartico.isDuplicateEngagement(uid) | The shared engagement dedupe gate — use it for other engagement-bearing pushes (see cid 105 below). |

| Smartico.dp(raw) | Execute a deep link — see Deep links. | | Smartico.configureDp(bindings) / registerDpHandler(fn) | Teach the deep-link router your UI — see Deep links. | | Smartico.on('engagement', cb) | Deduped stream of campaign engagements (cid 110, all activity types) — subscribe to this, not raw 110. | | Smartico.pendingEngagements() / takeEngagement() / onEngagementsChanged(fn) | The popup queue — see Engagement popups. | | Smartico.createPopupSession(payload, hooks) | Drive one popup's wrapper-page conversation — see Engagement popups. | | Smartico.createWidgetSession(hooks) | Drive one hosted widget's bridge conversation — see Hosting widget deep links. | | Smartico.registerPushToken(token, platform, appPackageId?) | Register a native push token (FCM/APNs), platform from PUSH_PLATFORM. | | Smartico.reportPushEngagement(type, ref) | Report push delivered / impression / click analytics — see Push notifications. |

Every api.* call returns a Promise that always settles — it resolves with the response, or rejects on timeout (requestTimeoutMs, default 30 s) or disconnect. It never hangs.

Server events

Subscribe with Smartico.on(event, cb) — safe to call before init(), and subscriptions survive re-init. The ones you'll actually use:

| Event | Fires when | Typical reaction | |---|---|---| | 'identify' | The user is identified — on login AND on every reconnect/re-identify | Refresh user-bound data (missions, tournaments, balances), register the push token | | 'props_change' | Public properties changed (points, level, inbox counter, …) | Merge data.props (only the changed keys) into your state | | 'engagement' | A fresh campaign engagement arrived (deduped cid 110, any activity type) | Popups queue themselves — see Engagement popups; any type can mean new inbox content | | 'reload_achievements' | Missions/badges changed server-side | Refetch missions and badges (via Smartico.raw — see the cache note above) | | 105 | Server-initiated deep link | if (!Smartico.isDuplicateEngagement(msg.engagement_uid)) Smartico.dp(msg.payload?.dp ?? msg.dp) | | 'show_spin' | A campaign asks to open a mini-game now | Smartico.dp('dp:gf_saw&id=' + msg.saw_template_id + '&standalone=true') | | 'jp_win', 'prize_drop_win' | Jackpot / prize-drop wins | Optional celebratory UI | | any raw ClassId number | That protocol message | The full message, as-is |

Full api.* reference. Smartico.api re-exposes the exact WSAPI surface of @smartico/public-api — every method with its parameters and return types is documented there. Live examples: expo.smartico.ai.

Deep links (dp:)

Operators author deep links everywhere in the BackOffice — popup buttons, inbox-message buttons, mission CTAs, campaign pushes. A deep link is a plain string:

dp:<action>[&key[=value]]*     e.g.  dp:gf_saw&id=42&standalone=true

A bare &flag means 'true'. Strings starting with http(s):// or / are also valid deep links (they parse as a go navigation). Whatever UI surface hands you such a string, execute it with:

Smartico.dp('dp:gf_missions');   // → true if something handled it

The SDK owns the protocol — grammar, the catalog of known actions, the dispatch order. It does NOT own your UI: it can't know how your app opens a screen. You inject that once at startup:

import { Linking } from 'react-native';
import { Smartico } from '@smartico/react-native';
import type { DeepLink, DpScreen } from '@smartico/react-native';

Smartico.configureDp({
  // dp:go / plain URLs. Tip: intercept your operator-site URLs onto native
  // screens here, and send everything else to the browser.
  openUrl: (url) => Linking.openURL(url).catch(() => {}),

  // Actions only the gamification widget can render (mini-games, store, …).
  // Host the vendor wrapper page in a WebView and pass dp.raw in its URL
  // (see buildWrapperUrl). Return false to DECLINE an action — it then falls
  // through to your custom handlers (e.g. a browser fallback).
  openWidget: (dp: DeepLink) => {
    if (!MY_IN_APP_WIDGETS.has(dp.action)) return false;
    navigation.navigate('SmarticoWebView', { dp: dp.raw });
    return true;
  },

  // Natively rendered screens. `screen` is a LOGICAL name from the catalog —
  // map it to your routes. Return false to decline (the dp is then dropped).
  openScreen: (screen: DpScreen) => {
    const route = { missions: 'Missions', tournaments: 'Tournaments', /* … */ }[screen];
    if (!route) return false;
    navigation.navigate(route);
    return true;
  },

  // dp:ask_push_permissions — a campaign asks the app to show the OS
  // notification-permission prompt (then register the token).
  requestPushPermissions: () => askAndRegisterPushToken(),
});

Operator-specific actions that aren't in the catalog go through custom handlers:

const unregister = Smartico.registerDpHandler((dp) => {
  if (dp.action === 'deposit') { openCashier(); return true; }
  return false; // not mine — let the next handler try
});

Dispatch order

Smartico.dp(raw) parses the string and checks, in order — first hit wins:

| # | Check | Handled by | |---|-------|-----------| | 1 | Service no-ops: ok, cancel, close, close_me, gf_close | swallowed (engagement-tracking taps) | | 2 | go (incl. plain URLs) | your openUrl | | 3 | action (dp:action&action=x) | sent to the socket as a client_action event | | 4 | ask_push_permissions | your requestPushPermissions (no-op if not bound) | | 5 | Native-screen catalog: gf_missions, gf_tournaments, gf_jackpots, gf_raffle, gf_levels, gf_badges, gf_change_avatar, gf_change_nickname, inbox, gf_activity, gf_board* | your openScreen with the logical name ('missions', 'leaderboard', …) | | 6 | Widget-only catalog: gf, gf_saw, gf_section, gf_store, gf_matchx, gf_quiz, gf_bonuses, gf_settings, gf_clans | your openWidget; return false to decline → falls through to custom handlers | | 7 | Custom handlers, in registration order | first one returning true | | 8 | Nothing matched | dropped; returns false. With DpRouter.debug = true (set it to __DEV__) the drop is logged. |

There is deliberately no fallback: an unknown action is dropped — the same behavior as the Smartico web SDK.

Two rules worth knowing

  • Widget-emitted dps stay in the widget. Buttons INSIDE a hosted widget (respin offers, section jumps) also arrive as deep links (native-bridge bcid 4). If the action is widget-family (isWidgetAction(action)), reload the SAME WebView with the new dp instead of routing globally — otherwise the user gets yanked out of the mini-game.
  • CTA dps close their popup first. PopupBridgeSession already does this for you: on bcid 4 it fires onClose, then routes the dp.

Hosting widget deep links (mini-games & co)

Actions from the widget catalog (gf_saw, gf_quiz, gf_store, …) are rendered by Smartico's gamification wrapper page (wrapper-gf.html). Your openWidget binding decides where that page lives. Two options, freely mixable per action:

A. In-app WebView — feels native, required for mini-games you launch from your own UI:

const url = buildWrapperUrl({
  base: Smartico.getPublicProps().native_app_gf_url, // server-built URL from identify (preferred)
  labelKey, brandKey, extUserId,
  hash,                       // REQUIRED here (unlike the popup wrapper)
  dp: dp.raw,                 // the widget executes the deep link itself
});
// force_mobile=true is appended automatically: the required SMTO-WRAPPER UA
// matches no mobile token, so without it the widget renders desktop layout.

// The SDK session handles the whole postMessage conversation — you supply
// what the UI should do:
const session = Smartico.createWidgetSession({
  onReady: () => hideLoader(),           // widget rendered
  onClose: () => navigation.goBack(),    // widget asks to close
  onNavigateInWidget: (dpRaw) => {       // in-widget flows (respin offers, …)
    showLoader();
    navigation.setParams({ dp: dpRaw }); // reload THIS WebView with the new dp
  },
});

<WebView
  source={{ uri: url }}
  userAgent={SMTO_WRAPPER_UA}
  onMessage={(e) => session.handleMessage(e.nativeEvent.data)}
/>

Everything else — analytics forwarding (SEND_TO_SOCKET), the stay-in-widget rule for widget-family deep links, routing other deep links natively after closing — happens inside the session. Keep a loader over the WebView until onReady (with a timeout fallback for bad deep links).

B. Phone browser as a last-resort fallback — zero UI to build; covers widget sections you don't embed (store, clans, …). Have openWidget decline them (return false) and register the fallback as your LAST custom handler, so any real handler wins simply by being registered before it:

Smartico.registerDpHandler((dp) => {
  if (!isWidgetAction(dp.action)) return false;
  Linking.openURL(buildWrapperUrl({ labelKey, brandKey, extUserId, hash, dp: dp.raw }));
  return true;
});

In a real browser the page identifies via the hash URL param, picks the mobile layout from the real user agent, and reports its own impressions/clicks — no bridge code needed. Note the hash is a live credential with an expiry — treat the URL accordingly.

Engagement popups

Campaigns deliver engagements over the socket as cid 110 pushes; activityType 30 is an on-screen popup (operator-authored HTML). The SDK handles the whole pipeline itself — you only render a WebView.

What the SDK does automatically once init() runs:

  • consumes every cid 110, dedupes by engagement_uid (the server re-delivers all pending engagements on every identify session — without dedupe each popup shows several times);
  • re-emits every fresh engagement (any activity type) as the facade event 'engagement' — use it for things like "inbox has new content";
  • queues popups (activityType 30) in an internal EngagementQueue;
  • clears the queue and the dedupe history on a user boundary (identify as a different user, or logout());
  • inside a popup session: injects the payload, forwards impression/click analytics to the socket (bcid 6 → cid 103/104 — BO statistics work with no extra code), and routes CTA deep links through the dp router.

What your app does — a minimal popup host:

function PopupHost() {
  const [payload, setPayload] = useState(null);
  const [visible, setVisible] = useState(false);
  const webRef = useRef<WebView>(null);

  // 1. Pump: when the queue has items and nothing is showing, take one.
  //    WHEN to show is your policy (e.g. hold while the device is in
  //    landscape, show one at a time).
  useEffect(() => Smartico.onEngagementsChanged(() => {
    if (!payload && Smartico.pendingEngagements() > 0) {
      setPayload(Smartico.takeEngagement());
    }
  }), [payload]);

  // 2. One SDK session per popup — it drives the wrapper-page conversation.
  const session = useMemo(() => payload && Smartico.createPopupSession(payload, {
    injectJs: (js) => webRef.current?.injectJavaScript(js),
    onReadyToShow: () => setVisible(true),          // fade the WebView in
    onClose: () => { setVisible(false); setPayload(null); },
  }), [payload]);

  if (!payload) return null;
  return (
    <WebView
      ref={webRef}
      source={{ uri: buildWrapperUrl({ wrapper: WRAPPER_POPUP_URL,
        labelKey, brandKey, extUserId }) }}
      userAgent={SMTO_WRAPPER_UA}                    // required: device_type WRAPPER
      onMessage={(e) => session?.handleMessage(e.nativeEvent.data)}
      style={{ backgroundColor: 'transparent', opacity: visible ? 1 : 0 }}
      originWhitelist={['*']}
    />
  );
}

The wrapper page (wrapper-popup.html, hosted by Smartico) talks to you over postMessage; PopupBridgeSession.handleMessage understands the protocol: PAGE_READY → inject payload → READY_TO_BE_SHOWN → show → CLOSE_ME / EXECUTE_DEEP_LINK / SEND_TO_SOCKET. Keep the WebView invisible until onReadyToShow — the user should never see a half-rendered page.

Push notifications

Pushes (activityType 40) are delivered through FCM/APNs, not the socket — they arrive when the app may be closed. The split: you obtain the device token and listen for notifications (needs a native module, e.g. expo-notifications); the SDK owns the protocol — token registration and lifecycle analytics.

import * as Notifications from 'expo-notifications';
import { PUSH_PLATFORM, Smartico } from '@smartico/react-native';

// 1. After login (identify): permission → token → register.
//    The registration (cid 1003) is queued until identify, so the token is
//    always bound to the right user — safe to call right after init().
const perm = await Notifications.requestPermissionsAsync();
if (perm.granted) {
  const t = await Notifications.getDevicePushTokenAsync();
  Smartico.registerPushToken(t.data, PUSH_PLATFORM.NATIVE_ANDROID, 'your.package.id');
}

// 2. Report lifecycle analytics. The push's FCM data payload carries
//    engagement_uid / message_id / action — pass them through:
function refOf(content) {
  const d = content?.data ?? {};
  return { engagement_uid: d.engagement_uid, message_id: d.message_id, action: d.action };
}

// Arrived while the app is open (you chose to display it):
Notifications.addNotificationReceivedListener((n) => {
  const ref = refOf(n.request.content);
  Smartico.reportPushEngagement('engagement_delivered', ref);
  Smartico.reportPushEngagement('engagement_impression', ref);
});

// User tapped (background or cold start) — the tap proves the whole chain,
// so retro-report everything, then execute the push's deep link:
Notifications.addNotificationResponseReceivedListener((resp) => {
  const ref = refOf(resp.notification.request.content);
  Smartico.reportPushEngagement('engagement_delivered', ref);
  Smartico.reportPushEngagement('engagement_impression', ref);
  Smartico.reportPushEngagement('engagement_action', ref);
  if (ref.action) Smartico.dp(ref.action);
});

Details the SDK takes care of:

  • reports go over HTTP (the public API endpoint derived from your label key), because at tap-time the socket may not exist;
  • reports fired before identify (a tap cold-started the app) are queued and flushed automatically on the first identify;
  • event types: engagement_delivered, engagement_impression, engagement_action (include ref.action), engagement_failed.

Coverage caveat: with standard notification-type pushes Android/iOS display the notification without waking the app, so impressions can only be reported for foreground arrivals and tapped notifications — a notification the user saw in the tray and dismissed is not observable by any app.

Notes

  • No native modules — the core is pure JS and uses the global WebSocket React Native provides. Works in Expo (Go) and bare React Native alike.
  • Native push-notification tokens: obtain the FCM/APNs token yourself (e.g. expo-notifications) and hand it to Smartico.registerPushToken(token, PUSH_PLATFORM.NATIVE_ANDROID, 'your.package.id'); report notification impressions/clicks with Smartico.reportPushEngagement(...).
  • Multiple labels in one app: the Smartico facade wraps a single connection. For more than one label, construct SmarticoConnection instances directly — the facade is a thin static wrapper over the same class.