NFC Host-based card emulation (HCE)

@wodin Are there any tutorial, hogy to copy it like in the “eas-build-post-install”: “echo 456” ?

Assuming you create your aid_list.xml file in a directory called res and you add it to Git, then I think the following would work:

  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "eject": "expo eject",
    "eas-build-post-install": "mkdir -p android/app/src/main/res/xml && cp -v res/aid_list.xml android/app/src/main/res/xml",
  },
1 Like

Well, thank you, I will try it. And I have one more question.
How to add these permission into androidmanifest.xml in managed expo project?

<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/service_name"
    android:requireDeviceUnlock="false">
    <aid-group
        android:category="other"
        android:description="@string/card_title">
        <aid-filter android:name="F201808175" />
    </aid-group>
</host-apdu-service>

Ah. For that you’d need a config plugin. And if you’re going to write a config plugin you might as well generate the aid_list.xml file in that too.

See Config Plugins - Expo Documentation
I believe you want to use withAndroidManifest.
Clone the https://github.com/expo/expo/ repository and run this to find examples:

git grep -l withAndroidManifest -- packages/*/plugin/src/

Here are some posts on the forums that have examples too (in no particular order):

Also maybe have a look at xml2js for generating aid_list.xml.

EDIT: Here’s another example:

Well, I try to write my own config plugin for android host-based card emulation, but it’s a little bit overkill for me. Can you help me to write the config plugin? :confused:

Remove the post install hook and try this.

I have only tested that it makes the changes specified here. I have not actually tried to use it in an app.

Create a file called plugin/withReactNativeHce.js with the following contents:

const { withAndroidManifest, withPlugins } = require("@expo/config-plugins");
const xml2js = require("xml2js");
const { mkdirSync, writeFileSync } = require("fs");

const NfcHceServiceXml = `
<service
  android:name="com.reactnativehce.services.CardService"
  android:exported="true"
  android:enabled="false"
  android:permission="android.permission.BIND_NFC_SERVICE">
  <intent-filter>
    <action
      android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
    <category android:name="android.intent.category.DEFAULT"/>
  </intent-filter>
  <meta-data
    android:name="android.nfc.cardemulation.host_apdu_service"
    android:resource="@xml/aid_list"/>
</service>`;

let NfcHceService;
xml2js.parseString(NfcHceServiceXml, (err, result) => (NfcHceService = result.service));

function withNfcHceAndroidManifest(config, { appIds }) {
  return withAndroidManifest(config, (config) => {
    config.modResults = addNfcPermissionToManifest(config.modResults);
    config.modResults = addNfcHceHardwareFeatureToManifest(config.modResults);
    config.modResults = addNfcHceServiceToManifest(config.modResults);
    writeAidList(appIds);
    return config;
  });
}

function addNfcPermissionToManifest(androidManifest) {
  // Add `<uses-permission android:name="android.permission.NFC" />` to the AndroidManifest.xml
  if (!Array.isArray(androidManifest.manifest["uses-permission"])) {
    androidManifest.manifest["uses-permission"] = [];
  }

  if (
    !androidManifest.manifest["uses-permission"].find(
      (item) => item.$["android:name"] === "android.permission.NFC"
    )
  ) {
    androidManifest.manifest["uses-permission"].push({
      $: {
        "android:name": "android.permission.NFC",
      },
    });
  }
  return androidManifest;
}

function addNfcHceHardwareFeatureToManifest(androidManifest) {
  // Add `<uses-feature android:name="android.hardware.nfc.hce" android:required="true" />` to the AndroidManifest.xml
  if (!Array.isArray(androidManifest.manifest["uses-feature"])) {
    androidManifest.manifest["uses-feature"] = [];
  }

  if (
    !androidManifest.manifest["uses-feature"].find(
      (item) => item.$["android:name"] === "android.hardware.nfc.hce"
    )
  ) {
    androidManifest.manifest["uses-feature"].push({
      $: {
        "android:name": "android.hardware.nfc.hce",
        "android:required": "true",
      },
    });
  }
  return androidManifest;
}

function addNfcHceServiceToManifest(androidManifest) {
  const { manifest } = androidManifest;

  if (!Array.isArray(manifest["application"])) {
    console.warn("withReactNativeHce: No manifest.application array?");
    return androidManifest;
  }

  const application = manifest["application"].find(
    (item) => item.$["android:name"] === ".MainApplication"
  );
  if (!application) {
    console.warn("withReactNativeHce: No .MainApplication?");
    return androidManifest;
  }

  if (!Array.isArray(application["service"])) {
    application["service"] = [];
  }

  if (
    !application["service"].find(
      (item) =>
        item.$["android:name"] === "com.reactnativehce.services.CardService"
    )
  ) {
    application["service"].push(NfcHceService);
  }

  return androidManifest;
}

function aidFilters(appIds) {
  return appIds.map((appId) => ({ $: { "android:name": appId } }));
}

function aidGroup(appIds) {
  return [
    {
      $: {
        "android:category": "other",
        "android:description": "@string/app_name",
      },
      "aid-filter": aidFilters(appIds),
    },
  ];
}

function hostApduService(appIds) {
  return {
    "host-apdu-service": {
      $: {
        "xmlns:android": "http://schemas.android.com/apk/res/android",
        "android:description": "@string/app_name",
        "android:requireDeviceUnlock": "false",
      },
      "aid-group": aidGroup(appIds),
    },
  };
}

function writeAidList(appIds) {
  const obj = hostApduService(appIds);
  const builder = new xml2js.Builder();
  const xml = builder.buildObject(obj);
  const dir = "android/app/src/main/res/xml";

  mkdirSync(dir, { recursive: true });
  writeFileSync(`${dir}/aid_list.xml`, xml);
}

module.exports = (config, props) =>
  withPlugins(config, [[withNfcHceAndroidManifest, props]]);

Install xml2js as a dev dependency. e.g.:

yarn add -D xml2js

Add the plugin and the application IDs to your app.json:

{
  "expo": {
    [...]
    "plugins": [
      [
        "./plugin/withReactNativeHce.js",
        {
          "appIds": [
            "D2760000850101",
            "F0010203040506",
            "F0394148148100"
          ]
        }
      ]
    ]
  }
}
1 Like

Omg Wodin, I will try that. Thank you so much in advance! Can it be implemented into expo SDK for other developers?

No, the right place for this, if it works, is in react-native-hce.

Dear Wodin,
The build was successful for me too. When I run it on Android there are not any warnings, but when I run it on iOS, it tells me:

> BlockquoteModule Hce requires main queue setup since it overrides `init` but doesn't implement `requiresMainQueueSetup`. In a future release React Native will default to initializing all native modules on a background thread unless explicitly opted-out of. 
at node_modules\react-native\Libraries\LogBox\LogBox.js:117:10 in registerWarning
at node_modules\react-native\Libraries\LogBox\LogBox.js:89:8 in RCTLog.setWarningHandler$argument_0
at node_modules\react-native\Libraries\Utilities\RCTLog.js:34:8 in logIfNoNativeHook
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:416:4 in __callFunction
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:109:6 in __guard$argument_0
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:364:10 in __guard
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:108:4 in callFunctionReturnFlushedQueue

I assume that is because iOS does not support host-based card emulation. Should I handle it somehow, or it does not a big problem. Can I leave it? And if I have to deal with this error, how could I solve this?

Another question. I created a folder as you wrote for the config plugin. Inside that folder (config-plugins) I created the "withReactNativeHce.js . After that in the root a created "app.config.json, because when I add more than two config plugins into app.json it says:
Wrong number of arguments provided for static config plugin, expected either 1 or 2, got 6
so in app.json only 1 or 2 config plugins are allowed… in app.config.json I included the plugin, as far as it similar what you suggested.

{

  "myconfig": {

    "config-plugins":

    [

      "config-plugins/withReactNativeHce.js",

      {

        "appIds": [

          "A0000001020304"

        ]

      },

      "@config-plugins/react-native-ble-plx",

      {

        "isBackgroundEnabled": true,

        "modes": [

          "peripheral",

          "central"

        ],

        "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices",

        "bluetoothPeripheralPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices"

      }

    ],

    "android": {

      "package": "com.freeridre.senitynfcappios"

    },

    "ios": {

      "bundleIdentifier": "com.freeridre.senitynfcappios"

    }

  }

}

but when I run the app it says:

Android Bundling complete 38ms
Error: Problem validating fields in app.config.json. Learn more: https://docs.expo.dev/workflow/configuration/
 • should NOT have additional property 'myconfig'.
Error: Problem validating fields in app.config.json. Learn more: https://docs.expo.dev/workflow/configuration/
 • should NOT have additional property 'myconfig'.

I know that I use the app.config.json in a wrong way, but I have not been able to figured out how should it be configurated.
Here is a snipp of my structure of the project:

Could you help me?

I’m not sure what that’s about but it looks like it might be a warning that react-native-hce will not work properly on iOS in future versions of React Native, unless some changes are made to react-native-hce.

As for what to do about it, ideally you would figure out what it means and submit a PR to react-native-hce to fix the problem :slight_smile: but you might be able to just disable the warning. e.g. See Disable the Yellow Box in React Native

No, app.json does not limit the number of config plugins you can have. You have misinterpreted the error message. Also app.config.json is the same format as app.json.

You can’t use myconfig. It would have to be expo. And you can’t use config-plugins for key in app.json or app.config.json. It has to be plugins.

So something like this should work:

{
  "expo": {
    "name": "name",
    "slug": "slug",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "plugins": [
      [
        "./config-plugins/withReactNativeHce.js",
        {
          "appIds": [
            "A0000001020304"
          ]
        }
      ],
      [
        "@config-plugins/react-native-ble-plx",
        {
          "isBackgroundEnabled": true,
          "modes": [
            "peripheral",
            "central"
          ],
          "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices",
          "bluetoothPeripheralPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices"
        }
      ],
      "./config-plugins/someOtherPluginWithNoOptions",
      [
        "./config-plugins/anotherPluginWithOptions",
        {
          "option": "value"
        }
      ]
    ],
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#FFFFFF"
      },
      "package": "com.example.app"
    },
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.example.app"
    }
  }
}
1 Like

Thank you so much! You are so helpful person! I glad that you help me. :slight_smile:
By th way, the build was successful again, but somehow my VS code does not find the config plugins, that I included, furthermore it does not find my assets/Images/… files, but the build was successful. What I did that I created a new folder where I put my screens for iOS and I created some other screens for Android devices. After that when I tried to include images, fonts, etc. expo or VS code did not find them, but the build was finished without errors. When I try to see my androidmanifest.xml file with expo tools extension in VS Code to check what was written inside of that, Expo-tools says:
Cannot find module 'c:\Users\dani\Documents\SenitySecuritySystemsRepo\NFCApp\SenityNFCiOS\SenityNFCAppiOS\config-plugins\withReactNativeHce.js' Source: Expo-tools (Extension)
Here is my app.json:

{
  "expo": {
    "name": "Senity",
    "slug": "SenityNFCForiOS",
    "version": "1.0.0",
    "orientation": "portrait",
    "backgroundColor": "#212832",
    "icon": "./assets/Images/Senity_app_icon_white_whit blue_v3.png",
    "splash": {
      "image": "./assets/Images/Senity_app_splash_sceen_icon.png",
      "resizeMode": "contain",
      "backgroundColor": "#212832"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": false,
      "requireFullScreen": true,
      "bundleIdentifier": "com.freeridre.SenityNFCForiOS",
      "splash": {
        "image": "./assets/Images/Senity_app_splash_sceen_icon.png",
        "resizeMode": "contain",
        "backgroundColor": "#212832"
      }
    },
    "android": {
      "package": "com.freeridre.SenityNFCForiOS",
      "splash": {
        "image": "./assets/Images/Senity_app_splash_sceen_icon.png",
        "resizeMode": "contain",
        "backgroundColor": "#212832"
      },
      "softwareKeyboardLayoutMode": "pan"
    },
    "web": {
      "favicon": "C:/Users/dani/Documents/SenitySecuritySystemsRepo/NFCApp/SenityNFCiOS/SenityNFCAppiOS/assets/favicon.png"
    },
    "plugins":
    [
      [
        "./config-plugins/withReactNativeHce.js",
        {
          "appIds": [
            "A0000001020304"
          ]
        }
      ],
      [
        "@config-plugins/react-native-ble-plx",
        {
          "isBackgroundEnabled": true,
          "modes": [
            "peripheral",
            "central"
          ],
          "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices",
          "bluetoothPeripheralPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices"
        }
      ],
      [
        "react-native-nfc-manager",
        {
          "nfcPermission": "It's for Senity Security Systems Ltd.",
          "selectIdentifiers": [
            "A0000002471001",
            "D2760000850100",
            "D2760000850101",
            "A0000003964D66344D0002"
          ]
        }
      ]
    ]
  }
}

I’m glad I could help. It’s also the first time I have written a config plugin to modify AndroidManifest.xml and the first time I have used xml2js :slight_smile:

You should change this to a relative path (./assets/…)

Otherwise if another user clones the repository and tries to build the web apo, the favicon will not be found.

I don’t use VS Code, so I am not sure why it is complaining. I use expo prebuild to test the config plugins. You just have to tidy up afterwards.

1 Like

Did it add the service into androidmanifest.xml? Because I can not see after prebuild process.

Yes, it’s true. I will change it. I found the problem. I used config-plugins or plugins for the cfg folder. After I changed it to plugin and changed the property too in app.json to “plugin” everything worked.

1 Like

Unfortunatelly, it did not work… hmm. I do not know what is the problem… So expo says:

expo-config(MODULE_NOT_FOUND)

Aprox. how much time the prebuild process to be finished?

When do you get that error?

With the following plugins definition in app.json:

    "plugins": [
      [
        "./plugin/withReactNativeHce.js",
        {
          "appIds": [
            "D2760000850101",
            "F0010203040506",
            "F0394148148100"
          ]
        }
      ]
    ]

I get the following:

android/app/src/main/res/xml/aid_list.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/app_name" android:requireDeviceUnlock="false">
  <aid-group android:category="other" android:description="@string/app_name">
    <aid-filter android:name="D2760000850101"/>
    <aid-filter android:name="F0010203040506"/>
    <aid-filter android:name="F0394148148100"/>
  </aid-group>
</host-apdu-service>

android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.rnhcetest">
  <uses-permission android:name="android.permission.INTERNET"/>
  <uses-permission android:name="android.permission.NFC"/>
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
  <uses-permission android:name="android.permission.VIBRATE"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <uses-feature android:name="android.hardware.nfc.hce" android:required="true"/>
  <queries>
    <intent>
      <action android:name="android.intent.action.VIEW"/>
      <category android:name="android.intent.category.BROWSABLE"/>
      <data android:scheme="https"/>
    </intent>
  </queries>
  <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
    <meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
    <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="43.0.0"/>
    <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
    <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
    <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@wodin/rn-hce-test"/>
    <service android:name="com.reactnativehce.services.CardService" android:exported="true" android:enabled="false" android:permission="android.permission.BIND_NFC_SERVICE">
      <intent-filter>
        <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
        <category android:name="android.intent.category.DEFAULT"/>
      </intent-filter>
      <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/aid_list"/>
    </service>
    <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="portrait">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="com.example.rnhcetest"/>
      </intent-filter>
    </activity>
    <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
  </application>
</manifest>

It took close to a minute to run when I tried it now and most of that was from installing the JavaScript dependencies.

After the prebuild the project looks like this:

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   .gitignore
        modified:   app.json
        modified:   package.json
        modified:   yarn.lock

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        android/
        index.js
        ios/
        metro.config.js

no changes added to commit (use "git add" and/or "git commit -a")

Since everything was committed to Git before the prebuild, I do the following to tidy up:

$ rm -r android index.js ios metro.config.js
$ git checkout .gitignore app.json package.json yarn.lock
Updated 4 paths from the index
$ yarn
yarn install v1.22.15
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@2.3.2: The platform "linux" is incompatible with this module.
info "fsevents@2.3.2" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning "react-native > react-native-codegen > jscodeshift@0.11.0" has unmet peer dependency "@babel/preset-env@^7.1.6".
[4/4] Building fresh packages...
Done in 3.32s.

Well, where I include “./plugin/withReactNativeHce.js” and “@config-plugins/react-native-ble-plx” and “react-native-nfc-manager”

But the strange thing is that after eas prebuild it generated the necessary permissions, so I guess it’s a kind of Vs code bug/error that really anoying…

We fixed the vscode extension, sorry for the inconvenience.

2 Likes

Yes, I saw, and thx for the awesome work! :slight_smile:

@wodin do you have any idea, what’s could be the problem?

Please check these:

  • I think it’s good
My app.json
"plugins": [
            [
                "./plugin/withReactNativeHce.js",
                {
                    "appIds": [
                        "A0000001020304"
                    ]
                }
            ],
            [
                "@config-plugins/react-native-ble-plx",
                {
                    "isBackgroundEnabled": true,
                    "modes": [
                        "peripheral",
                        "central"
                    ],
                    "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices",
                    "bluetoothPeripheralPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices"
                }
            ],
            [
                "react-native-nfc-manager",
                {
                    "nfcPermission": "It's for Senity Security Systems Ltd.",
                    "selectIdentifiers": [
                        "A0000002471001",
                        "D2760000850100",
                        "D2760000850101",
                        "A0000003964D66344D0002"
                    ]
                }
            ]
        ]
  • I think this could be bad
My Activity where I start the simulation
import LottieView from 'lottie-react-native';
import React, { useState, useEffect  } from 'react';
import {Platform, StyleSheet, Text, View,
        Image, TouchableOpacity, Alert, Vibration,
        BackHandler, AppState, Modal, Button
        } from 'react-native';
import 'react-native-gesture-handler';
import { Colors } from 'react-native/Libraries/NewAppScreen';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import * as ScreenOrientation from 'expo-screen-orientation';
import AppLoading from 'expo-app-loading';
import DeviceInfo from 'react-native-device-info';
import RNExitApp from 'react-native-exit-app';
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';
import * as Font from 'expo-font';
import NfcManager, {NfcTech, Ndef, NfcEvents, NfcAdapter, NfcError} from 'react-native-nfc-manager';
import * as SecureStore from 'expo-secure-store';
import HCESession, { NFCContentType, NFCTagType4 } from 'react-native-hce';
let simulation;

let uniqueId2 = DeviceInfo.getUniqueId();
let uniqueId = uniqueId2.slice(0,13);
let NFCData = false;
let readNDEF_bool = false;
let got_uid_2;
let read_or_write = false;
let ONE_SECOND_IN_MS = 100;
let SendBool = false



async function initNfc() {
  await NfcManager.start();
  console.log("NFC Manager has just started...");
}
export async function save(key, value) {
  await SecureStore.setItemAsync(key, value);
}
export async function getValueFor(key) {
  let result = await SecureStore.getItemAsync(key);
  if (result) {
    alert("🔐 Here's your value 🔐 \n" + result);
  } else {
    alert('No values stored under that key.');
  }
}
//HACK: It's for debugging purposes only
async function deleteFor(key) {
  SecureStore.deleteItemAsync(key);
  getValueFor(key);
}

let writeNdef = async ({ type, value}) => {
  console.log("itt vagyok")
  SendBool = true
  let result = false;
  try {
    console.log("||||||||||||||||||||||||||||||||||||||||");
    
    console.log("*****************************************");
    //const bytes = Ndef.encodeMessage([Ndef.textRecord("1B226FB2-F006-4BC2-A613-7C69ECB")]);
    //const bytes = Ndef.encodeMessage([Ndef.textRecord("1B226FB2-F006-4BC2-A613-7C69ECB0")]);
    let bytes;
    if(!read_or_write)
    {
      await NfcManager.requestTechnology(NfcTech.Ndef, {
        alertMessage: 'Ready to Send Your Unique ID to Senity',
      });
      //this.setState({send: true})
      
      console.log("write unique ID");
      console.log(uniqueId);
      bytes = Ndef.encodeMessage([Ndef.textRecord(uniqueId)]);
    }else if(read_or_write)
    {
      //this.setState({NFCData_bool: false})
      await NfcManager.requestTechnology(NfcTech.Ndef, {
        alertMessage: 'Ready to Send Your generated UID to Senity',
      });
      console.log("write generated ID");
      console.log(got_uid_2);
      bytes = Ndef.encodeMessage([Ndef.textRecord(got_uid_2)]);
    }      
    if (bytes) {
      await NfcManager.ndefHandler // Step2
        .writeNdefMessage(bytes); // Step3

      if (Platform.OS === 'ios') {
        await NfcManager.setAlertMessageIOS('Successfully sent NDEF');


        NFCData = true;
        Vibration.vibrate();
      }else
      {
        await NfcManager.setAlertMessage('Successfully sent NDEF')
        NFCData = true;
        Vibration.vibrate(50, 500);
      }
      /*this.setState({approved: true})
      this.setState({pushed: false})
      this.setState({error: false})*/
      if(read_or_write)
      {
        setTimeout(() =>{
          RNExitApp.exitApp();
        }, 2500);
      }
    }
    result = true;
  } catch (ex) {
    //console.warn(ex);
    if (ex instanceof NfcError.UserCancel) {
      // bypass
      console.log("User Canceled");
      //NFCData = false;
      Vibration.vibrate();
      let UCanceled = "User Canceled"
      return UCanceled;
    } else if (ex instanceof NfcError.Timeout) {
      Alert.alert('NFC Session Timeout');
      NFCData = false;
    }else{
      console.warn(ex);
      NFCData = false;
      if (Platform.OS === 'ios') {
        NfcManager.invalidateSessionWithErrorIOS(`${ex}`);
      } else {
        //Alert.alert('NFC Error', `${ex}`);
      }
    }
  }
  finally
  {
    NfcManager.cancelTechnologyRequest();
    console.log("Finally");
    
    console.log(NFCData);
    
  }
  
  // Step 4
  NfcManager.cancelTechnologyRequest().catch(() => 0);
  return NFCData;
}
let _cleanUp = () => {
    NfcManager.cancelTechnologyRequest().catch(() => 0);
  }
