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

@amsemnat/expo-sdk

v0.1.1

Published

Expo module that wraps the am-semnat iOS and Android SDKs — NFC read + PAdES sign for Romanian CEI eID cards.

Readme

@amsemnat/expo-sdk

Expo Module for reading and signing with Romanian electronic identity cards (CEI / eID) over NFC. Wraps the AmSemnatSDK iOS pod and ro.amsemnat:am-semnat-sdk Android library behind a single TypeScript surface.

Status

0.1.0 — pre-stable. Ships in lockstep with the iOS and Android sibling SDKs. Public surface is frozen; non-breaking additions only through 0.x.

Requirements

  • Expo SDK ≥ 52 (managed or bare — both work via expo prebuild)
  • iOS 15.0+ / Android API 24+
  • Device with NFC hardware (NFCTagReaderSession on iOS, NfcAdapter on Android)

Installation

npm install @amsemnat/expo-sdk
npx expo prebuild --clean

Add the config plugin to app.json / app.config.ts:

{
  "expo": {
    "plugins": ["@amsemnat/expo-sdk"]
  }
}

On iOS you must also supply NFCReaderUsageDescription in your app's ios.infoPlist block — Expo does not generate this for you:

{
  "expo": {
    "ios": {
      "infoPlist": {
        "NFCReaderUsageDescription": "Tap your ID card to sign in."
      }
    }
  }
}

The plugin handles the rest:

  • iOS — NFC reader-session formats entitlement + the two Romanian CEI applet AIDs in Info.plist
  • Android — <uses-feature android:name="android.hardware.nfc"> + BouncyCastle META-INF exclusion in the app's packagingOptions

Using in a plain React Native app (no Expo)

This package is an Expo Module, but Expo Modules run in any React Native app once expo-modules-core is installed — the managed workflow isn't required. From a react-native init project:

npx install-expo-modules@latest
npm install @amsemnat/expo-sdk
cd ios && pod install

install-expo-modules wires expo-modules-core into the iOS AppDelegate and Android MainApplication; no other source changes are needed. The TypeScript surface (AmSemnat.readIdentity, .sign, events, errors) behaves identically.

The config plugin still runs if you keep an app.json and invoke npx expo prebuild. Without expo prebuild, apply the NFC entitlement / Info.plist / AndroidManifest edits from Installation manually — the plugin's job is to generate those, not to be required at runtime.

Quick start

Read identity

import { AmSemnat, AmSemnatError } from '@amsemnat/expo-sdk';

try {
  const identity = await AmSemnat.readIdentity({
    can: '123456',    // 6-digit CAN from the card
    pin1: '1234',     // optional PIN1 for eDATA (empty = skip)
    onProgress: (step) => console.log('step:', step),
  });
  // identity.cnp, identity.firstName, identity.lastName, …
  // identity.chipAuthenticated (UX signal only)
  // identity.rawSodBase64 / .rawDg1Base64 / .rawDg2Base64
} catch (err) {
  if (err instanceof AmSemnatError) {
    if (err.code === 'PIN_VERIFY_FAILED') {
      console.log(`PIN1 wrong, ${err.retriesRemaining} retries left`);
    } else if (err.code === 'PACE_AUTH_FAILED') {
      console.log('Wrong CAN — prompt user');
    }
  }
  throw err;
}

Sign a PDF byte-range hash (PAdES)

import { AmSemnat } from '@amsemnat/expo-sdk';

const sig = await AmSemnat.sign({
  can: '123456',
  pin2: '123456',
  pdfHashBase64,                            // 48-byte SHA-384, base64
  signingTime: new Date().toISOString(),
  onProgress: (step) => console.log('step:', step),
});
// sig.signatureBase64         — 96 bytes, raw ECDSA P-384 r‖s
// sig.certificateBase64       — DER-encoded signing cert
// sig.signedAttributesBase64  — DER-encoded SET of CMS signed attributes

Offline passive authentication

import { AmSemnat } from '@amsemnat/expo-sdk';

const result = AmSemnat.verifyPassiveOffline({
  rawSodBase64: identity.rawSodBase64!,
  dataGroups: {
    DG1: identity.rawDg1Base64!,
    DG2: identity.rawDg2Base64!,
    DG14: identity.rawDg14Base64!,
  },
  trustAnchorsBase64: [/* `CSCA Romania`, DER → base64 */],
});
if (!result.valid) console.warn(result.errors);

