appidea / react-native-hce

Emulate smart cards inside React-Native application.
MIT License
71 stars 26 forks source link

Make this library to be usable with Expo #10

Open freeridre opened 3 years ago

freeridre commented 3 years ago

That would be awesome if you could make this library available for Expo, like react native nfc manager library. To do that you have to write a config plugin to be usable in managed expo projects. Wodin from expo's forum wrote a config plugin for this library, but I have just started to test it, and at present it's not working, because the hce service does not appear in Android's settings, and when I send APDU I always get 62 82 which means "File not found". So the aid_list.xml maybe missing somehow?! Here is the config plugin for managed expo projects: But I do not know it's working as it expeted...

`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">

<meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/aid_list"/> `;

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]]);`

appidea commented 2 years ago

Hi,

Thank You for preparing the snippets, although You can always create a pull request to add a kind of this functionality. I will try to add the possibilities in a next versions, if no PR will be created.

freeridre commented 2 years ago

Hi, Okey, thank you for the answer. Furthermore, thank your for your awesome work, and I am planning to create a PR for that, but before that, I have to confirm that works.

Henry-E commented 4 months ago

seconded about the desire to make this work easily with expo. Someone wrote something that might work already, still trying to get it working locally. https://forums.expo.dev/t/nfc-host-based-card-emulation-hce/57974