function readNdef() {
  console.log("readNdef");
  
    const cleanUp = () => {
      NfcManager.setEventListener(NfcEvents.DiscoverTag, null);
      NfcManager.setEventListener(NfcEvents.SessionClosed, null);
    };
    return new Promise((resolve) => {
      let tagFound = null;
      let parsed = null;
      NfcManager.setEventListener(NfcEvents.DiscoverTag, (tag) => {
        tagFound = tag;
        resolve(tagFound);

        console.log(tagFound);
        record = tagFound;
        const ndefRecords = tag.ndefMessage;
        function decodeNdefRecord(record)
        {
          if(Ndef.isType(record, Ndef.TNF_WELL_KNOWN, Ndef.RTD_TEXT)){
            let NdefCode = Ndef.text.decodePayload(record.payload);
            console.log("Data From PN532 is: ", NdefCode);
            //console.log(['text', Ndef.text.decodePayload(record.payload)]);
            //TODO:Here we have to save it into asyncStorage
            save('genuid', NdefCode);
            readNDEF_bool = true;
            NFCData = false;
            return ['text', Ndef.text.decodePayload(record.payload)];
          }else if(Ndef.isType(record, Ndef.TNF_WELL_KNOWN, Ndef.RTD_URI))
          {
            return ['uri', Ndef.uri.decodePayload(record.payload)];
          }
          return ['unknown', '---']
        }
        parsed = ndefRecords.map(decodeNdefRecord);
        if(Platform.OS === "ios")
        {
          NfcManager.setAlertMessageIOS('NDEF tag found');
        }else
        {
          NfcManager.setAlertMessage('NDEF tag found');
        }

        NfcManager.unregisterTagEvent().catch(() => 0);
      });

      NfcManager.setEventListener(NfcEvents.SessionClosed, () => {
        cleanUp();
        if (!tagFound) {
          resolve();
        }
      });

      NfcManager.registerTagEvent();
    });
}
export default class IosGetuidScreen extends React.Component {