Server-side verification against the official Romanian trust list is delegated to @amsemnat/verifier-node (shipped separately).

The SDK doesn't bundle any certificates. Two Romanian authorities publish the certs the SDK interacts with, one per PKI:

  • DGP — CSCA Romania, published at https://pasapoarte.mai.gov.ro/csca.html. Self-signed ICAO CSCA that issues the Document Signer embedded in the eMRTD SOD. This is the trust anchor for AmSemnat.verifyPassiveOffline(...). Use the self-signed certificate; the link certificates on that page are only useful when migrating trust from a prior CSCA key.
  • DGEP — RO CEI MAI Root-CA / Sub-CA, published at https://hub.mai.gov.ro/cei/info/descarca-cert. Issues the per-citizen signing certificates stored in the CEI applet and used by AmSemnat.sign(...); those are the anchors for verifying the PAdES signatures the SDK produces.

Your app owns freshness and revocation — re-fetch on a cadence appropriate for your trust window.

Localizing the NFC sheet

iOS owns the NFC reader-session sheet; the SDK writes phase-specific strings into it via an optional messages argument. Defaults are neutral English — production apps should pass a localized NfcMessages.

await AmSemnat.readIdentity({
  can, pin1,
  messages: {
    readyToScan:    t('nfc.holdCard'),
    authenticating: t('nfc.authenticating'),
    scanning:       t('nfc.reading'),
    progressFormat: t('nfc.progressFormat'),  // e.g. '{phase} — {percent}%'
    success:        t('nfc.done'),
    tagLost:        t('nfc.cardMoved'),
  },
  onProgress,
});

progressFormat composes the live per-DG read percentage into the sheet. Two tokens are substituted:

  • {phase} → your scanning string
  • {percent} → the reader's 0-100 progress, rendered as an integer

The English default '{phase} — {percent}%' renders as 'Reading your card… — 40%'. Pass progressFormat: '' to suppress the percentage and show scanning verbatim.

Android has no system NFC sheet in reader mode — render your own progress UI from the onProgress callback. The messages argument is accepted for payload symmetry but discarded on Android.

Cancelling on navigation away

On Android, NfcAdapter.enableReaderMode is Activity-scoped — an in-app navigation away from a reading screen does not tear the session down, so a follow-up readIdentity call on the same Activity fails with SESSION_CANCELLED-style conflicts. On iOS the reader sheet auto-invalidates when the app backgrounds but not on in-app screen transitions.

AmSemnat.cancelCurrentOp() handles both. Typical wiring with expo-router:

import { useFocusEffect } from 'expo-router';
import { useCallback } from 'react';
import { AmSemnat } from '@amsemnat/expo-sdk';

useFocusEffect(
  useCallback(() => {
    return () => {
      AmSemnat.cancelCurrentOp().catch(() => {});
    };
  }, []),
);

The in-flight readIdentity / sign promise rejects with AmSemnatError code 'SESSION_CANCELLED'. No-op when nothing is in flight. Safe to call repeatedly.

Progress events

ReadProgress: paceEstablishing → readingDg14 → chipAuthenticating → readingDg1 → readingDg2 → readingDg7 → readingEData → complete

SignProgress: paceEstablishing → verifyingPin → readingCertificate → signing → complete

DG14 fires before the other DGs because its keys are needed for Chip Authentication. Consumers should localize each value independently rather than depending on the exact order.

Logging

AmSemnat.setLogger({
  debug: console.log,
  info: console.log,
  error: (msg, err) => console.error(msg, err),
});

// Detach:
AmSemnat.setLogger(null);

Messages flow from the native AmSemnatLogger protocols through an onLog event; CAN and PIN bytes are redacted by the native SDKs before they reach you.

What's not in 0.x

  • Active Authentication (DG15) — the iOS fork supports it but the API surface deliberately omits it for 0.x; pass the rawDg*Base64 fields to @amsemnat/verifier-node for transferable proof instead.
  • Low-level tag: overloads from the native SDKs — no safe equivalent across React Native's threading model.

License

Apache-2.0 for this package's own code. See LICENSE, NOTICE, and ATTRIBUTION.md. Third-party attribution obligations flow through the sibling native SDKs.