From 5353780c67e193b1998e1bf7b3de1acb747a3029 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Thu, 22 Jun 2023 19:42:15 +0000 Subject: [PATCH] support native nfc --- packages/app/src/cores/nfc/nfc.core.js | 220 +++++++++++++++++++++---- 1 file changed, 191 insertions(+), 29 deletions(-) diff --git a/packages/app/src/cores/nfc/nfc.core.js b/packages/app/src/cores/nfc/nfc.core.js index d8aea5cc..759dd354 100644 --- a/packages/app/src/cores/nfc/nfc.core.js +++ b/packages/app/src/cores/nfc/nfc.core.js @@ -1,40 +1,137 @@ import Core from "evite/src/core" import TapShareDialog from "components/TapShare/Dialog" -import { Nfc, NfcUtils, NfcTagTechType } from "@capawesome-team/capacitor-nfc" + +const RecordTypes = { + "T": "text", + "U": "url" +} + +function decodePayload(record) { + let recordType = nfc.bytesToString(record.type) + + let payload = { + recordId: nfc.bytesToHexString(record.id), + recordType: RecordTypes[recordType], + data: null + } + + switch (recordType) { + case "T": { + let langCodeLength = record.payload[0] + + payload.data = record.payload.slice((1 + langCodeLength), record.payload.length) + + payload.data = nfc.bytesToString(text) + + break + } + case "U": { + let identifierCode = record.payload.shift() + + payload.data = nfc.bytesToString(record.payload) + + switch (identifierCode) { + case 4: { + payload.data = `https://${payload.data}` + break + } + case 3: { + payload.data = `http://${payload.data}` + break + } + } + + break + } + default: { + payload.data = nfc.bytesToString(record.payload) + break + } + } + + return payload +} + +function resolveSerialNumber(tag) { + let serialNumber = null + + serialNumber = nfc.bytesToHexString(tag.id) + + // transform serialNumber to contain a ":" every 2 bytes + serialNumber = serialNumber.replace(/(.{2})/g, "$1:") + + // remove the last : + serialNumber = serialNumber.slice(0, -1) + + return serialNumber +} + +function parseNdefMessage(ndefMessage) { + let message = [] + + ndefMessage.forEach((ndefRecord) => { + message.push(decodePayload(ndefRecord)) + }) + + return message +} export default class NFC extends Core { static refName = "NFC" static namespace = "nfc" + isNativeMode = false + instance = null subscribers = [] public = { - incompatible: null, + incompatible: true, scanning: false, - startScanning: this.startScanning.bind(this), + writeNdef: this.writeNdef.bind(this), instance: function () { return this.instance }.bind(this), subscribe: this.subscribe.bind(this), unsubscribe: this.unsubscribe.bind(this), } - async onInitialize() { - if ("NDEFReader" in window === false) { - return this.public.incompatible = true + subscribe(callback) { + // check if scan service is available, if not try to initialize + if (this.public.scanning === false) { + this.startScanning() } - this.instance = new NDEFReader() + this.subscribers.push(callback) + } - this.startScanning() + unsubscribe(callback) { + this.subscribers = this.subscribers.filter((subscriber) => { + return subscriber !== callback + }) + } + + async onInitialize() { + if (window.nfc) { + this.instance = window.nfc + + this.isNativeMode = true + + return this.startNativeScanning() + } + + if ("NDEFReader" in window) { + this.instance = new NDEFReader() + + return this.startScanning() + } + + return this.public.incompatible = true } async startScanning() { try { - //await navigator.permissions.query({ name: "nfc" }) - - this.instance.scan() + await this.instance.scan() this.public.scanning = true this.public.incompatible = false @@ -48,23 +145,11 @@ export default class NFC extends Core { } } - subscribe(callback) { - // check if scan service is available, if not try to initialize - if (this.public.scanning === false) { - this.startScanning() - } + startNativeScanning() { + this.public.scanning = true + this.public.incompatible = false - this.subscribers.push(callback) - - console.debug(`[NFC] SUBSCRIBED >`, this.subscribers.length) - } - - unsubscribe(callback) { - this.subscribers = this.subscribers.filter((subscriber) => { - return subscriber !== callback - }) - - console.debug(`[NFC] UNSUBSCRIBED >`, this.subscribers.length) + return this.registerNativeEventListeners() } handleRead(tag) { @@ -75,8 +160,6 @@ export default class NFC extends Core { subscriber(null, tag) }) - console.log(this.subscribers) - if (this.subscribers.length === 0) { if (tag.message.records?.length > 0) { // open dialog @@ -89,6 +172,32 @@ export default class NFC extends Core { } } + handleNativeRead(tag) { + console.debug(`[NFC] NATIVE READ >`, tag) + + tag.serialNumber = resolveSerialNumber(tag) + + if (tag.ndefMessage) { + tag.message = {} + tag.message.records = parseNdefMessage(tag.ndefMessage) + } + + this.subscribers.forEach((subscriber) => { + subscriber(null, tag) + }) + + if (this.subscribers.length === 0 && tag.message?.records) { + if (tag.message.records?.length > 0) { + // open dialog + app.DrawerController.open("nfc_card_dialog", TapShareDialog, { + componentProps: { + tag: tag, + } + }) + } + } + } + handleError(error) { this.subscribers.forEach((subscriber) => { subscriber(error, null) @@ -99,4 +208,57 @@ export default class NFC extends Core { this.instance.addEventListener("reading", this.handleRead.bind(this)) this.instance.addEventListener("error", this.handleError.bind(this)) } + + registerNativeEventListeners() { + nfc.addTagDiscoveredListener( + (e) => this.handleNativeRead(e.tag), + () => { + this.public.scanning = true + this.public.incompatible = false + }, + this.handleError + ) + + nfc.addNdefListener( + (e) => this.handleNativeRead(e.tag), + () => { + this.public.scanning = true + this.public.incompatible = false + }, + this.handleError + ) + } + + async writeNdef(payload, options) { + console.debug(`[NFC] WRITE >`, payload) + + if (!this.isNativeMode) { + return this.instance.write(payload, options) + } + + let message = [] + + const { records } = payload + + if (!Array.isArray(records)) { + throw new Error("records must be an array") + } + + for (const record of records) { + switch (record.recordType) { + case "text": { + message.push(ndef.textRecord(record.data)) + break + } + case "url": { + message.push(ndef.uriRecord(record.data)) + break + } + } + } + + return await new Promise((resolve, reject) => { + this.instance.write(message, resolve, reject) + }) + } } \ No newline at end of file