  constructor(props) {
        super(props);
        this.state={
            fontLoaded:false,
            UID:'',
            DeviceID: '',
            getUID: '',
            NFCSTATE: '',
            text: 'Tap to the virtual card to register your device',
            key: 'genuid',
            uidshow: false,
            uidtext: '',
            onlywrite: false,
            Vibrate_pattern: [1 * ONE_SECOND_IN_MS],
            restart_var: false,
            send: false,
            receive: false,
            error: false,
            canceled: false,
            approved: false,
            device_platform:true,
            NFCData_bool: true,
            pushed: false,
            NFCStatus: 'Sending your ID!',
            NFCStatusColor: '#212832'

            
            
        }
        
    }
  //this.setState({DeviceID:uniqueId});
_cleanUp = () => {
    NfcManager.cancelTechnologyRequest().catch(() => 0);
  };
_testNdef = async () => {
    //readNdef();
    if(NFCData && !read_or_write)
    {
      this.setState({ NFCData_bool: false })
      await readNdef();
            
      if (readNDEF_bool)
      {
        if(Platform.OS === "ios")
        {
          Vibration.vibrate();
        }else
        {
          Vibration.vibrate(50, 500);
        }
        this.setState({NFCStatus: 'Success!'})
        this.setState({approved: true})
        setTimeout(() =>{
          this.setState({NFCData_bool: true})
        }, 2500);
        this.setState({ text: '' });
        this.setState({ uidshow: true})
        let got_uid = await SecureStore.getItemAsync('genuid');
        console.log("got_uid");
        console.log(got_uid);
        let rm_aster_uid = got_uid.substring(0, got_uid.length - 2);
        this.setState({ uidtext: rm_aster_uid})
        read_or_write = true;
        
        this.setState({ text: 'Tap to authorize' });
        //this.setState({NFCStatus: 'Authorization!'})
      }else if(NFCData && !readNDEF_bool)
      {
        this.setState({ NFCData_bool: false })
        
        setTimeout(() =>{
          this.setState({NFCData_bool: true})
        }, 2500);
      }else if(!readNDEF_bool || rm_aster_uid.length === 0)
      {
        Vibration.vibrate();
        this.setState({restart_var: true})
        this.setState({ text: 'An error occured.\nPlease restart the process!'});

        
      }
    } else if (!NFCData)
    {
      
      readNDEF_bool ? this.setState({ uidshow: true }) : this.setState({ uidshow: false }); 
      if(!read_or_write)
      {
        this.setState({ text: 'Tap to the virtual card to register your device' });
        this.setState({ NFCData_bool: false })
      }else if(read_or_write)
      {
        this.setState({ NFCData_bool: false })
      }
      let WriteResult = await writeNdef({});
      console.log("WriteResult: " + WriteResult)
      if((typeof(WriteResult) === "boolean") && WriteResult)
      {
        console.log("Hellokaa1")
        this.setState({approved: true})
        this.setState({NFCStatusColor: 'blue'})
        this.setState({error: false})
        this.setState({NFCStatus: 'Success!'})
        setTimeout(() =>{
          this.setState({NFCData_bool: true})
        }, 1800);
        
      }else if((typeof(WriteResult) != "boolean") && WriteResult.startsWith("User Canceled"))
      {
        console.log("Hellokaa2")
        this.setState({error: false})
        this.setState({approved: false})
        this.setState({pushed: true})
        this.setState({NFCStatus: 'Sending your ID!'})
      }
      else
      {
        console.log("Hellokaa3")
        this.setState({error: true})
        this.setState({approved: false})
        this.setState({NFCStatus: 'Error!'})
        this.setState({NFCStatusColor: 'red'})
      }
    
      if (NFCData && !readNDEF_bool)
      {
        this.setState({ text: 'Tap again to get the generated UID' });
        
      }
    }
  };
  // Pre-step, call this before any NFC operations
  
