
With the evolution of browsers on mobile devices more native functionalities are possible in browser based application on a mobile device, a good example of this it NFC scanning. In the past this required you to make a Hybrid or Native mobile app. Nowadays, this is perfectly possible on any Android device that has NFC scanning from in the browser using the web NFC api: Web NFC API - Web APIs | MDN (mozilla.org)
For iOS, this is still not possible because security constrains in iOS. In case of Fiori, you’ll have to create an MDK application with a plugin for NFC scanning as explained here: https://community.sap.com/t5/technology-blogs-by-members/mdk-ios-nfc-uid-scanner-how-to-use-native-f...
This could help you for your new Fiori projects to have NFC scanning functionality. It can also help you to move away from Hybrid or Native Mobile applications which you might have running on BTP Mobile Services. This will save costs of Mobile Service, Mobile Device Management and simplify your application.
Using this in a UI5/Fiori application is super easy to do using the browser API for this. Unfortunately, when you want to integrate this application in BTP Work Zone, it won’t work anymore. Work Zone uses iframes for opening applications and the NFC API can only be used on the top level frames for security reasons.
See the documentation:
Scan: NDEFReader: scan() method - Web APIs | MDN (mozilla.org)
“Web NFC is only available to top-level frames and secure browsing contexts (HTTPS only). ”
Interact with NFC devices on Chrome for Android | Capabilities | Chrome for Developers
As a workaround, I came up with a plugin for Work Zone to make this possible. This plugin will be loaded on the top level frame of Work Zone and trigger the scanning. To notify the application running in Work Zone, I use IFrame communication.
The plugin contains only a “Component.ts” file:
In the Component.ts file, I create the NDEFReader object, provide a success handler to get the scanned object and error handler to know in case something goes wrong. In the end, this will activate the NFC scanner of the device and capture any NFC scanned during your session:
const ndef = new NDEFReader();
ndef.onreading = (event) => {
console.log("Message:");
console.log(JSON.stringify(event.message));
const data = event.message.records[0].data;
let decoder = new TextDecoder("utf-8");
let scan = decoder.decode(data);
};
ndef.onreadingerror = (event) => {
};
await ndef.scan();
Now, to inform the application that triggered the NFC scan with the result, I add the following code to the onRead event handler:
const iframes = document.querySelectorAll('iframe');
for (const iframe of iframes) {
iframe.contentWindow?.postMessage({ action: "NFCSCANNED", tag: {id:scan} }, "*");
}
Same for the Error event handler but using another action:
const iframes = document.querySelectorAll('iframe');
for (const iframe of iframes) {
iframe.contentWindow?.postMessage({ action: "NFCSCANNED", tag: {id:scan} }, "*");
}
Full code of the Component.ts file:
import MessageBox from "sap/m/MessageBox";
import BaseComponent from "sap/ui/core/UIComponent";
/**
* @namespace be.nmbs.plugins.nfcscanning
*/
export default class Component extends BaseComponent {
public static metadata = {
manifest: "json"
};
/**
* The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
* @Public
* @override
*/
public async init() {
// call the base component's init function
super.init();
if ('NDEFReader' in window) {
const ndef = new NDEFReader();
ndef.onreading = (event) => {
console.log("Message:");
console.log(JSON.stringify(event.message));
const data = event.message.records[0].data;
let decoder = new TextDecoder("utf-8");
let scan = decoder.decode(data);
const iframes = document.querySelectorAll('iframe');
for (const iframe of iframes) {
iframe.contentWindow?.postMessage({ action: "NFCSCANNED", tag: {id:scan} }, "*");
}
};
ndef.onreadingerror = (event) => {
const iframes = document.querySelectorAll('iframe');
for (const iframe of iframes) {
iframe.contentWindow?.postMessage({ action: "NFCSCANNED-ERROR", tag: {id:""} }, "*");
}
};
await ndef.scan();
}
}
}
To consume this in an application, I do the following in the eventhandler of a button:
Consuming could look like this:
onclick: async function (oEvent) {
window.addEventListener('message', (event)=>this.readTag(event));
},
readTag: function (event) {
if(!event.data || !event.data.action || event.data.action !== "NFCSCANNED"){
return;
}
MessageToast.show(`NFC Scan result: ${event.data.tag}`);
}
}
For using typescript, it might be useful to have the NFC typescript definition file: Web-nfc.d.ts
// Type definitions for Web NFC
// Project: https://github.com/w3c/web-nfc
// Definitions by: Takefumi Yoshii <https://github.com/takefumi-yoshii>
// TypeScript Version: 3.9
// This type definitions referenced to WebIDL.
// https://w3c.github.io/web-nfc/#actual-idl-index
interface Window {
NDEFMessage: NDEFMessage
}
declare class NDEFMessage {
constructor(messageInit: NDEFMessageInit)
records: ReadonlyArray<NDEFRecord>
}
declare interface NDEFMessageInit {
records: NDEFRecordInit[]
}
declare type NDEFRecordDataSource = string | BufferSource | NDEFMessageInit
interface Window {
NDEFRecord: NDEFRecord
}
declare class NDEFRecord {
constructor(recordInit: NDEFRecordInit)
readonly recordType: string
readonly mediaType?: string
readonly id?: string
readonly data?: DataView
readonly encoding?: string
readonly lang?: string
toRecords?: () => NDEFRecord[]
}
declare interface NDEFRecordInit {
recordType: string
mediaType?: string
id?: string
encoding?: string
lang?: string
data?: NDEFRecordDataSource
}
declare type NDEFMessageSource = string | BufferSource | NDEFMessageInit
interface Window {
NDEFReader: NDEFReader
}
declare class NDEFReader extends EventTarget {
constructor()
onreading: (this: this, event: NDEFReadingEvent) => any
onreadingerror: (this: this, error: Event) => any
scan: (options?: NDEFScanOptions) => Promise<void>
write: (
message: NDEFMessageSource,
options?: NDEFWriteOptions
) => Promise<void>
makeReadOnly: (options?: NDEFMakeReadOnlyOptions) => Promise<void>
}
interface Window {
NDEFReadingEvent: NDEFReadingEvent
}
declare class NDEFReadingEvent extends Event {
constructor(type: string, readingEventInitDict: NDEFReadingEventInit)
serialNumber: string
message: NDEFMessage
}
interface NDEFReadingEventInit extends EventInit {
serialNumber?: string
message: NDEFMessageInit
}
interface NDEFWriteOptions {
overwrite?: boolean
signal?: AbortSignal
}
interface NDEFMakeReadOnlyOptions {
signal?: AbortSignal
}
interface NDEFScanOptions {
signal: AbortSignal
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
10 | |
7 | |
7 | |
6 | |
5 | |
4 | |
4 | |
3 | |
3 | |
3 |