diff --git a/plugins/nfc/guest-js/index.ts b/plugins/nfc/guest-js/index.ts index 09a1cf80..15a4f35e 100644 --- a/plugins/nfc/guest-js/index.ts +++ b/plugins/nfc/guest-js/index.ts @@ -8,6 +8,9 @@ declare global { } } +export const RTD_TEXT = [0x54]; // "T" +export const RTD_URI = [0x55]; // "U" + export enum ScanKind { Ndef, Tag, @@ -53,9 +56,104 @@ export interface NFCRecord { payload: number[]; } +export function record( + format: NFCTypeNameFormat, + kind: string | number[], + id: string | number[], + payload: string | number[] +): NFCRecord { + return { + format, + kind: + typeof kind === "string" + ? Array.from(new TextEncoder().encode(kind)) + : kind, + id: typeof id === "string" ? Array.from(new TextEncoder().encode(id)) : id, + payload: + typeof payload === "string" + ? Array.from(new TextEncoder().encode(payload)) + : payload, + }; +} + +export function textRecord(text: string, id?: string | number[]): NFCRecord { + return record(NFCTypeNameFormat.NfcWellKnown, RTD_TEXT, id || [], text); +} + +const protocols = [ + "", + "http://www.", + "https://www.", + "http://", + "https://", + "tel:", + "mailto:", + "ftp://anonymous:anonymous@", + "ftp://ftp.", + "ftps://", + "sftp://", + "smb://", + "nfs://", + "ftp://", + "dav://", + "news:", + "telnet://", + "imap:", + "rtsp://", + "urn:", + "pop:", + "sip:", + "sips:", + "tftp:", + "btspp://", + "btl2cap://", + "btgoep://", + "tcpobex://", + "irdaobex://", + "file://", + "urn:epc:id:", + "urn:epc:tag:", + "urn:epc:pat:", + "urn:epc:raw:", + "urn:epc:", + "urn:nfc:", +]; + +function encodeURI(uri: string): number[] { + let prefix = ""; + + protocols.slice(1).forEach(function (protocol) { + if ((!prefix || prefix === "urn:") && uri.indexOf(protocol) === 0) { + prefix = protocol; + } + }); + + if (!prefix) { + prefix = ""; + } + + const encoded = Array.from( + new TextEncoder().encode(uri.slice(prefix.length)) + ); + const protocolCode = protocols.indexOf(prefix); + // prepend protocol code + encoded.unshift(protocolCode); + + return encoded; +} + +export function uriRecord(uri: string, id?: string | number[]): NFCRecord { + return record( + NFCTypeNameFormat.NfcWellKnown, + RTD_URI, + id || [], + encodeURI(uri) + ); +} + export async function scan( kind: ScanKind, - options?: ScanOptions, + options?: ScanOptions ): Promise { return await window.__TAURI_INVOKE__("plugin:nfc|scan", { kind: kind === ScanKind.Ndef ? "ndef" : "tag", diff --git a/plugins/nfc/ios/Sources/NfcPlugin.swift b/plugins/nfc/ios/Sources/NfcPlugin.swift index f50f8019..d9f52837 100644 --- a/plugins/nfc/ios/Sources/NfcPlugin.swift +++ b/plugins/nfc/ios/Sources/NfcPlugin.swift @@ -13,7 +13,8 @@ enum ScanKind { } enum TagProcessMode { - case write, read + case write(message: NFCNDEFMessage) + case read } class Session { @@ -23,7 +24,6 @@ class Session { let tagProcessMode: TagProcessMode var tagStatus: NFCNDEFStatus? var tag: NFCNDEFTag? - var writeMessage: NFCNDEFMessage? init( nfcSession: NFCReaderSession?, invoke: Invoke, keepAlive: Bool, tagProcessMode: TagProcessMode @@ -81,7 +81,7 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega } func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) { - Logger.error("Tag reader session error") + Logger.error("Tag reader session error \(error)") self.session?.invoke.reject("session invalidated with error: \(error)") } @@ -119,7 +119,7 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega // not an error because we're using invalidateAfterFirstRead: true Logger.debug("readerSessionInvalidationErrorFirstNDEFTagRead") } else { - Logger.error("NDEF reader session error") + Logger.error("NDEF reader session error \(error)") self.session?.invoke.reject("session invalidated with error: \(error)") } } @@ -134,15 +134,15 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega break case let .miFare(tag): metadata["kind"] = "MiFare" - metadata["id"] = tag.identifier + metadata["id"] = byteArrayFromData(tag.identifier) break case let .iso15693(tag): metadata["kind"] = "ISO15693" - metadata["id"] = tag.identifier + metadata["id"] = byteArrayFromData(tag.identifier) break case let .iso7816(tag): metadata["kind"] = "ISO7816Compatible" - metadata["id"] = tag.identifier + metadata["id"] = byteArrayFromData(tag.identifier) break default: metadata["kind"] = "Unknown" @@ -172,8 +172,8 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega self.closeSession(session, error: "cannot connect to tag: \(error)") } else { switch mode { - case .write: - self.writeNDEFTag(session: session, status: status, tag: tag) + case .write(let message): + self.writeNDEFTag(session: session, status: status, tag: tag, message: message) break case .read: if self.session?.keepAlive == true { @@ -187,8 +187,9 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega }) } - private func writeNDEFTag(session: NFCReaderSession, status: NFCNDEFStatus, tag: T) - { + private func writeNDEFTag( + session: NFCReaderSession, status: NFCNDEFStatus, tag: T, message: NFCNDEFMessage + ) { switch status { case .notSupported: self.closeSession(session, error: "Tag is not an NDEF-formatted tag") @@ -199,7 +200,7 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega case .readWrite: if let currentSession = self.session { tag.writeNDEF( - currentSession.writeMessage!, + message, completionHandler: { (error) in if let error = error { self.closeSession(session, error: "cannot write to tag: \(error)") @@ -337,12 +338,13 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega } if let session = self.session { - session.writeMessage = NFCNDEFMessage(records: ndefPayloads) if let nfcSession = session.nfcSession, let tagStatus = session.tagStatus, let tag = session.tag { session.keepAlive = false - self.writeNDEFTag(session: nfcSession, status: tagStatus, tag: tag) + self.writeNDEFTag( + session: nfcSession, status: tagStatus, tag: tag, + message: NFCNDEFMessage(records: ndefPayloads)) } else { invoke.reject( "connected tag not found, please wait for it to be available and then call write()") @@ -350,7 +352,9 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega } else { self.startScanSession( invoke: invoke, kind: .ndef, keepAlive: false, invalidateAfterFirstRead: false, - tagProcessMode: .write) + tagProcessMode: .write( + message: NFCNDEFMessage(records: ndefPayloads) + )) } } diff --git a/plugins/nfc/src/api-iife.js b/plugins/nfc/src/api-iife.js index b9cd5873..90fe53f8 100644 --- a/plugins/nfc/src/api-iife.js +++ b/plugins/nfc/src/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_NFC__=function(n){"use strict";var a,e;return n.ScanKind=void 0,(a=n.ScanKind||(n.ScanKind={}))[a.Ndef=0]="Ndef",a[a.Tag=1]="Tag",n.NFCTypeNameFormat=void 0,(e=n.NFCTypeNameFormat||(n.NFCTypeNameFormat={}))[e.Empty=0]="Empty",e[e.NfcWellKnown=1]="NfcWellKnown",e[e.Media=2]="Media",e[e.AbsoluteURI=3]="AbsoluteURI",e[e.NfcExternal=4]="NfcExternal",e[e.Unknown=5]="Unknown",e[e.Unchanged=6]="Unchanged",n.isAvailable=async function(){return await window.__TAURI_INVOKE__("plugin:nfc|isAvailable")},n.scan=async function(a,e){return await window.__TAURI_INVOKE__("plugin:nfc|scan",{kind:a===n.ScanKind.Ndef?"ndef":"tag",...e})},n.write=async function(n){return await window.__TAURI_INVOKE__("plugin:nfc|write",{records:n})},n}({});Object.defineProperty(window.__TAURI__,"nfc",{value:__TAURI_NFC__})} +if("__TAURI__"in window){var __TAURI_NFC__=function(n){"use strict";const e=[84],t=[85];var r,o;function a(n,e,t,r){return{format:n,kind:"string"==typeof e?Array.from((new TextEncoder).encode(e)):e,id:"string"==typeof t?Array.from((new TextEncoder).encode(t)):t,payload:"string"==typeof r?Array.from((new TextEncoder).encode(r)):r}}n.ScanKind=void 0,(r=n.ScanKind||(n.ScanKind={}))[r.Ndef=0]="Ndef",r[r.Tag=1]="Tag",n.NFCTypeNameFormat=void 0,(o=n.NFCTypeNameFormat||(n.NFCTypeNameFormat={}))[o.Empty=0]="Empty",o[o.NfcWellKnown=1]="NfcWellKnown",o[o.Media=2]="Media",o[o.AbsoluteURI=3]="AbsoluteURI",o[o.NfcExternal=4]="NfcExternal",o[o.Unknown=5]="Unknown",o[o.Unchanged=6]="Unchanged";const c=["","http://www.","https://www.","http://","https://","tel:","mailto:","ftp://anonymous:anonymous@","ftp://ftp.","ftps://","sftp://","smb://","nfs://","ftp://","dav://","news:","telnet://","imap:","rtsp://","urn:","pop:","sip:","sips:","tftp:","btspp://","btl2cap://","btgoep://","tcpobex://","irdaobex://","file://","urn:epc:id:","urn:epc:tag:","urn:epc:pat:","urn:epc:raw:","urn:epc:","urn:nfc:"];return n.RTD_TEXT=e,n.RTD_URI=t,n.isAvailable=async function(){return await window.__TAURI_INVOKE__("plugin:nfc|isAvailable")},n.record=a,n.scan=async function(e,t){return await window.__TAURI_INVOKE__("plugin:nfc|scan",{kind:e===n.ScanKind.Ndef?"ndef":"tag",...t})},n.textRecord=function(t,r){return a(n.NFCTypeNameFormat.NfcWellKnown,e,r||[],t)},n.uriRecord=function(e,r){return a(n.NFCTypeNameFormat.NfcWellKnown,t,r||[],function(n){let e="";c.slice(1).forEach((function(t){e&&"urn:"!==e||0!==n.indexOf(t)||(e=t)})),e||(e="");const t=Array.from((new TextEncoder).encode(n.slice(e.length))),r=c.indexOf(e);return t.unshift(r),t}(e))},n.write=async function(n){return await window.__TAURI_INVOKE__("plugin:nfc|write",{records:n})},n}({});Object.defineProperty(window.__TAURI__,"nfc",{value:__TAURI_NFC__})}