  async componentDidMount(){
    if(Platform.OS === "ios")
      {
        this.setState({device_platform: false})
      }
    BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
    this.setState({fontLoaded:true});
    console.log("ID: ");
    console.log(uniqueId2);
    this.setState({DeviceID:uniqueId2});
    //HACK: it's for debugging purposes only
    //deleteFor('genuid');
    got_uid_2 = await SecureStore.getItemAsync('genuid');
    console.log(got_uid_2);
    if (got_uid_2)
    {
      let sliced_got_uid_2 = got_uid_2.substring(0, got_uid_2.length - 2)
      this.setState({ uidtext: sliced_got_uid_2 })
      this.setState({ uidshow: true })
      this.setState({ onlywrite: true})
      read_or_write = true;
      readNDEF_bool = true;
      this.setState({text: ''})
      this.setState({ text: 'Tap to authorize' });
      this._testNdef();
    }
    else
    {
      read_or_write = false;
      console.log("You do not have UID set yet");
      this.setState({ text: 'Tap to the virtual card to register your device'})
    }
  }
  
  async componentWillUnmount(){
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
    _cleanUp();
  }
  handleOnPress = () =>
  {
    this.setState({restart_var : false })
    this.setState({ text: 'Tap to the virtual card to register your device'})
    NFCData = false
    readNDEF_bool = false
    read_or_write = false

  }
  handleBackPress = () => {
    RNExitApp.exitApp();
  }
  cancelNFCAndroid = () =>{
    NfcManager.cancelTechnologyRequest();
    this.setState({NFCData_bool: true}) 
  }
  loadNFCAndroid = () =>{
    if(!(this.state.error || this.state.approved))
    {
      this.setState({pushed: true})
      this.setState({error: false})
      this.setState({approved: false})
    }else if(this.state.error || this.state.approved && read_or_write)
    {
      this.setState({pushed: true})
      this.setState({error: false})
      this.setState({approved: false})
      this.setState({NFCStatus: 'Sending your ID!'})
    }else if(this.state.error || this.state.approved && (!readNDEF_bool && NFCData))
    {
      this.setState({pushed: true})
      this.setState({error: false})
      this.setState({approved: false})
      this.setState({NFCStatus: 'Getting your UID!'})
    }else
    {
      this.setState({pushed: false})
      this.setState({error: false})
      this.setState({approved: false})
    }    
  }
  NCFstatus = () =>{
    if(this.state.approved)
    {
      return { color: 'green'}
    }else if(this.state.error)
    {
      return { color: 'red'}
    }
  }
  startSimulation = async () => {
    console.log("Simulation started!")
    const tag = new NFCTagType4(NFCContentType.Text, "Hello world");
    simulation = await (new HCESession(tag)).start();
  }
  
