【Web NFC】JavaScriptでNFCタグのデータを読み書きしてみた

f:id:aptpod-tetsu:20200610145317p:plain Webチームの蔵下です。Chrome 81でWeb NFCが試験的に導入されました! ちょっと変わり種なのでネット上ではあまり話題にならなかったのですが、個人的にはビッグニュースでした。

Web NFCを使うと、下記のTweetのような実在するカードとWebサイトを組み合わせたゲームなどが実装できます! すごい!

勢いのままにWeb NFCを触ってみたので、ソースコードを交えて使い方を紹介します。

Web NFCとは?

Web NFCとは、JavaScriptでWebサイトからNFCタグにデータを読み書きできるAPIです。記事公開時点(2020年6月)ではAndroid Chromeでの対応となっているため、NFC機能が搭載されているAndroidスマートフォンで動作が確認できます。

NFCタグ

NFC(Near Field Communication)とは近接型無線通信方式で、デバイスでNFCタグにタッチするだけでデータの読み書きができます。今回Web NFC確認用に Amazon で購入したNFCタグは、容量が144Byteと小さいですが価格も安く手軽に試せます。

f:id:aptpod_tech-writer:20200608171359j:plain
購入したNFCタグ。シールになっていて貼り付けられる。

Web NFCを試す前の下準備

Web NFCはまだ試験的な導入のため、 chrome://flags/#enable-experimental-web-platform-features のフラグを Enabled にするか、 Origin Trials の設置が必要です。

f:id:aptpod_tech-writer:20200608171248p:plain
chrome://flags/#enable-experimental-web-platform-featuresをEnabledに変更

Web NFCのソースコード

Web NFCを実際に試したソースコードを紹介します。

Scan

NFCタグからデータを読み込むには、NDEFReaderscan() でScan機能を起動します。起動した状態でデバイスでNFCタグにタッチすると reading Eventが発火し、NFCタグに付与されているシリアルナンバー( serialNumber )と書き込まれているデータ( message )が受け取れます。一度Scanすると、NFCタグにタッチする度にEventが発火します。

const scan = async () => {
  try {
    const reader = new NDEFReader()
    await reader.scan()

    // Scanは起動しているが、NFCタグからデータが読み込めなかった
    reader.addEventListener('error', (event) => {
      console.log(error)
    })

    // データを読み込んだ
    reader.addEventListener('reading', ({ serialNumber, message }) => {
      const record = message.records[0]
      const { data, recordType } = record
      // recordTypeごとにdecode処理を実行する
    })
  } catch (error) {
    // Scan起動失敗
    console.error(error)
  }
}

受け取った message の中に、NFCに書き込まれていたデータが records として配列で格納されています。複数のデータが書き込まれている場合、 records に複数個の record が入ります。 record に付与されている recordType でデータの種類が特定できるので、recordType ごとにdecode処理を実装します。

※ 初めて Scan を実行する際に下記のようなダイアログが表示されます

f:id:aptpod_tech-writer:20200608171822p:plain
NFC機能確認ダイアログ

Text

recordTypetext の場合はTextデータとなります。 TextDecoder でdecodeすることで、 data を文字列として扱えます。

const { data, encoding, recordType } = record
if (recordType === 'text') {
  const textDecoder = new TextDecoder(encoding)
  const text = textDecoder.decode(data)
  console.log(`Text: ${text}`)
}
JSON

recordTypemimemediaTypeapplication/json の場合はJSONデータとなります。 JSON.parse() でparseすることで、Objectとして扱えます。

const { data, mediaType, recordType } = record
if (recordType === 'mime' && mediaType === 'application/json') {
  const textDecoder = new TextDecoder()
  const json = JSON.parse(textDecoder.decode(data))
  console.log(json)
}
Image

recordTypemimemediaTypeapplication/png の場合はPNGデータ(Image)となります。 dataBlob へ変換し、 image.srcURL.createObjectURL() で渡すことで画像が表示できます。

const { data, mediaType, recordType } = record
if (recordType === 'mime' && mediaType === 'image/png') {
  const blob = new Blob([data], { type: mediaType })
  const image = new Image()
  image.src = URL.createObjectURL(blob)
}

Write

NFCタグへデータを書き込むには、NDEFWriterwrite() を実行し、デバイスをNFCタグにタッチします。データは文字列で書き込まれるため、データの種類ごとにencode処理が必要になります。

Text

Textは文字列のため、そのまま write() で書き込みます。

const writeText = async(text) => {
  try {
    const writer = new NDEFWriter()
    await writer.write(text)
  } catch (error) {
    console.error(error)
  }
}
JSON

dataは JSON.stringify() で文字列へ変換し、 recordType , mediaType を付与した record を作成します。作成した recordrecords 配列に格納して write() で書き込みます。

const writeJson = async(data) => {
  const encoder = new TextEncoder()
  const jsonRecord = {
    recordType: 'mime',
    mediaType: 'application/json',
    data: encoder.encode(JSON.stringify(data)),
  }

  try {
    const writer = new NDEFWriter()
    await writer.write({ records: [jsonRecord] })
  } catch (error) {
    console.error(error)
  }
}
Image

書き込みたいImageを ArrayBuffer へ変換し、 recordType , mediaType を付与した record を作成します。作成した recordrecords 配列に格納して write() で書き込みます。

const writeImage = async (url) => {
  const imageRecord = {
    recordType: 'mime',
    mediaType: 'image/png',
    data: await (await fetch(url)).arrayBuffer(),
  }

  try {
    const writer = new NDEFWriter()
    await writer.write({ records: [imageRecord] })
  } catch (error) {
    console.error(error)
  }
}

Imageを書き込む方法は上記のように用意されていますが、NFCタグは一般的に数百バイトと容量が小さく、私達が普段扱っているような画像は書き込めません。今後のNFCタグの性能向上に期待したいですが、しばらくは画像をNFCタグへ直接書き込むことは難しいでしょう。

まとめ

デバイスをタッチしてアクションを起こすという動作は、スマートフォンや交通系ICカードなどの普及もあり、私達の生活で当たり前のものとなりました。今まではネイティブのアプリケーションでしか実装できなかったNFCのデータ通信も、JavaScriptを使ってWebサイトで実装できるようになると、新しいサービスや体験が生まれてくるかもしれません!

今後も変わり種JavaScriptの情報を発信していきますのでご期待ください!