
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
@cross-deck/react-native
Advanced tools
Crossdeck SDK for React Native + Expo — verified subscriptions, entitlements, error capture, and product telemetry across iOS + Android JS apps in one package.
Crossdeck's React Native SDK — verified subscriptions, entitlements,
error capture, and product telemetry across iOS + Android JS apps in
one package. Same SDK surface as @cross-deck/web
@cross-deck/node
— cross-platform teams write identical call-sites.import { Crossdeck } from "@cross-deck/react-native";
Crossdeck.init({
appId: "app_rn_xxx",
publicKey: "cd_pub_live_…",
environment: "production",
});
await Crossdeck.identify("user_847");
const ents = await Crossdeck.getEntitlements();
if (Crossdeck.isEntitled("pro")) showPro();
Crossdeck.track("paywall_shown", { variant: "v3" });
npm install @cross-deck/react-native @react-native-async-storage/async-storage
# Expo
npx expo install @cross-deck/react-native @react-native-async-storage/async-storage
@react-native-async-storage/async-storage is an optional peer
dependency. Without it the SDK falls back to in-memory storage
(identity + queue don't survive app restarts; events queued offline
are lost on cold launch). Install it for production.
| Pillar | Surface |
|---|---|
| Subscriptions & entitlements | getEntitlements(), isEntitled(key), listEntitlements(), onEntitlementsChange(listener), syncPurchases({rail, signedTransactionInfo, purchaseToken}) |
| Behavioural analytics | track(name, properties), identify(userId, options), register(props), group(type, id, traits), consent({...}) |
| Error capture | Auto: ErrorUtils.setGlobalHandler + globalThis.fetch wrap. Manual: captureError(err), captureMessage(msg), setTag, setContext, addBreadcrumb, setErrorBeforeSend |
Every Crossdeck SDK ships these patterns by default:
isEntitled() after init(),
even on a cold launch with no network. A Crossdeck outage can
never fail a paying customer down to free.identify(userId)
switches the entitlement cache to a per-user AsyncStorage slot
(crossdeck:entitlements:<sha256(userId)>) and unconditionally
wipes the in-memory snapshot. A user-switch on a shared device
CANNOT cross-read a prior user's cache, even if the in-memory
clear is somehow skipped — the storage keys are physically
separate. reset() then wipes every per-user slot on the device
(logout-grade).(projectId, eventId).syncPurchases (v1.4.0).
Same signed transaction → same key → backend short-circuits
with idempotent_replay: true on retry. A network blip or app
crash mid-flight that re-fires the same purchase never
double-processes.onPermanentFailure + console.error regardless of debug mode.
No silent infinite-retry-with-growing-backlog.<email> / <card> (sentinel tokens
match the backend's defence-in-depth scrubber). Recursive — nested
{user:{email:...}} payloads ship scrubbed.| Option | Default | Notes |
|---|---|---|
appId | — | Required. From the Crossdeck dashboard. |
publicKey | — | Required. cd_pub_live_… or cd_pub_test_…. |
environment | — | Required. "production" or "sandbox". Must match key prefix. |
baseUrl | https://api.cross-deck.com/v1 | Override for self-hosted setups. |
persistIdentity | true | Set false to defer AsyncStorage writes until after a consent gate. |
storage | AsyncStorage (auto-detected) | Pass a SecureStore / MMKV adapter for higher-security app shells. |
storagePrefix | "crossdeck:" | Key namespace inside the storage adapter. |
autoHeartbeat | true | Disable for CI / tests. |
eventFlushBatchSize | 20 | Flush when buffer reaches this size. |
eventFlushIntervalMs | 5000 | Idle interval before flushing a partial batch. |
appVersion | — | Your app's version (e.g. "1.2.3"). Auto-attached to every event as properties.appVersion. |
platform | auto-detected | Override the Platform.OS detection. |
timeoutMs | 15000 | Per-request HTTP timeout. |
debug | false | Verbose diagnostic logging via the §16 debug-signal vocabulary. |
scrubPii | true | Disable only if your pipeline does its own PII redaction downstream. |
errorCapture | true | Disable if you have a separate error tracker (Sentry, Bugsnag) and don't want duplicates. |
init() returns void but kicks off async hydration (identity, super-
props, entitlement cache, persisted event queue). Every async method
(identify, track, flush, getEntitlements, etc.) awaits the
internal ready promise — callers can fire methods immediately after
init() without manual sequencing.
Sync methods (isEntitled, getSuperProperties, diagnostics)
read in-memory state. Until init() has fired they return sensible
empties.
v1.0 ships WITHOUT auto-session tracking. Wire your nav library's listener into:
import { AppState } from "react-native";
AppState.addEventListener("change", (state) => {
if (state === "background") {
void Crossdeck.flush();
}
});
Auto sessions + deep-link tracking land in v1.1 as opt-in
autoTrack: { sessions, deepLinks }.
Crossdeck.diagnostics();
// {
// started: true,
// anonymousId: "anon_1mqz3…",
// crossdeckCustomerId: "cdcust_abc",
// developerUserId: "user_847",
// sdkVersion: "1.0.0",
// baseUrl: "https://api.cross-deck.com/v1",
// platform: "ios",
// clock: { lastServerTime: 1779…, lastClientTime: 1779…, skewMs: 12 },
// entitlements: { count: 2, lastUpdated: 1779…, stale: false, listenerErrors: 0 },
// events: { buffered: 0, dropped: 0, inFlight: 0, lastFlushAt: 1779…, lastError: null, consecutiveFailures: 0, nextRetryAt: null }
// }
The SDK ships its own contracts registry — every behavioural guarantee the SDK makes (per-user cache isolation, deterministic Idempotency-Key, queue durability, etc.) lives in contracts/**/*.json at the monorepo root and is bundled into every release. The customer's lockfile pins SDK code + contracts atomically — drift between what the SDK does and what it claims is structurally impossible. See contracts/README.md for the full architecture.
CrossdeckContracts — typed access to the bundled registryimport { CrossdeckContracts } from "@cross-deck/react-native";
CrossdeckContracts.all(); // enforced contracts only
CrossdeckContracts.allIncludingHistorical(); // + proposed + retired
CrossdeckContracts.byId("per-user-cache-isolation");
CrossdeckContracts.byPillar("entitlements");
CrossdeckContracts.withStatus("proposed");
CrossdeckContracts.findByTestName("identify(B) makes A's entitlements unreachable from in-memory");
CrossdeckContracts.sdkVersion; // "1.5.0"
CrossdeckContracts.bundledIn; // "@cross-deck/react-native@1.5.0"
The Contract type is exported alongside; the binary-stability promise is documented in contracts/README.md.
Crossdeck.reportContractFailure(input) — surface contract test failuresWhen a contract test asserts and fails — in your CI, a dogfood run, or a customer integration test — fire a typed crossdeck.contract_failed event over the Crossdeck reliability channel. This is one-way operational telemetry to the Crossdeck operations team (Privacy Policy §6, "Flow B"); it never enters your track() pipeline, never shows in your dashboard, never bills against your event quota. The wire shape is schema-locked at contracts/diagnostics/contract-failed-payload-schema-lock.json:
Crossdeck.reportContractFailure({
contractId: "per-user-cache-isolation",
failureReason: "expected isolation across user switch, got cross-read",
runContext: __DEV__ ? "dogfood" : "ci",
runId: process.env.GITHUB_RUN_ID ?? Date.now().toString(36),
testRef: {
file: "tests/entitlement-cache-isolation.test.ts",
name: "identify(B) makes A's entitlements unreachable from in-memory",
},
});
No new endpoint, no special ingest path — the event lands in the same pipeline every other track() call does. It surfaces immediately in the dashboard's live event feed, the breakdown chart (group by contract_id, sdk_platform), and any alert rule with event = crossdeck.contract_failed.
Properties stamped on the wire:
| Property | Source |
|---|---|
contract_id | caller |
sdk_version, sdk_platform | auto-stamped (@cross-deck/react-native ships sdk_platform: "react-native") |
failure_reason, run_context, run_id | caller |
test_file, test_name | set when testRef is provided |
device_class | optional, set by caller (categorical bucket — e.g. "ios-phone", "android-tablet") |
The wire shape is schema-locked at contracts/diagnostics/contract-failed-payload-schema-lock.json; per-SDK assertion tests gate it on every release. Free-form extra keys are not accepted — adding a field requires an amendment to the schema-lock contract first.
For per-test-framework hooks see contracts/README.md § Reporting contract failures.
MIT © VistaApps (Pty) Ltd
FAQs
Crossdeck SDK for React Native + Expo — verified subscriptions, entitlements, error capture, and product telemetry across iOS + Android JS apps in one package.
The npm package @cross-deck/react-native receives a total of 17 weekly downloads. As such, @cross-deck/react-native popularity was classified as not popular.
We found that @cross-deck/react-native demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.