  stopSimulation = async () => {
    await simulation.terminate();
  }
  render() {
    return (
      
      <View style={background.main}>
        <Modal
          transparent={true}
          visible={this.state.device_platform && !this.state.NFCData_bool}
        >
          <View
            style={background.modal_background}
          >
            <View
              style={background.modal_load}
            >
              <View
                style={this.state.pushed && !(this.state.approved || this.state.error) ? background.EmptyView : background.text1hide}
              >
                <LottieView
                  ref={animation => {
                  this.animation = animation;
                  }}
                  style={background.lottie_load}
                  resizeMode="cover"
                  source={require('../../assets/loading_circle_v4.json')}
                  autoPlay
                >
                </LottieView>
              </View>
              <View
                style={this.state.error ? background.EmptyView : background.text1hide}
              >
                <LottieView
                  ref={animation => {
                  this.animation = animation;
                  }}
                  style={background.lottie_load}
                  resizeMode="cover"
                  source={require('../../assets/error_lottie.json')}
                  autoPlay
                >
                </LottieView>
              </View>
              <View
                style={this.state.approved ? background.EmptyView : background.text1hide}
              >
                <LottieView
                  ref={animation => {
                  this.animation = animation;
                  }}
                  style={background.lottie_load_approved}
                  resizeMode="cover"
                  source={require('../../assets/approved_lottie_v3.json')}
                  autoPlay
                >
                </LottieView>
              </View>
              <Text
                style={[background.modal_text, this.NCFstatus()]}
              >
                {this.state.NFCStatus}
              </Text>
              <TouchableOpacity
                style={background.button_cancel_tchopa}
                onPress={this.cancelNFCAndroid}
              >
                <Text
                  style={background.button_cancel_text}
                >
                  Cancel
                </Text>
              </TouchableOpacity>

            </View>
          </View>
        </Modal>
        <Text
            style={!this.state.restart_var ? background.text1show: background.restart_text}
        >
          {this.state.text}
        </Text>

        <View>
          <TouchableOpacity
            style={!this.state.restart_var ? background.Topacity1: background.display_none}
            onPress={()=>{
              this.startSimulation()
              //this.loadNFCAndroid()
              //this._testNdef();
            }} 
          >
          
            
          
            
            <Image
                style={!this.state.restart_var ? background.card: background.display_none}
              source={require('../../assets/card_blank.png')}
            >
            </Image>
            <Text style={this.state.uidshow ?
              background.textuidshow : background.text1hide}>
              {this.state.uidtext}
            </Text>
            {/*<Text style={
              background.textuidshow}>
              A4 B4 FF AA
            </Text>*/}
          </TouchableOpacity>
          <TouchableOpacity
            style={this.state.restart_var ?
              background.restart_touchableopacityshow: background.display_none}
              onPress={this.handleOnPress}
              >
            <LottieView
              ref={animation => {
                this.animation = animation;
              }}
              source={require('../../assets/restart.json')}
              autoPlay
            />
            
          </TouchableOpacity>
          <Text style={this.state.restart_var ? background.restart_label: background.display_none}>
            Restart
          </Text>
        </View>          
        <Text style={background.copyrighttext}> © 2021 Senity Security Systems Ltd.
        </Text>
      </View>
    );
     
  }  
}

const background = StyleSheet.create({
    main:{
        backgroundColor: '#212832',
        flex: 1,
        flexDirection: 'column',   
    },
    card:{
        //marginTop: '15%',
        //justifyContent: 'center',
        alignSelf: 'center',
        height: '100%',
        width: '100%',
        //resizeMode: 'stretch'
        //resizeMode: 'cover', 
        resizeMode: 'contain'
    },
    Topacity1:{
      //flex: 1,
      width: '95%',
      height: '55%',
      alignSelf: 'center',
    },
    textuidshow: {
      position: 'absolute',
      alignSelf: 'center',
      textAlign: 'center',
      marginTop: '38%',
      color: '#fff5ee',
      fontSize: 50,
      fontFamily: 'Apple SD Gothic Neo',
      fontWeight: 'bold',
      letterSpacing: 5,
      textTransform: 'uppercase'
    },
    text1show:{
        alignSelf: 'center',
        textAlign: 'center',
        marginTop: '20%',
        color: 'white',
        fontSize: 28,
        fontFamily: 'Apple SD Gothic Neo',
        fontWeight: 'bold',
    },
    text1hide:{
      display:'none'
    },
    copyrighttext: {
      bottom: '5%',
      position: 'absolute',
      alignSelf: 'center',
      color: 'white',
      fontFamily: 'Apple SD Gothic Neo',
      fontSize: 8,
    },
    restart_touchableopacityshow:
    {
        marginTop: '30%',
        alignSelf: 'center',
        height: 200,
        width: 200,
        //flex: 2
    },
    restart_lottie:
    {
      justifyContent: 'center',
      //marginTop: '5%',
      alignSelf: 'center',
      height: 5,
      width: 30
      //flex: 2
    },
    restart_text:
    {
      alignSelf: 'center',
      textAlign: 'center',
      marginTop: '20%',
      color: 'red',
      fontSize: 28,
      fontFamily: 'Apple SD Gothic Neo',
      fontWeight: 'bold',
    },
    restart_label:
    {
      position: 'absolute',
      bottom: 15,
      alignSelf: 'center',
      textAlign: 'center',
      color: 'red',
      fontSize: 20,
      fontFamily: 'Apple SD Gothic Neo',
    },
    display_none:
    {
      display: 'none'
    },
    modal_background:{
      flex: 1,
      backgroundColor: 'rgba(1, 0, 0, 0.5)',
      alignItems: 'center',
      justifyContent: 'center'
    },
    modal_load:{
      width: 280,
      height: 280,
      margin: 20,
      backgroundColor: "#fff5ee",
      borderRadius: 20,
      padding: 35,
      shadowColor: "black",
      shadowOffset: {
        width: 10,
        height: 2
    },
    },
    no_modal:{
      display: 'none'
    },
    lottie_load:{
      width: 125,
      height: 125,
      alignSelf: 'center'

    },
    lottie_load_approved:{
      width: wp(15),
      height: hp(15),
      alignSelf: 'center'

    },
    modal_text:{
      alignSelf: 'center',
      textAlign: 'center',
      color: '#212832',
      fontSize: 18,
      fontFamily: 'Apple SD Gothic Neo',
      paddingBottom: 25,
      fontWeight: 'bold'
    },
    button_cancel_tchopa:{
      width: 180,
      alignSelf: 'center',
      textAlign: 'center',
      borderRadius: 15,
      borderWidth: 1,
      backgroundColor: '#fff5ee'
    },
    button_cancel_text:{
      letterSpacing: 2.5,
      alignSelf: 'center',
      textAlign: 'center',
      color: '#212832',
      fontSize: 25,
      fontFamily: 'Apple SD Gothic Neo',
    },
    EmptyView:{

    }
});

So there are two functions:

startSimulation
startSimulation = async () => {
    console.log("Simulation started!")
    const tag = new NFCTagType4(NFCContentType.Text, "Hello world");
    simulation = await (new HCESession(tag)).start();
}
stopSimulation
stopSimulation = async () => {
    await simulation.terminate();
}

I call startSimulation at

onPress={()=>{
              this.startSimulation()
              //this.loadNFCAndroid()
              //this._testNdef();
}}

Finally, I get the 90 00 APDU code which means: Command successfully executed (OK).
but I do not get the message “Hello World”
Here is the list of APDU messages: APDU messages

I think that the Expo side of things is probably OK.

It would probably be best to ask someone who knows something about NFC and HCE.

It might also be worth trying to get the simplest possible plain React Native app working. If that doesn’t work, talk to the React Native HCE people or maybe Stack Overflow.

When the simple plain RN version works, try getting it to work in Expo with the config plugin. If it works as a plain RN app, but not in Expo with the config plugin, then try running expo prebuild and compare the results with the working RN app. Based on that we can figure out what changes might be needed in the config plugin.

Good luck.