近距離無線通信(NFC)は、通常数センチ未満の近接距離で2台の デバイス間の無線通信を可能にします。 NFCは国際標準(ISO/IEC 18092)で、13.56 MHzで動作する近接結合デバイスの 簡易な無線相互接続のためのインターフェースとプロトコルを定義します。

ハードウェア標準は [[[NFC-STANDARDS]]] に定義されています。

本文書は、NFC技術に基づく特定のユースケースを可能にするAPIを定義します。 本仕様の現在の範囲は NDEF です。

低レベルの I/O 操作(例:ISO-DEP、NFC-A/B、NFC-F)およびホストベースのカード エミュレーション(HCE)は、現在の範囲ではサポートされていません

実装者は本仕様が不安定であると見なされていることに注意する必要があります。 議論に参加していない実装者は、互換性のない方法で仕様が変更されることに 気づくでしょう。最終的に候補勧告(Candidate Recommendation)段階に到達する前に 本仕様を実装することに関心のあるベンダーは、GitHub上のリポジトリを購読し、 議論に参加するべきです。

本文書は、含まれるインターフェースを実装する単一製品に適用される適合基準を定義します: UA(ユーザーエージェント)。

導入

Web NFC のユーザーシナリオは次のとおりです:デバイスを受動的に給電される NFC タグ(プラスチックカードやステッカーなど) に近づけて、データを読み書きします。

NFCは磁気誘導を利用して動作します。つまり、リーダー(能動的な給電デバイス)は小さな電荷を発生させ、それが磁場を生成します。 この磁場が受動デバイスに電力を供給し、それを電気的インパルスに変換してデータを通信します。したがって、 デバイスが範囲内にあるときは常に読み取りが行われます(NFC Analog Specification および NFC Digital Protocol, NFC Forum, 2006 を参照)。 ピアツーピア接続も同様に機能し、デバイスが周期的にイニシエータモードに切り替わってターゲットをスキャンし、 後にターゲットモードに戻ることで動作します。ターゲットが見つかると、データはタグの場合と同様に読み取られます。

NFCは既存のRFID標準に基づいているため、多くのNFCチップセットはRFIDタグの読み取りをサポートしますが、 これらのうちいくつかは単一ベンダーのみがサポートし、NFC標準の一部ではない場合があります。 そのため、本ドキュメントではNFCデータ交換フォーマット(NDEF)とやり取りする方法を規定しています。

用語と慣習

使用される拡張バッカス・ノア形式(ABNF)表記は [[RFC5234]] に規定されています。

NFC は Near Field Communications の略で、13.56 MHzで動作する短距離無線技術を指し、 10 cm 未満の距離でデバイス間の通信を可能にします。NFCの通信プロトコルおよびデータ交換フォーマットは、 ISO/IEC 14443 や FeliCa を含む既存の無線周波数識別(RFID)標準に基づいています。 NFC標準には ISO/IEC 18092[5] や NFC Forum が定義するものが含まれます。完全な一覧は NFC Forum Technical Specifications を参照してください。

NFC adapter は、特定のハードウェア要素(NFCチップ)で実装された NFC 機能へのアクセスを提供する基盤プラットフォーム上のソフトウェア実体です。 デバイスは組み込みのものやUSB経由で接続された複数の NFC アダプタを持つ場合があります。

NFC tag は受動的なNFCデバイスで、blocklistedではありません。 NFC tag は能動的な NFC デバイスが近接しているときに磁気誘導で給電されます。NDEF をサポートする NFC tag は単一の NDEF message を含みます。

メッセージの読み取り方法は、リーダーとタグが同一メーカーであることを要求する 独自技術を通じて行われる場合があります。それらはまた NDEF メッセージを公開することがあります。

NFC peer は他のデバイスと相互作用して NFC を用いてデータを交換できる能動的な給電デバイスです。

現在の仕様では、ピアツーピアはサポートされていません。

NFC deviceNFC peer または NFC tag のいずれかです。

NDEF は NFC Forum Data Exchange Format の略で、[[!NFC-NDEF]] に標準化された 軽量のバイナリメッセージ形式です。

NDEF message は1つ以上のアプリケーション定義の NDEF record をカプセル化します。 NDEF メッセージは NFC tag に保存されたり、NFC対応デバイス間で交換されたりします。

用語 NFC contentNFC tag へ送信または受信される全てのバイトを指します。 現在の API ではこれは NDEF message と同義です。

NFC 標準

NFC は NFC Forum により標準化されており、[[NFC-STANDARDS]] に記述されています。

NDEF 互換のタグタイプ

NFC Forum は、NFC デバイスで操作可能にするために 5 種類の異なるタグタイプのサポートを義務付けています。 同様の要件は Android のようなオペレーティングシステムにも求められます。

それに加えて、MIFARE Standard は古い MIFARE Standard 上で NDEF を動作させる方法を規定しており、 実装者によってはオプションでサポートされることがあります。

NDEF マッピングに関する注記は次にあります: MIFARE Classic as NFC Type MIFARE Classic Tag.

  1. NFC Forum Type 1: このタグは ISO/IEC 14443-3A (NFC-A) に基づきます。タグは書き換え可能で 読み取り専用に設定することができます。メモリサイズは `96` バイトから `2` Kbytes の間です。 通信速度は `106` kbit/s です。他の全てのタイプとは対照的に、これらのタグは NFC フィールド内の複数タグを扱うためのアンチコリジョン保護を持ちません。
  2. NFC Forum Type 2: このタグは ISO/IEC 14443-3A (NFC-A) に基づきます。タグは書き換え可能で 読み取り専用に設定することができます。メモリサイズは `48` バイトから `2` Kbytes の間です。 通信速度は `106` kbit/s です。
  3. NFC Forum Type 3: このタグは日本工業規格(JIS)X 6319-4(ISO/IEC 18092)に基づき、 一般に FeliCa として知られています。タグは書き換え可能または読み取り専用のいずれかに事前設定されています。 メモリは `2` kbytes です。通信速度は `212` kbit/s または `424` kbit/s です。
  4. NFC Forum Type 4: このタグは ISO/IEC 14443-4 A/B (NFC A, NFC B) に基づき、 通信に対して NFC-A または NFC-B のいずれかをサポートします。さらにタグはオプションで ISO-DEP (ISO/IEC 14443(ISO/IEC 14443-4:2008 第4部:伝送プロトコル)で定義されたデータ交換プロトコル)をサポートする場合があります。 タグは書き換え可能または読み取り専用に事前設定されています。可変メモリで最大 `32` kbytes。 通信速度は `106`、`212`、または `424` kbit/s のいずれかをサポートします。
  5. NFC Forum Type 5: このタグは ISO/IEC 15693 (NFC-V) に基づき、ISO/IEC 15693 の RF タグ上で NDEF メッセージの読み書きを可能にします。これらのタグは長距離 RFID リーダーからもアクセス可能です。 NFC 通信は短距離に制限され、送信側ピアがフィールドを生成する ISO/IEC 18092 の アクティブ通信モード を 使用する場合があります。これにより電力消費のバランスがとれ、リンクの安定性が向上します。可変メモリで最大 `64` kbytes。 通信速度は `26.48` kbit/s です。
  6. MIFARE Standard: このタグは、しばしば MIFARE Classic や MIFARE Mini のブランド名で販売され、 ISO/IEC 14443-3A(NFC-Aとしても知られる、ISO/IEC 14443-3:2011 第3部:初期化とアンチコリジョンに定義)に基づきます。 タグは書き換え可能で読み取り専用に設定できます。メモリサイズは `320` から `4` kbytes の間です。 通信速度は `106` kbit/s です。

    MIFARE Standard は NFC Forum のタイプではなく、NXP ハードウェアを使用するデバイスのみが読み取り可能です。 MIFARE Standard に基づくタグの読み書きのサポートは非標準的ですが、普及とレガシーシステムでの使用のために タイプに含まれています。

NFC Forum によって NDEF record 用に標準化されたデータタイプに加えて、バスカードやドアオープナーのような多くの商用製品は MIFARE Standard に基づいており、動作のために特定の NFC チップ(カードとリーダーが同じベンダーであること)を要求する場合があります。

NDEF レコードとフィールド

NDEF recordNDEF message の一部です。各レコードはデータペイロードと関連する 型情報を含むバイナリ構造です。加えて、ペイロードサイズやデータが複数レコードに分割されているか等の データ構造に関する情報も含みます。

一般的なレコードは次のようになります:

最初の3バイト(図の行)が必須です。最初にヘッダバイト、その後に TYPE LENGTH fieldPAYLOAD LENGTH field が続き、どちらもゼロである場合があります。

TNF field(ビット `0-2`、type name format)は型名の形式を示し、ネイティブな NFC ソフトウェアスタックで露出されることが多いです。 このフィールドは以下の NDEF レコードペイロードタイプを表す二進値を取り得ます:
TNF value 説明
0 Empty record
1 NFC Forum [=well-known type record=]
2 MIME type record
3 Absolute-URL record
4 NFC Forum external type record
5 Unknown record
6 Unchanged record
7 将来の使用のために予約

IL field(ビット `3`、id length)は ID LENGTH field が存在するかを示します。 IL field が `0` の場合、ID field は存在しません。

SR field(ビット `4`、short record)は短いレコードを示し、 ペイロード長が `255` バイト以下のものです。通常のレコードは `255` バイトを超え得て、 最大で `4` GB まで可能です。短レコードは長さを示すのに1バイトのみを使用しますが、 通常のレコードは長さを示すのに4バイトを使用します(`2``32``-1` バイト)。

CF field(ビット `5`、chunk flag)はペイロードが複数レコードにわたって chunked されているかを示します。

Web NFC は受信したチャンク化されたレコードをすべて論理レコードに変換し、 送信時に必要であれば透過的にペイロードをチャンク化します。

ME field(ビット `6`、message end)はこのレコードが NDEF message の最後であるかを示します。

MB field(ビット `7`、message begin)はこのレコードが NDEF message の最初であるかを示します。

TYPE LENGTH fieldTYPE field のバイト長を表す符号なし8ビット整数です。

TYPE fieldPAYLOAD field の構造、エンコーディング、および形式を記述する グローバルに一意で管理された識別子であり、TNF field の値によって決定されます。

[[[!NFC-RTD]]] は TYPE field の名前を大文字小文字を区別せずに比較することを要求しています。

ID LENGTH fieldID field のバイト長を表す符号なし8ビット整数です。

ID field は URI 参照([[RFC3986]])の形式の識別子で、一意であり絶対または相対にすることができます (後者の場合、アプリケーションはベースURIを提供しなければなりません)。中間および終端のチャンクレコードは ID field を持つべきではなく、他のレコードは持つことができます。

PAYLOAD LENGTH fieldPAYLOAD field のバイト長を示します。 SR field が `1` の場合、そのサイズは1バイトで、そうでなければ4バイトであり、 それぞれ8ビットまたは32ビットの符号なし整数を表します。

PAYLOAD field はアプリケーションのバイトを運びます。データの内部構造は NDEF にとって不透明です。 後述の特定のケースでは、このフィールドがデータとして NDEF message を含む場合があります。

NDEF レコードの種類

空の NDEF レコード(TNF 0)

empty recordTYPE LENGTH fieldID LENGTH field、および PAYLOAD LENGTH field は `0` でなければならず、 したがって TYPE fieldID field、および PAYLOAD field は存在してはなりません。

Well-known type records(TNF 1)

NFC Forum は [[NFC-RTD]](Resource Type Definition 仕様)で、テキスト、URL、メディア等の 有用なサブレコードタイプの小さなセットを標準化しています。これらを well-known type record と呼びます。 さらに、スマートポスター(オプションで埋め込まれた URL、テキスト、署名、アクション用のレコードを含む)やハンドオーバーレコードのような より複雑な相互作用のために設計されたレコードタイプもあります。

well-known type recordsTYPE field に格納される型情報は二種類あります: ローカル型グローバル型

Well-known ローカル型

NFC Forum の local type は NFC Forum またはアプリケーションによって定義され、 常に小文字の文字または数字で始まります。これらは通常、包含するレコードのローカルコンテキスト内でのみ一意な短い文字列です。 型の意味が包含するレコードのローカルコンテキストの外で重要でない場合や、ストレージ使用量が厳しい制約である場合に使用されます。 Smart poster はローカル型の使用例です。

[=local type=] は包含レコードの型で定義されるため、ネームスペースを必要としません。 このため同じローカル型名が別のレコード型内で異なる意味や異なるペイロード型で使用されることがあります。

Well-known グローバル型

NFC Forum の global type は NFC Forum によって定義・管理され、通常は大文字で始まります。 例:テキストのための "`T`"、URL のための "`U`"、スマートポスターの "`Sp`"、 署名の "`Sig`"、ハンドオーバーキャリアの "`Hc`"、ハンドオーバー要求の "`Hr`"、ハンドオーバー選択の "`Hs`" など。

テキストレコード
Text record は [[NDEF-TEXT]] で定義された [=well-known type record=] です。 TNF field は `1` で、TYPE field は "`T`"(`0x54`)です。 PAYLOAD field の最初のバイトはステータスバイトで、その次に US-ASCII エンコーディングの [=language tag=] が続きます。 残りのペイロードは実際のテキストで、ステータスバイトに従って UTF-8 または UTF-16 のいずれかでエンコードされます:
  • ビット0から5は [=language tag=] の長さを定義します。
  • ビット6は `0` です。
  • ビット7が `0` の場合、ペイロードは UTF-8 でエンコードされ、ビット7が `1` の場合は UTF-16 でエンコードされます。
URI レコード

URI record は [[NDEF-URI]] で定義されています。 TNF field は `1`、TYPE field は "`U`"(`0x55`)です。 PAYLOAD field の最初のバイトは URI 識別子コードで、略語表のインデックスになっており、 その値が残りのURIの前に付加されます。例えば値 `0` は何も付加しないことを示し、 `1` は "`http://www.`"、`0x04` は "`https://`" を表します。 残りのペイロードは UTF-8 文字列としての URI の残りを含みます(最初のバイトが `0` の場合は全体の URI を表します)。

URI は [[RFC3987]] で定義されており、実際には UTF-8 エンコードされた IRI で、URN や URL になり得ます。

スマートポスター(Smart poster)レコード
Smart poster は [[NDEF-SMARTPOSTER]] に定義されており、 ペイロードとして NDEF message を含む NDEF レコードとして指定の Web コンテンツを記述します。以下のレコードを含むことがあります:
  • 単一の必須 URI recordsmart poster コンテンツを指します。

    [[NDEF-SMARTPOSTER]] は、アプリケーションが NDEF message 内に存在する場合、 他の URI record が同時に含まれていても、smart poster レコードのみを使用することを 規定しています。

  • 0 個以上の Text records がコンテンツに関連する title record として機能します。 複数のタイトルレコードが存在する場合、それらは異なる language tags でなければなりません。 アプリケーションはエンドユーザーに提示するために1つの title record を選択するべきです。
  • 0 個以上の MIME type records がコンテンツに関連する icon record として機能します。 MIME type は通常 "`image/jpg`"、"`image/png`"、"`image/gif`"、あるいは "`video/mpeg`" などです。 アプリケーションはエンドユーザーに提示するために1つの icon record を選択するべきです。
  • 1 つのオプションの type record があり、smart poster 固有の [=local type name=] "`t`" を持ち、 PAYLOAD fieldURI record が参照するコンテンツの MIME タイプ(UTF-8 エンコード)を含みます。
  • 1 つのオプションの size record があり、smart poster 固有の [=local type name=] "`s`" を持ち、 PAYLOAD field は URI レコードで参照されるオブジェクトのサイズを示す 4 バイトの 32 ビット符号なし整数を含みます。
  • 1 つのオプションの action record があり、smart poster 固有の [=local type name=] "`act`" を持ち、 PAYLOAD field は単一バイトを含み、その値は次の意味を持ちます:
    説明
    0 アクションを実行する
    1 後で保存する
    2 編集のために開く
    3..0xFF 将来の使用のために予約

    action record が欠落している場合、smart poster コンテンツに対するデフォルトのアクションはありません。

    NDEF 標準化時点では、値 `0`("do the action")は SMS 送信、通話、ブラウザ起動のようなユースケース向けを想定していました。 同様に、値 `1`("save the content for later processing")は SMS を受信トレイに保存、URL をブックマークに保存、 電話番号を連絡先に保存する等のユースケースを想定していました。値 `2`("open for editing")は スマートポスターコンテンツを編集のためにデフォルトアプリで開くことを意図していました。

    実装はここで定義されたアクションに対して標準化された挙動を実装する必要はありません。 この API ではどのアクションをアプリケーションが定義するかはアプリ次第です(上記のユースケースを含め得ます)。 Web NFC は値を提供するだけです。

  • smart poster は他のレコードを含む場合があり、これらはアプリケーション固有の方法で処理できます。
以下の例は、テキストと URL レコードを埋め込んだスマートポスターのレコードを示します。
署名レコード

NDEF Signature は [[NDEF-SIGNATURE]] で定義されています。 その TYPE field は "`Sig`"(`0x53`, `0x69`, `0x67`)を含み、 PAYLOAD field はバージョン、署名、証明書チェーンを含みます。

現在の仕様では、これはサポートされていません。

ハンドオーバーレコード

NFC handover は [[NFC-HANDOVER]] で定義されており、 Bluetooth や WiFi のような代替通信キャリアのネゴシエーションと有効化を可能にする対応するメッセージ構造です。 ネゴシエートされた通信キャリアは、その後(別途)両デバイス間で写真送信、Bluetooth プリンタへの印刷、 テレビへのビデオストリーミングなどの特定のアクティビティを行うために使用されます。

現在の仕様では、これはサポートされていません。

MIME type records(TNF 2)

MIME type record は関連付けられた MIME type を持つバイナリデータを格納するレコードです。

Absolute-URL records(TNF 3)

absolute-URL record では TYPE fieldabsolute-URL string を含み、ペイロードではありません。

注:Windows Phone のような一部プラットフォームはペイロードに追加データを格納していましたが、 これらのレコードの任意のペイロードデータは Android のような他のプラットフォームによって無視されます。 Android でそのようなレコードを読み取ると、Chrome で URL を読み込もうとし、クライアントアプリケーション向けには 意図されていません。

External type records(TNF 4)

NFC Forum の external type record は アプリケーション指定のデータタイプ用で、[[[NFC-RTD]]] に定義されています。

external type はプレフィックス `"urn:nfc:ext:"` に続けて所有者の [=domain=] 名、 その後に `U+003A`(`:`)を追加し、ゼロ以外のタイプ名を続ける URN です。例えば `"urn:nfc:ext:w3.org:atype"` は TYPE field に `"w3.org:atype"` として格納されます。

Unknown type records(TNF 5)

unknown record は関連付けられた MIME type を持たない不透明なデータを格納するレコードであり、 `application/octet-stream` のデフォルト MIME type が想定され得ます。 [[NFC-NDEF]] 仕様は、NDEF パーサーがペイロードを処理せずに保存または転送することを推奨しています。

Unchanged type records(TNF 6)

unchanged record はチャンク化されたデータセットのレコードチャンクであり、最初のレコードを除く任意のレコードに使用されます。 chunked ペイロードは複数の NDEF record に分散され、次のルールに従います:
  • 最初のチャンクレコードは CF field が設定され、全体のチャンク化ペイロードの型を示す TYPE field が設定され、 全体のチャンク化ペイロードで使用される識別子を示す ID field を持つことがあります。その PAYLOAD LENGTH field は このレコード内のペイロードチャンクのサイズを示します。
  • 中間チャンクレコードは CF field が設定され、最初のチャンクと同じ ID field を持ち、 TYPE LENGTH fieldIL field は `0` でなければならず、TNF field は `6`(unchanged)でなければなりません。
  • 終端チャンクレコードはこのフラグがクリアされ、他は中間チャンクレコードと同じルールに従います。
  • チャンク化されたペイロードは単一の NDEF message に含まれなければならないため、 最初および中間のチャンクレコードは ME field を設定できません。
最初のレコード:
中間レコード:
最終レコード:

Web NFC の任意の実装は、チャンク化されたレコードを単一の論理レコードとして透過的に公開しなければなりません。

ユースケース

いくつかの NFC ユーザーシナリオは こちら および Web NFC Use Cases ドキュメントに列挙されています。基本的な Web NFC の相互作用は以下の通りです。

NFC tag の読み取り

Web NFC を使用している top-level browsing context の {{Document}} が 可視状態のときに、NDEF message を含む NFC tag を読み取ります。 例えば、ウェブページがユーザーに NFC タグをタップするよう指示し、その後タグから情報を受け取る場合です。

NFC tag への書き込み

ユーザーが NFC tag に書き込み可能なウェブページを開きます。書き込み操作には次のいずれかが含まれます:
  1. フォーマットされていない NFC tag への書き込み。
  2. フォーマット済みだが空の NFC tag への書き込み。
  3. 既に NDEF message を含む NFC tag への書き込み。
  4. その他の書き込み可能な NFC tag(例:汎用タグの上書き)への書き込み。

注:NFC tag への書き込み操作は常に読み取り操作も伴う点に注意してください。

NFC tag を読み取り専用にする

ユーザーが NFC tag を恒久的に読み取り専用にできるウェブページを開きます。操作には次のいずれかが含まれます:
  1. フォーマットされていない NFC tag を恒久的に読み取り専用にする。
  2. フォーマット済みだが空の NFC tag を恒久的に読み取り専用にする。
  3. 既に NDEF message を含む NFC tag を恒久的に読み取り専用にする。

注:NFC tag を恒久的に読み取り専用にする操作は常に読み取り操作を伴います。

複数の NFC adapter のサポート

ユーザーは内蔵アダプタに加えて、1 台以上の外部 NFC adapter をデバイスに接続する場合があります。ユーザーは任意の NFC adapter を使用できます。

機能

Web NFC 仕様のハイレベルな機能は次のとおりです:
  1. 単一または複数の NFC adapter を持つデバイスをサポートする。 NFC 関数を呼び出した時点で複数のアダプタが存在する場合、UA は全ての NFC adapter を並列に動作させます。
  2. 受動的(スマートカード、タグなど)な NFC デバイスとの通信をサポートする。
  3. ユーザーが発見した受動的 NFC デバイスに対して(例:読み取り、書き込み、トランシーブ)操作を行えるようにし、 その過程で読み取られたペイロードに NDEF message としてアクセスできるようにする。
  4. 書き込み可能なタグのような互換デバイスが範囲内に入ったときに、NDEF record を介してペイロードを NDEF message として書き込めるようにする。

本セクションでは、開発者が本仕様のさまざまな機能をどのように利用できるかを示します。

機能サポート

Web NFC がサポートされているかどうかの検出は {{NDEFReader}} オブジェクトをチェックすることで行えます。 ただし、これは NFC ハードウェアが利用可能であることを保証するものではありません。

      if ("NDEFReader" in window) { /* Scan and write NDEF Tags */ }
    

データ書き込みに関する一般情報

データの書き込みは概して簡単ですが、NFC での書き込みの仕組みに関していくつか注意点があります。

NFC リーダーはポーリングで動作するため、タグに書き込んだり恒久的に読み取り専用にするには、 まずタグが見つかって読み取られる必要があり、つまりポーリングを初期化する必要があります。

既に `scan()` を呼んでポーリングが開始されていない場合、`write()` と `makeReadOnly()` メソッドは タグが見つかって読み取られ、操作が試行されるまで一時的にポーリングを開始します。

つまり、フローは最初にタグが見つかったときに読み取りが行われ、その後に書き込み操作が続くということです。

これにより、`scan()` が実行中で `reading` イベントのイベントリスナがある場合、 `write()` や `makeReadOnly()` の操作中にそのイベントが一度発火する可能性があり、 意図した挙動でない場合があります。

以下のセクションではこの挙動に対処する方法を説明しますが、まずは簡単な例をいくつか示します。

テキスト文字列を書き込む

テキスト文字列を NFC タグに書き込むのは簡単です。

      const ndef = new NDEFReader();
      ndef.write(
        "Hello World"
      ).then(() => {
        console.log("Message written.");
      }).catch(error => {
        console.log(`Write failed :-( try again: ${error}.`);
      });
    

URL を書き込む

URL タイプの NDEF レコードを書き込むには、NDEFMessage を使用します。ここでは async/await を利用します。

      const ndef = new NDEFReader();
      try {
        await ndef.write({
          records: [{ recordType: "url", data: "https://w3c.github.io/web-nfc/" }]
        });
      } catch {
        console.log("Write failed :-( try again.");
      };
    

書き込み中の初期読み取りの扱い

書き込むにはタグが見つかって読み取られる必要があります。これにより、既存データやシリアル番号を確認して 実際に書き込みたいタグかどうかを判断することができます。

このため、`write()` は `reading` イベント内から呼び出すことが推奨されます。`makeReadOnly()` についても同様です。

以下の例は共通の `reading` ハンドラと単独の書き込みのために特化したハンドラを調整する方法を示しています。

      const ndef = new NDEFReader();
      let ignoreRead = false;

      ndef.onreading = (event) => {
        if (ignoreRead) {
          return; // write pending, ignore read.
        }

        console.log("We read a tag, but not during pending write!");
      };

      function write(data) {
        ignoreRead = true;
        return new Promise((resolve, reject) => {
          ndef.addEventListener("reading", event => {
            // Check if we want to write to this tag, or reject.
            ndef.write(data).then(resolve, reject).finally(() => ignoreRead = false);
          }, { once: true });
        });
      }

      await ndef.scan();
      try {
        await write("Hello World");
        console.log("We wrote to a tag!")
      } catch(err) {
        console.error("Something went wrong", err);
      }
    

タイムアウト付きで書き込みをスケジュールする

書き込み操作に時間制限を設けることが有用な場合があります。例えばユーザーにタグをタッチするよう促し、 一定時間内にタグが見つからなければタイムアウトする、という場合です。

      const ndef = new NDEFReader();
      ndef.onreading = (event) => console.log("We read a tag!");

      function write(data, { timeout } = {}) {
        return new Promise((resolve, reject) => {
          const ctlr = new AbortController();
          ctlr.signal.onabort = () => reject("Time is up, bailing out!");
          setTimeout(() => ctlr.abort(), timeout);

          ndef.addEventListener("reading", event => {
            ndef.write(data, { signal: ctlr.signal }).then(resolve, reject);
          }, { once: true });
        });
      }

      await ndef.scan();
      try {
        // Let's wait for 5 seconds only.
        await write("Hello World", { timeout: 5_000 });
      } catch(err) {
        console.error("Something went wrong", err);
      } finally {
        console.log("We wrote to a tag!");
      }
    

スキャンエラーの処理

この例は {{NDEFReader/scan}} のプロミスが拒否された場合や `readingerror` が発火した場合に何が起きるかを示します。

      const ndef = new NDEFReader();
      ndef.scan().then(() => {
        console.log("Scan started successfully.");
        ndef.onreadingerror = (event) => {
          console.log("Error! Cannot read data from the NFC tag. Try a different one?");
        };
        ndef.onreading = (event) => {
          console.log("NDEF message read.");
        };
      }).catch(error => {
        console.log(`Error! Scan failed to start: ${error}.`);
      });
    

単一タグを一度だけ読み取る

この例は単一のタグを読み取ってポーリングを停止する便利関数を簡単に作る方法を示し、 不要な作業を減らしてバッテリ寿命を節約します。

この例は指定ミリ秒後にタイムアウトするよう簡単に拡張できます。

      const ndef = new NDEFReader();

      function read() {
        return new Promise((resolve, reject) => {
          const ctlr = new AbortController();
          ctlr.signal.onabort = reject;
          ndef.addEventListener("reading", event => {
            ctlr.abort();
            resolve(event);
          }, { once: true });
          ndef.scan({ signal: ctlr.signal }).catch(err => reject(err));
        });
      }

      read().then(({ serialNumber }) => {
        console.log(serialNumber);
      });
    

タグからデータを読み、空のタグに書き込む

この例はタグ上に保存できるさまざまな種類のデータを読み取る方法を示します。タグが未フォーマットまたは空のレコードを含む場合、 値 "Hello World" のテキストメッセージを書き込みます。

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = async ({ message }) => {
        if (message.records.length == 0 ||               // unformatted tag
            message.records[0].recordType == "empty") {  // empty record
          await ndef.write({
            records: [{ recordType: "text", data: "Hello World" }]
          });
          return;
        }

        const decoder = new TextDecoder();
        for (const record of message.records) {
          switch (record.recordType) {
            case "text":
              const textDecoder = new TextDecoder(record.encoding);
              console.log(`Text: ${textDecoder.decode(record.data)} (${record.lang})`);
              break;
            case "url":
              console.log(`URL: ${decoder.decode(record.data)}`);
              break;
            case "mime":
              if (record.mediaType === "application/json") {
                console.log(`JSON: ${JSON.parse(decoder.decode(record.data))}`);
              }
              else if (record.mediaType.startsWith("image/")) {
                const blob = new Blob([record.data], { type: record.mediaType });

                const img = document.createElement("img");
                img.src = URL.createObjectURL(blob);
                img.onload = () => window.URL.revokeObjectURL(this.src);

                document.body.appendChild(img);
              }
              else {
                console.log(`Media not handled`);
              }
              break;
            default:
              console.log(`Record not handled`);
          }
        }
      };
    

NFC タグでゲーム進捗を保存・復元する

関連するデータソースのフィルタリングはカスタムレコード識別子(この場合 "`/my-game-progress`")を使用して行えます。 データを読み取ると直ちにカスタム NDEF データレイアウトで書き込みを行いゲーム進捗を更新します。

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = async ({ message }) => {
        if (message.records[0]?.id !== "/my-game-progress")
          return;
        console.log(`Game state: ${ JSON.stringify(message.records) }`);

        const encoder = new TextEncoder();
        const newMessage = {
          records: [{
            id: "/my-game-progress",
            recordType: "mime",
            mediaType: "application/json",
            data: encoder.encode(JSON.stringify({
              level: 3,
              points: 4500,
              lives: 3
            }))
          }]
        };
        await ndef.write(newMessage);
        console.log("Message written");
      };
    

JSON を書き込み・読み取り(直列化・逆直列化)

JSON データの保存と受信は、直列化と逆直列化で簡単に行えます。

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = (event) => {
        const decoder = new TextDecoder();
        for (const record of event.message.records) {
          if (record.mediaType === "application/json") {
            const json = JSON.parse(decoder.decode(record.data));
            const article =/^[aeio]/i.test(json.title) ? "an" : "a";
            console.log(`${json.name} is ${article} ${json.title}`);
          }
        }
      };

      const encoder = new TextEncoder();
      await ndef.write({
        records: [
          {
            recordType: "mime",
            mediaType: "application/json",
            data: encoder.encode(JSON.stringify({
              name: "Benny Jensen",
              title: "Banker"
            }))
          },
          {
            recordType: "mime",
            mediaType: "application/json",
            data: encoder.encode(JSON.stringify({
              name: "Zoey Braun",
              title: "Engineer"
            }))
          }]
      });
    

データを書き込み既存データを出力する

データの書き込みには NFC tag をタップする必要があります。

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = async (event) => {
        const decoder = new TextDecoder();
        for (const record of event.message.records) {
          console.log("Record type:  " + record.recordType);
          console.log("MIME type:    " + record.mediaType);
          console.log("=== data ===\n" + decoder.decode(record.data));
        }

        try {
          await ndef.write("Overriding data is fun!");
        } catch(error) {
          console.log(`Write failed :-( try again: ${error}.`);
        }
      };
    

NDEF メッセージのリスニングを停止する

{{NDEFScanOptions/signal}} を使用して 3 秒間 NDEF メッセージを読み取ります。

      const ndef = new NDEFReader();
      const ctrl = new AbortController();

      await ndef.scan({ signal: ctrl.signal });
      ndef.onreading = () => {
        console.log("NDEF message read.");
      };

      ctrl.signal.onabort = () => {
        console.log("We're done waiting for NDEF messages.");
      };

      // Stop listening to NDEF messages after 3s.
      setTimeout(() => ctrl.abort(), 3_000);
    

スマートポスターメッセージを書き込む

      const ndef = new NDEFReader();
      const encoder = new TextEncoder();
      await ndef.write({ records: [
        {
          recordType: "smart-poster",  // Sp
          data: { records: [
            {
              recordType: "url",  // URL record for the Sp content
              data: "https://my.org/content/19911"
            },
            {
              recordType: "text",  // title record for the Sp content
              data: "Funny dance"
            },
            {
              recordType: ":t",  // type record, a local type to Sp
              data: encoder.encode("image/gif") // MIME type of the Sp content
            },
            {
              recordType: ":s",  // size record, a local type to Sp
              data: new Uint32Array([4096]) // byte size of Sp content
            },
            {
              recordType: ":act",  // action record, a local type to Sp
              // do the action, in this case open in the browser
              data: new Uint8Array([0])
            },
            {
              recordType: "mime", // icon record, a MIME type record
              mediaType: "image/png",
              data: await (await fetch("icon1.png")).arrayBuffer()
            },
            {
              recordType: "mime", // another icon record
              mediaType: "image/jpg",
              data: await (await fetch("icon2.jpg")).arrayBuffer()
            }
          ]}
        }
      ]});
    

NDEF メッセージをペイロードに含む外部レコードを読み取る

外部タイプレコードはアプリケーション定義のレコードを作成するために使用できます。 これらのレコードはペイロードとして NDEF message を含むことがあり、 アプリケーションのコンテキストで使用される local types を含む独自の NDEF records を持ちます。

smart poster レコードタイプもペイロードとして NDEF message を含む点に注意してください。

NDEF はレコードの順序を保証しないため、関連データをカプセル化する目的で NDEF message をペイロードに持つ外部タイプレコードを使うことは有用です。

次の例は、テキストレコードと local type "act"(アクション)を含む NDEF message を ペイロードとして持つソーシャル投稿向けの外部レコードを読み取る方法を示しています。 "act" の定義は smart poster から借用されていますが、ローカルアプリケーションの文脈で使用されます。

      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = (event) => {
        const externalRecord = event.message.records.find(
          record => record.type == "example.com:smart-poster"
        );

        let action, text;

        for (const record of externalRecord.toRecords()) {
          if (record.recordType == "text") {
            const decoder = new TextDecoder(record.encoding);
            text = decoder.decode(record.data);
          } else if (record.recordType == ":act") {
            action = record.data.getUint8(0);
          }
        }

        switch (action) {
          case 0: // do the action
            console.log(`Post "${text}" to timeline`);
            break;
          case 1: // save for later
            console.log(`Save "${text}" as a draft`);
            break;
          case 2: // open for editing
            console.log(`Show editable post with "${text}"`);
            break;
        }
      };
    

NDEF メッセージをペイロードに含む外部レコードを書き込む

外部タイプレコードは、ペイロードとして NDEF message を含むようなアプリケーション定義レコードを作成するために使用できます。

      const ndef = new NDEFReader();
      await ndef.write({ records: [
        {
          recordType: "example.game:a",
          data: {
            records: [
              {
                recordType: "url",
                data: "https://example.game/42"
              },
              {
                recordType: "text",
                data: "Game context given here"
              },
              {
                recordType: "mime",
                mediaType: "image/png",
                data: getImageBytes(fromURL)
              }
            ]
          }
        }
      ]});
    

外部レコード内の不明レコードを読み書きする

不明タイプレコードは開発者がその意味を知っている場合、外部タイプレコード内で有用であり、 そのため MIME タイプを指定する必要を避けられます。

      const encoder = new TextEncoder();
      const ndef = new NDEFReader();
      await ndef.write({ records: [
        {
          recordType: "example.com:shoppingItem", // External record
          data: {
            records: [
              {
                recordType: "unknown", // Shopping item name
                data: encoder.encode("Food")
              },
              {
                recordType: "unknown", // Shopping item description
                data: encoder.encode("Provide nutritional support for an organism.")
              }
            ]
          }
        }
      ]});
    
      const ndef = new NDEFReader();
      await ndef.scan();
      ndef.onreading = (event) => {
        const shoppingItemRecord = event.message.records[0];
        if (shoppingItemRecord?.recordType !== "example.com:shoppingItem")
          return;

        const [nameRecord, descriptionRecord] = shoppingItemRecord.toRecords();

        const decoder = new TextDecoder();
        console.log("Item name: " + decoder.decode(nameRecord.data));
        console.log("Item description: " + decoder.decode(descriptionRecord.data));
      };
    

NFC タグを恒久的に読み取り専用にする

NFC タグを恒久的に読み取り専用にすることは簡単です。

      const ndef = new NDEFReader();
      ndef.makeReadOnly().then(() => {
        console.log("NFC tag has been made permanently read-only.");
      }).catch(error => {
        console.log(`Operation failed: ${error}`);
      });
    
      const ndef = new NDEFReader();
      try {
        await ndef.write("Hello world");
        console.log("Message written.");
        await ndef.makeReadOnly();
        console.log("NFC tag has been made permanently read-only after writing to it.");
      } catch (error) {
        console.log(`Operation failed: ${error}`);
      }
    

データ表現

The NDEFMessage interface

任意の NDEF message の内容は NDEFMessage インターフェイスによって公開されます:

      [SecureContext, Exposed=Window]
      interface NDEFMessage {
        constructor(NDEFMessageInit messageInit);
        readonly attribute FrozenArray<NDEFRecord> records;
      };

      dictionary NDEFMessageInit {
        required sequence<NDEFRecordInit> records;
      };
    

records プロパティは listNDEF record を表し、 NDEF message を定義します。

NDEFMessageInit 辞書は NDEF message を初期化するために使用されます。

The NDEFRecord interface

任意の NDEF record の内容は NDEFRecord インターフェイスによって公開されます:

      [SecureContext, Exposed=Window]
      interface NDEFRecord {
        constructor(NDEFRecordInit recordInit);

        readonly attribute USVString recordType;
        readonly attribute USVString? mediaType;
        readonly attribute USVString? id;
        readonly attribute DataView? data;

        readonly attribute USVString? encoding;
        readonly attribute USVString? lang;

        sequence<NDEFRecord>? toRecords();
      };

      dictionary NDEFRecordInit {
        required USVString recordType;
        USVString mediaType;
        USVString id;

        USVString encoding;
        USVString lang;

        any data; // DOMString or BufferSource or NDEFMessageInit
      };
    

mediaType プロパティは MIME type を表し、 NDEF record のペイロードの MIME タイプを示します。

recordType プロパティは NDEF record の種類を表します。

id プロパティは record identifier を表し、 これは絶対または相対の URL です。識別子の一意性は本仕様ではなく生成者によって保証されます。

NFC NDEF 仕様では record identifier の代わりに "message identifier" と "payload identifier" の用語が使われますが、 識別子はメッセージ(レコードの集合)ではなく各レコードに紐付いており、ペイロードが存在しない場合でも含まれる可能性があります。

encoding 属性は、ペイロードがテキストデータである場合に使用される エンコーディング名(encoding name)を表します。

lang 属性は、エンコードされた場合のペイロードの言語タグ(language tag)を表します。

language tag は [[BCP47]] 仕様で定義された Language-Tag の生成規則に一致する string です (可能な値の正式な一覧は IANA Language Subtag Registry を参照してください)。 言語範囲は U+002D ハイフンマイナス ("-") で区切られた 1 つ以上の subtags で構成されます。 例えば 'en-AU' はオーストラリアで話される英語を表し、'fr-CA' はカナダで話されるフランス語を表します。 [[RFC5646]] セクション2.2.9 の有効性基準を満たし、IANA レジストリへの参照なしに検証可能な言語タグは構造的に有効と見なされます。

data プロパティは PAYLOAD field のデータを表します。

toRecords() メソッドは呼び出されたとき、NDEF Record に対して convert NDEFRecord.data bytes を実行した結果を返さなければなりません。

NDEFRecordInit 辞書は、record type である recordType と、 任意の record identifier id およびペイロードデータ dataNDEF record を初期化するために使用されます。

さらに、特定の record types にのみ適用される追加のオプションフィールドがあります:
  • "mime": オプションの MIME type mediaType
  • "text": オプションのエンコーディングラベル encoding と言語タグ lang

NDEFRecordInit のデータ型から NDEF record 型へのマッピングは、 アルゴリズム的な手順で示されており、[[[#steps-receiving]]] および [[[#writing-content]]] セクションで説明されています。

convert NDEFRecord.data bytes を実行するには、 |record:NDEFRecord| が与えられたとき、次の手順を実行してください:

  1. |bytes:byte sequence| を record の data 属性の値とします。
  2. |recordType:record type| を |record| の recordType 属性の値とします。
  3. |recordType| が "`smart-poster`" の場合、|bytes| と `"smart-poster"` を与えて parse records from bytes を実行した結果を返します。
  4. |recordType| に対して validate external type を実行して true が返る場合、|bytes| と `"external"` を与えて parse records from bytes を実行した結果を返します。
  5. それ以外の場合、{ "NotSupportedError" } を [= exception/throw =] します。

The record type string

この文字列は NDEFRecord に許可される record types を定義します。 [[[#data-mapping]]] セクションはそれが NDEF record 型にどのようにマッピングされるかを説明します。

標準化された well known type name は次のいずれかです:

The "empty" string
emptyNDEFRecord を表す値。
The "text" string
Text record を表す値。
The "url" string
URI record を表す値。
The "smart-poster" string
Smart poster レコードを表す値。
The "absolute-url" string
absolute-URL record を表す値。
The "mime" string
MIME type record を表す値。
The "unknown" string
unknown record を表す値。

[=well known type names=] に加えて、組織がカスタムの external type name を作成することも可能です。 これはドメイン名とカスタムタイプ名をコロン `U+003A` (`:`) で区切った文字列です。

アプリケーションは local type name を使用することもできます。これは小文字または数字で始まらなければならない文字列で、 NFC Forum の [=local type=] を表します。通常、親レコードのペイロードである NDEFMessage のレコード内で使用されます(例:smart poster)。 local type のコンテキストは、このレコードが属する NDEFMessage のペイロードである親レコードであり、 local type name はそのコンテキストで他の型名と競合しないようにするべきです。

Web NFC の任意の実装はチャンク化されたレコードを単一の論理レコードとして透過的に公開しなければならないため、 unchanged record は明示的に表現されません。

2 つの well-known type records(NFC Forum の local type および global type を含む)は、 文字ごとに大文字小文字を区別して比較しなければなりません。

2 つの external types は文字ごとに小文字・大文字を区別せずに比較しなければなりません。

任意の well-known type recordexternal type のバイナリ表現は相対 URI(RFC 3986)として書かれなければならず、 それぞれ名前空間識別子 (NID) "`nfc`" と名前空間固有文字列 (NSS) "`wkt`" および "`ext`" を省略します。つまり、 "`urn:nfc:wkt:`" および "`urn:nfc:ext:`" プレフィックスを省略します。 例えば "`urn:nfc:ext:company.com:a`" は "`company.com:a`" として格納され、 Text record の well-known type records は "`urn:nfc:wkt:T`" ですが、"`T`" として格納されます。

データマッピング

[[[#writing-content]]] セクションで使用されるように、NDEFRecordInit のデータ型から NDEF record 型へのマッピングは次の通りです:

{{recordType}} {{mediaType}} {{data}} record type [=TNF field=] [=TYPE field=]
"`empty`" unused unused Empty record 0 unused
"`text`" unused {{BufferSource}} or
{{DOMString}}
[=Well-known type record=] 1 "`T`"
"`url`" unused {{DOMString}} [=Well-known type record=] 1 "`U`"
"`smart-poster`" unused {{NDEFMessageInit}} [=Well-known type record=] 1 "`Sp`"
[=local type name=] prefixed by a colon `U+003A` (`:`), e.g., "`:act`", "`:s`", and "`:t`" unused {{BufferSource}} or {{NDEFMessageInit}} [=Local type=] record* 1 [=local type name=], e.g., "`act`", "`s`", and "`t`"
"`mime`" [= MIME type =] {{BufferSource}} MIME type record 2 [= MIME type =]
"`absolute-url`" unused {{DOMString}} url Absolute-URL record 3 [=Absolute-URL=]
[=external type name=] unused {{BufferSource}} or
{{NDEFMessageInit}}
External type record 4 [=external type name=]
"`unknown`" unused {{BufferSource}} [=Unknown record=] 5 unused

* [=local type=] レコードは別のレコードの NDEFMessage ペイロード内に埋め込まれている必要があります。

受信した NDEF message に対して使用されるように、NDEF record 型から NDEFRecord へのマッピングは次の通りです。

record type [=TNF field=]
[=TYPE field=] {{recordType}} {{mediaType}}
[=Empty record=] 0 unused "`empty`" null
[=Well-known type record=] 1 "`T`" "`text`" null
[=Well-known type record=] 1 "`U`" "`url`" null
[=Well-known type record=] 1 "`Sp`" "`smart-poster`" null
[=Local type=] record* 1 [=local type name=], e.g., "`act`", "`s`", and "`t`" [=local type name=] prefixed by a colon `U+003A` (`:`), e.g., "`:act`", "`:s`", and "`:t`" null
[=MIME type record=] 2 [=MIME type=] "`mime`" The MIME type used in the NDEF record
[=Absolute-URL record=] 3 URL "`absolute-url`" null
[=External type record=] 4 [=external type name=] [=external type name=] null
Unknown record 5 unused "`unknown`" null

NDEFReader オブジェクト

NDEFReader は、タグなどのデバイスが磁気誘導フィールド内にあるときに browsing context に NFC 機能を公開し、NDEF messages を読み取ります。 また、到達範囲内の NFC tagNDEF messages を書き込むためにも使用されます。

    typedef (DOMString or BufferSource or NDEFMessageInit) NDEFMessageSource;

    [SecureContext, Exposed=Window]
    interface NDEFReader : EventTarget {
      constructor();

      attribute EventHandler onreading;
      attribute EventHandler onreadingerror;

      Promise<undefined> scan(optional NDEFScanOptions options={});
      Promise<undefined> write(NDEFMessageSource message,
                                     optional NDEFWriteOptions options={});
      Promise<undefined> makeReadOnly(optional NDEFMakeReadOnlyOptions options={});
    };

    [SecureContext, Exposed=Window]
    interface NDEFReadingEvent : Event {
      constructor(DOMString type, NDEFReadingEventInit readingEventInitDict);

      readonly attribute DOMString serialNumber;
      [SameObject] readonly attribute NDEFMessage message;
    };

    dictionary NDEFReadingEventInit : EventInit {
      DOMString? serialNumber = "";
      required NDEFMessageInit message;
    };
  

NDEFMessageSource は {{NDEFReader/write()}} メソッドが受け付ける引数型を表すユニオン型です。

NDEFReadingEvent は新しい NFC 読み取り時にディスパッチされるイベントです。 serialNumber プロパティは衝突回避と識別に用いられるデバイスのシリアル番号を表し、 利用できない場合は空文字列です。messageNDEFMessage オブジェクトです。

NDEFReadingEventInit は、シリアル番号と message メンバー経由の NDEFMessageInit データで新規イベントを初期化するために使用されます。 serialNumber が存在しない、または null の場合、空文字列でイベントを初期化します。

ほとんどのタグには安定した一意識別子(UID)がありますが、すべてではなく、読み取りのたびに ランダムな番号を生成するタグもあります。シリアル番号は通常、`:` で区切られた 4 または 7 個の数値で構成されます。

{{NDEFReader}} オブジェクトは次の 内部スロットを持ちます:

Internal Slot Initial value Description (non-normative)
[[\WriteOptions]] null 書き込み用の {{NDEFWriteOptions}} 値。
[[\WriteMessage]] null 書き込まれる {{NDEFMessage}}。 初期状態では未設定です。

onreading は新しい読み取りが利用可能であることを通知するために呼び出される {{EventHandler}} です。

onreadingerror は読み取り中にエラーが発生したことを通知するために呼び出される {{EventHandler}} です。

設定オブジェクトに関連付けられた NFC 状態

NFC をサポートする browsing contextactive documentrelevant settings object には、次の 内部スロットを持つ NFC state レコードが関連付けられています:

Internal Slot Initial value Description (non-normative)
[[\Suspended]] false NFC 機能が suspended かどうかを示すブール値フラグ。初期値は false。
[[\ActivatedReaderList]] empty set {{NDEFReader}} インスタンスの set
[[\PendingWrite]] empty |promise:Promise| と |writer:NDEFReader| のタプル。|promise| は保留中の {{Promise}} を、 |writer| は {{NDEFReader}} を保持します。
[[\PendingMakeReadOnly]] empty |promise:Promise| と |writer:NDEFReader| のタプル。|promise| は保留中の {{Promise}} を、 |writer| は {{NDEFReader}} を保持します。

activated reader objects[[\ActivatedReaderList]] 内部スロットの値です。

pending write tuple[[\PendingWrite]] 内部スロットの値です。

pending makeReadOnly tuple[[\PendingMakeReadOnly]] 内部スロットの値です。

NFC is suspended[[\Suspended]] 内部スロットが true の場合です。

suspend NFC するには、[[\Suspended]] 内部スロットを true に設定します。

resume NFC するには、[[\Suspended]] 内部スロットを false に設定します。

内部スロットは本仕様における記法としてのみ使用され、実装がそれらを明示的な内部プロパティに対応付ける必要はありません。

NFC アダプタの扱い

実装は本仕様で記述されるアルゴリズム手順に従って、複数の NFC adapter を使用してもよい(MAY)ものとします。

権限の取得

Web NFC API は [=default powerful feature=] であり、[=powerful feature/name=] は "nfc" です。

obtain permission するには、次の手順を実行します:
  1. |state:PermissionState| を、" nfc " で [=getting the current permission state=] を実行した結果とします。
  2. |state| が {{PermissionState["granted"]}}(すなわち、[[[PERMISSIONS]]] API を用いて origin および global object に対して権限が付与されている)であれば、true を返します。
  3. それ以外で |state| が {{PermissionState["prompt"]}} の場合、 任意でユーザーに "nfc" の request permission to use を行います。 許可された場合は true を返します。

    request permission の手順はまだ明確に定義されていません。 現時点では、UA は与えられた originglobal object に対する "nfc" の ポリシーについてユーザーに問い合わせ、ユーザーが許可すれば true を返します。

  4. false を返します。

可視状態の変更の扱い

本仕様における [=page visibility change steps=] は、文字列 |visibilityState| と {{Document}} |document| が与えられた場合、次のとおりです:

  1. |visibilityState| が `"visible"` の場合、resume NFC し、これらの手順を中止します。
  2. それ以外の場合、次を実行します:
    1. Suspend NFC
    2. abort a pending write operation を試みます。
    3. abort a pending make read-only operation を試みます。

用語 suspended は、NFC 操作が一時停止された状態を指し、 一時停止中は NDEFReader によっていかなる NFC content も書き込まれず、 受信した NFC content もいかなる {{NDEFReader}} にも提示されません。

保留中の書き込み操作の中止

abort a pending write operation を試みるには、次の手順を実行します:
  1. pending write tuple |tuple| が存在しない場合、これらの手順を中止します。
  2. |tuple| の writer がすでに進行中の NFC データ転送を開始している場合、これらの手順を中止します。
  3. |tuple| の promise を {{"AbortError"}} で reject し、これらの手順を中止します。

    promise を reject すると pending write tuple はクリアされます。

保留中の読み取り専用化操作の中止

abort a pending make read-only operation を試みるには、次の手順を実行します:
  1. pending makeReadOnly tuple |tuple| が存在しない場合、これらの手順を中止します。
  2. |tuple| の writer がすでに NFC タグを恒久的な読み取り専用にしている場合、これらの手順を中止します。
  3. |tuple| の promise を {{"AbortError"}} で reject し、これらの手順を中止します。

    promise を reject すると pending makeReadOnly tuple はクリアされます。

NFC の解放

environment settings object 上で release NFC するには、次の手順を実行します:

  1. Suspend NFC
  2. abort a pending write operation を試みます。
  3. abort a pending make read-only operation を試みます。
  4. activated reader objects をクリアします。
  5. 基盤プラットフォーム上の NFC リソースを解放します。

UA はドキュメントの relevant settings object が与えられた場合、 追加の unloading document cleanup steps として release NFC しなければなりません。

NDEFWriteOptions 辞書

      dictionary NDEFWriteOptions {
        boolean overwrite = true;
        AbortSignal? signal;
      };
    

overwrite プロパティの値が false の場合、write アルゴリズムNFC tag を読み取り、その上に NDEF レコードがあるかどうかを確認し、 ある場合はいかなる保留中の書き込みも実行しません。

signal プロパティにより、{{NDEFReader/write()}} 操作を中止できます。

NDEFMakeReadOnlyOptions 辞書

      dictionary NDEFMakeReadOnlyOptions {
        AbortSignal? signal;
      };
    

signal プロパティにより、{{NDEFReader/makeReadOnly()}} 操作を中止できます。

NDEFScanOptions 辞書

        dictionary NDEFScanOptions {
          AbortSignal signal;
        };
      

signal プロパティにより、{{NDEFReader/scan()}} 操作を中止できます。

Writing content

本セクションは、タイマーが期限切れになる前に、次回近接範囲に入ったときに NFC tagNDEF message を書き込む方法を説明します。 任意の時点で、現在のメッセージが送信されるか書き込みが中止されるまで、 1 つの origin につき設定可能な NDEF message は最大 1 つです。

write() メソッド

NDEFReader.write メソッドは、呼び出されたとき、write a message アルゴリズムを実行しなければなりません:
  1. |p:Promise| を新しい {{Promise}} オブジェクトとします。
  2. 現在アクティブな top-level browsing context で実行していない場合、|p| を {{"InvalidStateError"}} で reject して |p| を返します。
  3. |message:NDEFMessageSource| を第 1 引数とします。
  4. |options:NDEFWriteOptions| を第 2 引数とします。
  5. |signal:AbortSignal| を、存在すれば |options| の同名の辞書メンバー、そうでなければ null とします。
  6. |signal| が [= AbortSignal/aborted =] の場合、|signal| の [=AbortSignal/abort reason=] で |p| を reject し、|p| を返します。
  7. |signal| が null でない場合、|signal| に 次の中止手順を追加します:
    1. environment settings object 上で abort a pending write operation を実行します。
  8. [=promise/React=] to |p|:
    1. |p| が settle(fulfill または reject)された場合、存在すれば pending write tuple をクリアします。
  9. |p| を返し、次の手順をin parallel に実行します:
    1. obtain permission が false を返す場合、|p| を {{"NotAllowedError"}} で reject し、これらの手順を中止します。
    2. 基盤となる NFC Adapter が存在しない、または接続を確立できない場合、 |p| を {{"NotSupportedError"}} で reject し、これらの手順を中止します。
    3. UA が基盤の NFC Adapter にアクセスすることを許可されていない場合(例: ユーザー設定)、 |p| を {{"NotReadableError"}} で reject し、これらの手順を中止します。
    4. 基盤の NFC Adapter がデータのプッシュをサポートしていない場合、 |p| を {{"NotSupportedError"}} で reject し、これらの手順を中止します。
    5. 実装は |p| を {{"NotSupportedError"}} で reject してもよく、 これらの手順を中止してもかまいません(MAY)。

      UA はこの地点でメッセージ書き込みを中止することがあります。 終了の理由は実装詳細です。例えば、要求された操作をサポートできない場合があります。

    6. |message|、`""`、`0` を与えて create NDEF message を呼び出した結果として、 UA により作成される NDEF message の表記を |output| とします。 これが例外を投げた場合、その例外で |p| を reject し、これらの手順を中止します。
    7. abort a pending write operation を試みます。

      書き込みは、それまでに構成された書き込み操作をすべて置き換えます。

    8. `this`.[[\WriteOptions]] を |options| に設定します。
    9. `this`.[[\WriteMessage]] を |output| に設定します。
    10. pending write tuple を (`this`, |p|) に設定します。
    11. NFC tag |device| が通信範囲に入ったときはいつでも、 start the NFC write の手順を実行します。

      NFC is suspended の場合、ユーザーによって promise が中止されるか、 NFC tag が通信範囲に入るまで待機を続けます。

start the NFC write するには、次の手順を実行します:
  1. |p:Promise| を pending write tuple の promise とします。
  2. |writer| を pending write tuple の writer とします。
  3. |options:NDEFWriteOptions| を |writer|.[[\WriteOptions]] とします。
  4. 近接範囲の NFC tag がフォーマットまたは書き込みのための NDEF 技術を公開しない場合、 |p| を {{"NotSupportedError"}} で reject して |p| を返します。
  5. NFC is not suspended であることを検証します。
  6. 成功した場合、次を実行します:
    1. |device| が NFC tag で、|options| の overwrite が false の場合、 タグを読み取り、タグ上に NDEF レコードがあるか確認します。 ある場合、|p| を {{"NotAllowedError"}} で reject して |p| を返します。
    2. |output:NDEFMessage| を |writer|.[[\WriteMessage]] とします。
    3. 通信範囲内の NFC adapter を用いて、|output| をバッファとして |device| へのデータ転送を開始します。

      近接範囲の NFC tag が未フォーマットで NDEF フォーマット可能な場合は、 それをフォーマットし、|output| をバッファとして書き込みます。

      複数のアダプタはユーザーにより順次使用されるべきです。 異なる複数の接続された NFC adapter で同時にタップが起こる可能性は非常に低いです。 それが起きた場合、成功するまで(望ましくは 1 台ずつ)タップを繰り返す必要があるでしょう。 ここでのエラーは操作を繰り返す必要があることを示します。そうしないと、 すべての接続された NFC adapter で操作が成功したとユーザーが誤解する恐れがあります。

    4. 転送が失敗した場合、|p| を {{"NetworkError"}} で reject し、これらの手順を中止します。
    5. 転送が完了したら、|p| を resolve します。

NDEF メッセージの作成

|source:NDEFMessageSource|、|context:string|、|recordsDepth:unsigned short| を与えて create NDEF message するには、次の手順を実行します:
  1. |source:NDEFMessageSource| の型で分岐します:
    {{DOMString}}
    • |textRecord| を、|recordType| を "`text`"、|data| を |source| に設定して初期化した NDEFRecord とします。
    • |records| を « |textRecord| » のリストとします。
    • |source| の records を |records| に設定します。
    {{BufferSource}}
    • |mimeRecord| を、|recordType| を "`mime`"、|data| を |source|、 |mediaType| を "`application/octet-stream`" に設定して初期化した NDEFRecord とします。
    • |records| を « |mimeRecord| » のリストとします。
    • |source| の records を |records| に設定します。
    {{NDEFMessageInit}}
    • |source| の records が [= list/is empty =] の場合、{{TypeError}} を [= exception/throw =] します。
    • |recordsDepth| を 1 増やします。
    • |recordsDepth| > `32` の場合、{{TypeError}} を [= exception/throw =] します。
    unmatched type
    • {{TypeError}} を [= exception/throw =] します。
  2. これらの手順の結果として UA により作成される NDEF message の表記を |output| とします。
  3. list |source| の records 内の [= list/For each =] |record:NDEFRecordInit| について次を実行します:
    1. |record:NDEFRecordInit|、|context|、|recordsDepth| を与えて create NDEF record を実行した結果、または基盤プラットフォームが 等価な値を |ndef| に提供することを確実にします。アルゴリズムが例外 |e| を投げた場合、 |promise| を |e| で reject し、これらの手順を中止します。
    2. |output| に |ndef| を追加します。
  4. |output| と |context| を与えて check created records を実行し、|error: Error| を投げた場合、 |promise| を |error| で reject し、これらの手順を中止します。
  5. |output| を返します。

作成済みレコードの検査

|records: NDEFRecord sequence| および |context: string| を与えて check created records するには、次の手順を実行します:
  1. |context| が `"smart-poster"` で、|records| がちょうど 1 つの URI record を含まない場合、または複数の type recordsize recordaction record を含む場合、 {{TypeError}} を [= exception/throw =] します。
  2. |context| が `"smart-poster"` の場合、URI record を |records| の先頭に移動します。

現時点の Web NFC は、smart poster 内で external typelocal type のレコードを書き込むことを許可しています。 また、empty records も許可されます。 アプリケーションは smart poster 内の余分なレコードを無視してもかまいません。

アイコンレコードのメディアタイプは `"image/"` や `"video/"` に限定することもできますが、 [[NDEF-SMARTPOSTER]] 仕様では実際には他のメディアタイプレコードも smart poster 内で許容され、 例えば vCard の連絡先カードが関連する MIME types のいずれかを使用するなど、アプリケーション固有の方法で扱えます。

NDEF レコードの作成

|record:NDEFRecordInit|、|context:string|、|recordsDepth:unsigned short| を与えて create NDEF record するには、次の手順を実行します:
  1. UA により作成される NDEF record の表現を |ndef| とします。
  2. |record| の id が undefined でない場合:
    • |identifier| を |record| の id とします。
    • |ndef| の IL field を `1` に設定します。
    • |ndef| の ID LENGTH field を |identifier| の長さに設定します。
    • |ndef| の ID field を |identifier| に設定します。
  3. |record| の recordType に応じて切り替え、以下に示すアルゴリズムを |record|、|ndef|、|context|、|recordsDepth| で呼び出し、その結果を返します。 例外 |e| が投げられた場合、|promise| を |e| で reject し、これらの手順を中止します。
    "`empty`"
    "`text`"
    "`url`"
    "`mime`"
    "`smart-poster`"
    "`absolute-url`"
  4. |record| の recordType がコロン `U+003A` (`:`) で始まる場合:
    • |context| が `""`(すなわち |record| が別の NDEF record のペイロードではない) の場合、{{TypeError}} で |promise| を reject し、これらの手順を中止します。
    • |record| の recordType に対して validate local type を実行して false を返す場合、{{TypeError}} で |promise| を reject し、これらの手順を中止します。
    • |record|、|ndef|、|context|、|recordsDepth| を与えて map local type to NDEF を実行した結果を返します。 それが例外 |e| を投げる場合、|promise| を |e| で reject し、これらの手順を中止します。
  5. |record| の recordType に対して validate external type が true を返す場合、 |record|、|ndef|、|context|、|recordsDepth| を与えて map external data to NDEF を返します。 それが例外 |e| を投げる場合、|promise| を |e| で reject し、これらの手順を中止します。
  6. それ以外の場合、{{TypeError}} を [= exception/throw =] し、これらの手順を中止します。

外部タイプの検証

[[NFC-RTD]] は、外部タイプが発行組織の [=domain=] 名、コロン `U+003A` (`:`)、 そして 1 文字以上の型名を必ず含むことを規定しています。例として "`w3.org:member`" のように、すべて ASCII 文字で保存されます。

[[NFC-RTD]] は URN 接頭辞 “`urn:nfc:ext:`” も規定しますが、 これは NDEF record には保存されません。そのため、Web NFC アプリケーションは external type records を作成する際に URN 接頭辞を指定すべきではありません(SHOULD NOT)。

[[NFC-RTD]] は、例えば NDEF messages を読み取る場合など、外部タイプ名は URN 接頭辞 “`urn:nfc:ext:`” で表現することを求めています。 しかし、external type recordsTNF FIELD を `0x04` に設定することで区別されるため、 型名の衝突リスクはないと考えられます。 また、Web で URN の使用を避けるよう求める W3C TAG の勧告もあります。 したがって、Web NFC は NDEF messages の読み取り・書き込みのいずれでも URN 接頭辞を使用しません。

|input:USVString| を与えて split external type するには、次の手順を実行します:

  1. |input| に `U+003A` (`:`) が見つからない場合、failure を返します。
  2. |domain| を、|input| の先頭から最初の `U+003A` (`:`) の直前までの文字列とします。
  3. |type| を、最初の `U+003A` (`:`) の直後(もしあれば)から末尾までの文字列とします。
  4. |domain| と |type| のペアを返します。

|input:USVString| を与えて validate external type するには、次の手順を実行します:

  1. [=split external type=] の結果として |domain| と |type| を得るか、失敗なら false を返します。
  2. |domain| が [=valid domain string=] でない場合、false を返します。
  3. |type| に、[=ASCII alphanumeric=] 以外、または `U+0024` (`$`)、`U+0027` (`'`), `U+0028` `LEFT PARENTHESIS` (`(`)、`U+0029` `RIGHT PARENTHESIS` (`)`)、`U+002A` (`*`)、`U+002B` (`+`)、 `U+002C` (`,`)、`U+002D` (`-`)、`U+002E` (`.`)、`U+003B` (`;`)、`U+003D` (`=`)、 `U+0040` (`@`)、`U+005F` (`_`) のいずれでもない [=code points=] が含まれている場合、false を返します。
  4. true を返します。

ローカルタイプの検証

|input:USVString| を与えて validate local type するには、次の手順を実行します:

  1. |localTypeName| を、|input| の最初の `U+003A` (`:`) の直後から末尾までとします。
  2. |localTypeName| が {{USVString}} でない、または長さが 255 バイトを超える場合、false を返します。
  3. |localTypeName| が小文字または数字で始まっていない場合、false を返します。
  4. |input| が、その包含する NDEF message 内で定義された任意の NDEF recordrecord type と等しい場合、false を返します。
  5. true を返します。

空レコードから NDEF へのマッピング

|record:NDEFRecordInit|、|ndef| を与えて map empty record to NDEF するには、次の手順を実行します:
  1. |record| の mediaType が undefined でない場合、{{TypeError}} を [= exception/throw =] します。
  2. |record| の id が undefined でない場合、{{TypeError}} を [= exception/throw =] します。
  3. |ndef| の TNF field を `0`(empty record)に設定します。
  4. |ndef| の IL field を `0` に設定します。
  5. |ndef| の TYPE LENGTH field および PAYLOAD LENGTH field を `0` に設定し、 TYPE fieldPAYLOAD field を省略します。
  6. |ndef| を返します。

文字列から NDEF へのマッピング

|record:NDEFRecordInit| と |ndef| を与えて map text to NDEF するには、次の手順を実行します:

これは、クライアントが特に [=well-known type record=] としてテキストを書き込みたい場合に有用です。 他の選択肢として、明示的な MIME type のテキスト型を用いる "`mime`" の値を使用することもでき、 例えば "`text/xml`" や "`text/vcard`" を用いることで、より明確な区別が可能です。

  1. |record| の mediaType が undefined でない場合、{{TypeError}} を [= exception/throw =] します。
  2. |record| の data の型が {{DOMString}} または {{BufferSource}} でない場合、 {{TypeError}} を [= exception/throw =] し、これらの手順を中止します。
  3. |documentLanguage:string| を [=document element=] の lang 属性とします。
  4. |documentLanguage| が空文字列である場合、"`en`" に設定します。
  5. |language:string| を、存在する場合は |record| の lang に、そうでなければ |documentLanguage| に設定します。
  6. |record| の data の型で分岐します:
    {{DOMString}}
    1. |record| の encoding が undefined ではなく、かつ "`utf-8`" でもない場合、 {{TypeError}} を [= exception/throw =] し、これらの手順を中止します。
    2. |encoding label:string| を "`utf-8`" に設定します。
    {{BufferSource}}
    1. |encoding label:string| を、存在する場合は |record| の encoding に、 そうでなければ "`utf-8`" に設定します。
    2. |encoding label| が "`utf-8`"、"`utf-16`"、"`utf-16le`"、"`utf-16be`" のいずれでもない場合、 {{TypeError}} を [= exception/throw =] します。
  7. |encoding name| を、|encoding label| から 取得された [=encoding/name|name=] とします。
  8. |header:byte| を次のように構築した byte とします:
    1. |encoding name| が UTF-8 に等しい場合、ビット `7` を `0` に設定し、 そうでなければ `1` に設定します。
    2. ビット `6` を `0`(予約)に設定します。
    3. |languageLength:octet| を |language| string の長さとします。
    4. |languageLength| を 6 ビットに格納できない場合(|languageLength| > 63)、 {{SyntaxError}} を [= exception/throw =] します。
    5. ビット `5` からビット `0` に |languageLength| を設定します。
  9. |data:byte sequence| を空の [= byte sequence =] とします。
    1. |data| の最初の byte(位置 0)を |header| に設定します。
    2. |data| の位置 1(2 番目の byte)から |languageLength| までを |language| に設定します。
    3. |record| の data の型で分岐します:
      {{DOMString}}
      1. |stream:byte stream| を、|record| の dataUTF-8 encode を実行した結果の byte stream とします。
      2. readend-of-stream を返すまで、 |stream| から |data|(位置 |languageLength| + 1 から)へバイトを 読み込みます。
      {{BufferSource}}
      1. |record| の data のバイトを |data| の(位置 |languageLength| + 1 から) 設定します。
  10. |length:unsigned long| を |data| の [=byte sequence/length=] とします。
    1. |ndef| の TNF field を `1`([=well-known type record=])に設定します。
    2. |ndef| の TYPE field を "`T`"(`0x54`)に設定します。
    3. |ndef| の PAYLOAD LENGTH field を |length| に設定します。
    4. |length| > `0` の場合、|ndef| の PAYLOAD field を |data| に設定します。
  11. |ndef| を返します。

URL から NDEF へのマッピング

|record:NDEFRecordInit| と |ndef| を与えて map a URL to NDEF するには、次の手順を実行します:
  1. |record| の mediaType が undefined でない場合、{{TypeError}} を [= exception/throw =] します。
  2. |record| の data が {{DOMString}} でない場合、{{TypeError}} を [= exception/throw =] します。
  3. |url:URL| を、|record| の dataparsing した結果とします。
  4. |url| が failure の場合、{{SyntaxError}} を [= exception/throw =] します。
  5. |serializedURL:string| を |url| の serialization とします。
  6. [[[NFC-STANDARDS]]] の URI Record Type Definition 仕様 セクション 3.2.2 に定義された URI プレフィックスを |serializedURL| に対して照合します。
  7. |prefixString:string| を、一致したプレフィックス、または emptystring とします。
  8. |prefixByte:byte| を、対応するプレフィックス番号、または `0` とします。
  9. |shortenedURL:string| を、|serializedURL| の先頭から |prefixString| を取り除いた string とします。
  10. |data:byte sequence| を空の [= byte sequence =] とします。
    1. |data| の最初の byte を |prefixByte| に設定します。
    2. |stream:byte stream| を、|shortenedURL| に UTF-8 encode を実行した結果の byte stream とします。
    3. readend-of-stream を返すまで、 |stream| から |data|(位置 1 から)へバイトを 読み込みます。
  11. |length:unsigned long| を |data| の [=byte sequence/length=] とします。
  12. |ndef| の TNF field を `1`([=well-known type record=])に設定します。
  13. |ndef| の TYPE field を "`U`"(`0x55`)に設定します。
  14. |ndef| の PAYLOAD LENGTH field を |length| に設定します。
  15. |length| > `0` の場合、|ndef| の PAYLOAD field を |data| に設定します。
  16. |ndef| を返します。

バイナリデータから NDEF へのマッピング

|record:NDEFRecordInit| と |ndef| を与えて map binary data to NDEF するには、 次の手順を実行します:
  1. |record| の data の型が {{BufferSource}} でない場合、{{TypeError}} を [= exception/throw =] します。
  2. |mimeType| を、|record| の mediaType に対して parse a MIME type を実行して得られる MIME type とします。
  3. |mimeType| が failure の場合、|mimeTypeRecord| を、type が "`application`"、 subtype が "`octet-stream`" の新しい MIME type record とします。
  4. |arrayBuffer| を |record| の data に設定します。
  5. |length:unsigned long| を |arrayBuffer|.[[\ArrayBufferByteLength]] に設定します。
  6. |data:byte sequence| を |arrayBuffer|.[[\ArrayBufferData]] に設定します。
  7. |ndef| の TNF field を `2`(MIME type)に設定します。
  8. |ndef| の TYPE field を、|mimeType| を入力として serialize a MIME type を実行した結果に設定します。
  9. |ndef| の PAYLOAD LENGTH field を |length| に設定します。
  10. |length| > `0` の場合、|ndef| の PAYLOAD field を |data| に設定します。
  11. |ndef| を返します。

外部データから NDEF へのマッピング

|record:NDEFRecordInit|、|ndef|、|recordsDepth:unsigned short| を与えて map external data to NDEF するには、次の手順を実行します:
  1. |record| の mediaType が undefined でない場合、{{TypeError}} を [= exception/throw =] します。
  2. |domain| と |type| を、|record| の recordType に対して [=split external type=] を実行した結果とします。
  3. |domain| を、|domain| と true を与えて domain to ASCII を実行した結果とします。
  4. |customTypeName| を |domain|、"`:`"、|type| を連結したものとします。
  5. |customTypeName| が {{USVString}} でない、または長さが 255 バイトを超える場合、 {{TypeError}} を [= exception/throw =] します。
  6. |ndef| の TYPE field を |customTypeName| に設定します。
  7. |record| の data の型が {{BufferSource}} または {{NDEFMessageInit}} でない場合、 {{TypeError}} を [= exception/throw =] します。
  8. |ndef| の TNF field を `4`(external type record)に設定します。
  9. |record| の data の型が {{BufferSource}} の場合、
    1. |arrayBuffer| を |record| の data に設定します。
    2. |length:unsigned long| を |arrayBuffer|.[[\ArrayBufferByteLength]] に設定します。
    3. |data:byte sequence| を |arrayBuffer|.[[\ArrayBufferData]] に設定します。
    4. |ndef| の PAYLOAD LENGTH field を |length| に設定します。
    5. |length| > `0` の場合、|ndef| の PAYLOAD field を |data| に設定します。
  10. |record| の data の型が {{NDEFMessageInit}} の場合、
    1. |ndef| の PAYLOAD field を、 |record| の data、`"external"`、|recordsDepth| を与えた create NDEF message を実行した結果に設定します。
    2. |ndef| の PAYLOAD LENGTH field を、 |ndef| の PAYLOAD field の長さに設定します。
  11. |ndef| を返します。

ローカルタイプから NDEF へのマッピング

|record:NDEFRecordInit|、|ndef|、|context:string|、|recordsDepth:unsigned short| を与えて map local type to NDEF するには、次の手順を実行します:
  1. |record| の mediaType が undefined でない場合、{{TypeError}} を [= exception/throw =] します。
  2. |record| の data の型が {{BufferSource}} または {{NDEFMessageInit}} でない場合、 {{TypeError}} を [= exception/throw =] します。
  3. |ndef| の TNF field を `1`([=well-known type record=])に設定します。
  4. |localTypeName| を、|record| の recordType の最初の `U+003A` (`:`) の直後から末尾までとします。
  5. |ndef| の TYPE field を |localTypeName| に設定し、 local type name を表します。
  6. |context| が `"smart-poster"`、|localTypeName| が "`s`"(`0x73`) で、|record| の data の型が {{BufferSource}} でない、またはバイト長が 4 を超える場合、{{TypeError}} を [= exception/throw =] します。
  7. |context| が `"smart-poster"`、|localTypeName| が "`act`"(`0x61` `0x63` `0x74`) で、|record| の data の型が {{BufferSource}} でない、またはバイト長がちょうど 1 ではない場合、 {{TypeError}} を [= exception/throw =] し、これらの手順を中止します。
  8. |record| の data の型が {{BufferSource}} の場合、
    1. |arrayBuffer| を |record| の data に設定します。
    2. |length:unsigned long| を |arrayBuffer|.[[\ArrayBufferByteLength]] に設定します。
    3. |data:byte sequence| を |arrayBuffer|.[[\ArrayBufferData]] に設定します。
    4. |ndef| の PAYLOAD LENGTH field を |length| に設定します。
    5. |length| > `0` の場合、|ndef| の PAYLOAD field を |data| に設定します。
  9. |record| の data の型が {{NDEFMessageInit}} の場合、
    1. |ndef| の PAYLOAD field を、 |record| の data、`"local"`、|recordsDepth| を与えた create NDEF message を実行した結果に設定します。
    2. |ndef| の PAYLOAD LENGTH field を、 |ndef| の PAYLOAD field の長さに設定します。
  10. |ndef| を返します。

スマートポスターから NDEF へのマッピング

|record:NDEFRecordInit|、|ndef|、|recordsDepth:unsigned short| を与えて map smart poster to NDEF するには、次の手順を実行します:
  1. |record| の mediaType が undefined でない場合、{{TypeError}} を [= exception/throw =] します。
  2. |record| の data の型が {{NDEFMessageInit}} でない場合、 {{TypeError}} を [= exception/throw =] します。
  3. |ndef| の TNF field を `1`([=well-known type record=])に設定します。
  4. |ndef| の TYPE field を "`Sp`"(`0x53` `0x70`)に設定します。
  5. |ndef| の PAYLOAD field を、|record| の data、`"smart-poster"`、 |recordsDepth| を与えた create NDEF message を実行した結果に設定します。
  6. |ndef| の PAYLOAD LENGTH field を、|ndef| の PAYLOAD field の長さに設定します。
  7. |ndef| を返します。

absolute-URL から NDEF へのマッピング

|record:NDEFRecordInit|、|ndef|、|context:string| を与えて map absolute-URL to NDEF するには、次の手順を実行します:
  1. |context| が `"smart-poster"` の場合、{{TypeError}} を [= exception/throw =] します。

    [[NDEF-SMARTPOSTER]] 仕様では smart poster 内の URL は 1 つのみ許可され、 それは単一の URI record でなければなりません。

  2. |record| の mediaType が undefined でない場合、{{TypeError}} を [= exception/throw =] します。
  3. |record| の data が {{DOMString}} でない場合、{{TypeError}} を [= exception/throw =] します。
  4. |record| の dataparsing した結果が failure の場合、 {{SyntaxError}} を [= exception/throw =] します。
  5. |arrayBuffer| を |record| の data に設定します。
  6. |data:byte sequence| を |arrayBuffer|.[[\ArrayBufferData]] に設定します。
  7. |ndef| の TNF field を `3`([=absolute-URL record=])に設定します。
  8. |ndef| の TYPE field を |data| に設定します。
  9. |ndef| の PAYLOAD LENGTH field を `0` に設定し、PAYLOAD field を省略します。
  10. |ndef| を返します。

Making content read-only

本セクションは、近接範囲内にあるときに NFC tag を恒久的に読み取り専用にする方法を説明します。 任意の時点で、NFC tag が恒久的に読み取り専用にされるか操作が中止されるまで、 1 つの origin につきリクエストは最大 1 件です。

makeReadOnly() メソッド

NDEFReader.makeReadOnly メソッドは、呼び出されたとき、 make an NFC tag permanently read-only アルゴリズムを実行しなければなりません:
  1. |p:Promise| を新しい {{Promise}} オブジェクトとします。
  2. 現在アクティブな top-level browsing context で実行していない場合、|p| を {{"InvalidStateError"}} で reject して |p| を返します。
  3. |options:NDEFMakeReadOnlyOptions| を第 2 引数とします。
  4. |signal:AbortSignal| を、存在すれば |options| の同名の辞書メンバー、そうでなければ null とします。
  5. |signal| が [= AbortSignal/aborted =] の場合、|signal| の [=AbortSignal/abort reason=] で |p| を reject し、|p| を返します。
  6. |signal| が null でない場合、|signal| に 次の中止手順を追加します:
    1. environment settings object 上で abort a pending make read-only operation を実行します。
  7. [=promise/React=] to |p|:
    1. |p| が settle(fulfill または reject)された場合、存在すれば pending write tuple をクリアします。
  8. |p| を返し、次の手順をin parallel に実行します:
    1. obtain permission が false を返す場合、|p| を {{"NotAllowedError"}} で reject し、これらの手順を中止します。
    2. 基盤となる NFC Adapter が存在しない、または接続を確立できない場合、 |p| を {{"NotSupportedError"}} で reject し、これらの手順を中止します。
    3. UA が基盤の NFC Adapter にアクセスすることを許可されていない場合(例: ユーザー設定)、 |p| を {{"NotReadableError"}} で reject し、これらの手順を中止します。
    4. 実装は |p| を {{"NotSupportedError"}} で reject してもよく、これらの手順を中止してもかまいません(MAY)。

      UA はこの地点で中止することがあります。終了の理由は実装詳細です。 例えば、実装が要求された操作をサポートできない場合があります。

    5. abort a pending make read-only operation を試みます。

      読み取り専用化操作は、それまでに構成された読み取り専用化操作をすべて置き換えます。

    6. pending makeReadOnly tuple を (`this`, |p|) に設定します。
    7. NFC tag |device| が通信範囲に入ったときはいつでも、 start the NFC make read-only の手順を実行します。

      NFC is suspended の場合、ユーザーによって promise が中止されるか、 NFC tag が通信範囲に入るまで待機を続けます。

start the NFC make read-only するには、次の手順を実行します:
  1. |p:Promise| を pending makeReadOnly tuple の promise とします。
  2. 近接範囲の NFC tag がフォーマットのための NDEF 技術を公開しない場合、 |p| を {{"NotSupportedError"}} で reject して |p| を返します。
  3. NFC is not suspended であることを検証します。
  4. 成功した場合、次を実行します:
    1. 通信範囲内の NFC adapter を用いて、|device| を恒久的に読み取り専用にします。
    2. 操作が失敗した場合、|p| を {{"NetworkError"}} で reject し、これらの手順を中止します。
    3. 操作が完了したら、|p| を resolve します。
    4. この操作は一方向であり、元に戻すことはできません。 一度 NFC タグを読み取り専用にすると、以後書き込みはできません。

コンテンツのリッスン

NFC content をリッスンするには、クライアントは NDEFReader.scan() を呼び出して {{NDEFReader}} インスタンスをアクティブ化しなければなりません(MUST)。 その上で "`reading`" イベントのイベントリスナをアタッチすると、NFC content にアクセスできます。

activated reader objects に任意の {{NDEFReader}} インスタンスが存在する場合、 UA は接続されたすべての NFC アダプタで NDEF message をリッスンしなければなりません(MUST)。

scan() メソッド

受信する NFC content は {{NDEFReader}} インスタンスで照合されます。

NDEFReader.scan メソッドが呼び出されたとき、UA は次の NFC listen algorithm を実行しなければなりません(MUST):
  1. |p:Promise| を新しい {{Promise}} オブジェクトとします。
  2. 現在アクティブな top-level browsing context で実行していない場合、|p| を {{"InvalidStateError"}} で reject して |p| を返します。
  3. |reader:NDEFReader| を {{NDEFReader}} インスタンスとします。
  4. |options| を第 1 引数とします。
  5. |signal:AbortSignal| を、存在すれば |options| の同名の辞書メンバー、そうでなければ null とします。
  6. |signal| が [= AbortSignal/aborted =] の場合、|signal| の [=AbortSignal/abort reason=] で |p| を reject し、|p| を返します。
  7. |signal| が null でない場合、|signal| に 次の clean up the pending scan 手順を追加します:
    1. |reader| を activated reader objects から削除します。
    2. activated reader objects が [= list/is empty =] の場合、 すべての NFC adapter 上で NDEF message のリッスンを停止する要求を行います。
  8. |p| を返し、次の手順をin parallel に実行します:
    1. obtain permission が false を返す場合、|p| を {{"NotAllowedError"}} で reject し、これらの手順を中止します。
    2. 基盤となる NFC Adapter が存在しない、または接続を確立できない場合、 |p| を {{"NotSupportedError"}} で reject し、これらの手順を中止します。
    3. UA が基盤の NFC Adapter にアクセスすることを許可されていない場合(例: ユーザー設定)、 |p| を {{"NotReadableError"}} で reject し、これらの手順を中止します。
    4. |reader| がすでに activated reader objects に存在する場合、 |p| を {{"InvalidStateError"}} で reject し、これらの手順を中止します。
    5. |reader| を activated reader objects に追加します。
    6. |p| を resolve します。
    7. UA が NFC 技術を検出するたびに、NFC reading algorithm を実行します。

NFC 読み取りアルゴリズム

NDEF コンテンツを受信するには、NFC reading algorithm を実行します:
  1. NFC is suspended の場合、これらの手順を中止します。
  2. 近接範囲の NFC tag が読み取りまたはフォーマットのための NDEF 技術を公開しない場合、 次のサブ手順を実行します:
    1. [= list/For each =] activated reader objects 内の {{NDEFReader}} インスタンス |reader:NDEFReader| について、次のサブ手順を実行します:
      1. |reader| に対して "`readingerror`" という名前の Fire an event を実行します。
    2. これらの手順を中止します。
  3. |serialNumber:serialNumber| を、デバイス識別子(数値の並び)または、利用不可の場合は null とします。
  4. |serialNumber| が null でない場合、同じ順序で各数値を ASCII hex digit で表現し、U+003A(`:`)で連結した string に設定します。
  5. |message:NDEFMessage| を新しい NDEFMessage オブジェクトとし、|message| の records を空の list に設定します。
  6. 近接範囲の NFC tag が未フォーマットで NDEF フォーマット可能な場合、|input| を null とします。 それ以外の場合、受信した NDEF message の表記を |input| とします。

    UA は未フォーマットの NFC tag を、NDEF record を含まない NDEF message として(すなわち {{NDEFMessage/records}} プロパティが空配列)表現するべきです(SHOULD)。

  7. |input| の一部である [= list/For each =] NDEF record について、次のサブ手順を実行します:
    1. |ndef| を現在の NDEF record の表記とし、 |typeNameField:number| を TNF field に対応する値、 |payload:byte sequence| を PAYLOAD field データに対応する値とします。
    2. |record:NDEFRecord| を、|ndef| と `""` を与えて parse an NDEF record を実行した結果とします。
    3. |record| が null でない場合、|message| の records に |record| をappend します。
  8. 与えられた |serialNumber| と |message| で dispatch NFC content の手順を実行します。

NFC コンテンツのディスパッチ

serialNumber の |serialNumber:serialNumber| と、型 NDEFMessage の |message:NDEFMessage| を与えて dispatch NFC content するには、次の手順を実行します:

  1. [= list/For each =] activated reader objects 内の {{NDEFReader}} インスタンス |reader:NDEFReader| について、
    1. |reader| に対して、NDEFReadingEvent を用い、 その serialNumber 属性を |serialNumber| に、 その message 属性を |message| に初期化して、 "`reading`" という名前の fire an event を実行します。

コンテンツのパース

バイト列からのレコードのパース

|bytes:byte sequence| および |context: string| を与えて parse records from bytes するには、次の手順を実行します:
  1. |bytes| の長さが `0` の場合、null を返します。
  2. |records| を空のリストとします。
  3. |bytes| に未読のバイトがある限り、次のサブ手順を実行します:
    1. 残りの |bytes| の長さが `3` 未満の場合、null を返します。
    2. 以降のいずれかのステップで残りの |bytes| の長さを超えて読み取る必要がある場合、null を返します。
    3. |ndef| を現在の NDEF record の表記とします。
    4. |header:byte| を |bytes| の次のバイトとします。
      1. |messageBegin:boolean|(MB field)を |header| の最上位ビット(ビット 7)とします。
      2. これがこれらのサブ手順の最初の反復で、かつ |messageBegin| が false の場合、null を返します。
      3. |messageEnd:boolean|(ME field)を |header| のビット 6 とします。
      4. チャンク化されたレコードはサブレコードとして許可されないため、ビット 5(CF field)は無視します。

      5. |shortRecord:boolean|(SR field)を |header| のビット 4 とします。
      6. |hasIdLength:boolean|(IL field)を |header| のビット 3 とします。
      7. |ndef| の |typeNameField:number|(TNF field)を |header| のビット 2〜0 の整数値とします。
    5. |typeLength:number| を |bytes| の次のバイト(TYPE LENGTH field)の整数値とします。
    6. |shortRecord| が true の場合、|payloadLength:number| を |bytes| の次のバイト (PAYLOAD LENGTH field)の整数値とします。
    7. それ以外の場合、|payloadLength| を |bytes| の次の 4 バイトの整数値とします。
    8. |hasIdLength| が true の場合、|idLength:number| を |bytes| の次のバイト (ID LENGTH field)の整数値とし、そうでなければ `0` とします。
    9. |typeLength| > 0 の場合、|ndef| の |type:string| を、次の |typeLength| バイト (TYPE field)に対して UTF-8 decode を実行した結果とし、 そうでなければ |type| を空文字列とします。
    10. |idLength| > 0 の場合、|ndef| の |id:string| を、次の |idLength| バイト (ID field)に対して UTF-8 decode を実行した結果とし、 そうでなければ |ndef| の |id| を空文字列とします。
    11. |ndef| の |payload| を、最後の |payloadLength| バイト (PAYLOAD field)の byte sequence とし、`0` バイトであってもよいものとします。
    12. |record:NDEFRecord| を、|ndef| と |context| を与えて parse an NDEF record を実行した結果とします。
    13. |record| が null でない場合、|records| に |record| をappend します。
    14. |messageEnd| が true の場合、
      1. |records| と |context| を与えて check parsed records を実行し、 |error| を投げた場合、|promise| を |error| で reject し、これらの手順を中止します。
      2. それ以外の場合、これらのサブ手順を終了します(ループを終了)。
  4. |records| を返します。

パース済みレコードの検査

|records: NDEFRecord sequence| および |context: string| を与えて check parsed records するには、次の手順を実行します:
  1. |context| が `"smart-poster"` で、|records| がちょうど 1 つの URI record を含まない場合、 または複数の type recordsize recordaction record を含む場合、 {{TypeError}} を [= exception/throw =] します。
  2. それ以外の場合は true を返します。

NDEF レコードのパース

|ndef| および |context:string| を与えて parse an NDEF record を |record:NDEFRecord| に行うには、次の手順を実行します:
  1. |record| の id を |ndef| の |id:string| に設定します。
  2. |record| の lang を null に設定します。
  3. |record| の encoding を null に設定します。
  4. |ndef| の |typeNameField:number|(TNF field)が `0`(empty record)の場合:
    1. |record| の id を null に設定します。
    2. |record| の recordType を "`empty`" に設定します。
    3. |record| の mediaType を null に設定します。
    4. |record| の data を null に設定します。
  5. |ndef| の |typeNameField| が `1`([=well-known type record=])の場合:
    1. |ndef| の |type:string| が "`T`"(`0x54`) の場合、 |record| を |ndef| に対して parse an NDEF text record を実行した結果に設定します。
    2. |ndef| の |type:string| が "`U`"(`0x55`) の場合、 |record| を |ndef| に対して parse an NDEF URL record を実行した結果に設定します。
    3. |ndef| の |type:string| が "`Sp`"(`0x53` `0x70`) の場合、 |record| を |ndef| に対して parse an NDEF smart-poster record を実行した結果に設定します。
    4. |ndef| の |type:string| が "`s`"(`0x73`) で、 |context| が `"smart-poster"` と等しい場合、 |record| を |ndef| に対して parse a smart-poster size record を実行した結果に設定します。
    5. |ndef| の |type:string| が "`t`"(`0x74`) で、 |context| が `"smart-poster"` と等しい場合、 |record| を |ndef| に対して parse a smart-poster type record を実行した結果に設定します。
    6. |ndef| の |type:string| が "`act`"(`0x61` `0x63` `0x74`) で、|context| が `"smart-poster"` と等しい場合、 |record| を |ndef| に対して parse a smart-poster action record を実行した結果に設定します。
    7. |ndef| の |type:string| に対して validate local type を実行して true を返す場合、
      1. |context| が `"external"` または `"smart-poster"` でない場合、 {{TypeError}} を [= exception/throw =] します。
      2. |record| を |ndef| に対して parse a local type record を実行した結果に設定します。
    8. それ以外の場合、{{TypeError}} を [= exception/throw =] します。
  6. |ndef| の |typeNameField| が `2`(MIME type record)の場合、 |record| を |ndef| に対して parse an NDEF MIME type record を実行した結果に設定するか、 基盤プラットフォームが |record| オブジェクトのプロパティに等価な値を提供することを確実にします。
  7. |ndef| の |typeNameField| が `3`(absolute-URL record)の場合、 |record| を |ndef| に対して parse an NDEF absolute-URL record を実行した結果に設定します。
  8. |ndef| の |typeNameField| が `4`(external type record)の場合、 |record| を |ndef| に対して parse an NDEF external type record を実行した結果に設定するか、 基盤プラットフォームが |record| オブジェクトのプロパティに等価な値を提供することを確実にします。
  9. |ndef| の |typeNameField| が `5`(unknown record)の場合、 |record| を |ndef| に対して parse an NDEF unknown record を実行した結果に設定するか、 基盤プラットフォームが |record| オブジェクトのプロパティに等価な値を提供することを確実にします。
  10. それ以外の場合、{{TypeError}} を [= exception/throw =] します。

NDEF well-known `T` レコードのパース

|ndefRecord| を与えて parse an NDEF text record を |record:NDEFRecord| に行うには、 次の手順を実行します:
  1. |record| の recordType を "`text`" に設定します。
  2. |record| の mediaType を null に設定します。
  3. |ndefRecord| の PAYLOAD field が存在しない場合、 |record| の data を null に設定して |record| を返します。
  4. |header:byte| を |ndefRecord| の PAYLOAD field の最初の byte とします。
  5. |languageLength:octet| を、|header| のビット `5` からビット `0` の値とします。
  6. |language:string| を、2 バイト目から |languageLength| + `1` バイト目までを ASCII decode した結果とします。
  7. |buffer:byte sequence| を、|ndefRecords| の PAYLOAD field の残り (|header| と |languageLength| バイト分を除く)の byte sequence とします。
  8. |record| の lang を |language| に設定します。
  9. |record| の encoding を、|header| のビット `7` が `0` の場合は "`utf-8`"、 それ以外は "`utf-16be`" に設定します。
  10. |record| の data を |buffer| に設定します。
  11. |record| を返します。

NDEF well-known `U` レコードのパース

|ndefRecord| を与えて parse an NDEF URL record を |record:NDEFRecord| に行うには、 次の手順を実行します:
  1. |record| の recordType を "`url`" に設定します。
  2. |record| の mediaType を null に設定します。
  3. |ndefRecord| の PAYLOAD field が存在しない場合、 |record| の data を null に設定して |record| を返します。
  4. |buffer:byte sequence| を、|ndefRecords| の PAYLOAD fieldbyte sequence とします。
  5. |prefixByte:byte| を、|buffer| の最初の byte の値とします。
  6. |prefixByte| の値が [[[NFC-STANDARDS]]] の URI Record Type Definition 仕様 セクション 3.2.2 表 3 の URL 展開コードに一致する場合、
    1. |prefixString:string| を、|prefixByte| の値に対応する byte sequence の値とします。
    2. |record| の data を、|prefixString| に |buffer| を連結したものに設定します。
  7. それ以外で |prefixByte| に一致がない場合、|record| の data を |buffer| に設定します。
  8. |record| を返します。

NDEF well-known `Sp` レコードのパース

|ndefRecord| を与えて parse an NDEF smart-poster record を |record:NDEFRecord| に行うには、 次の手順を実行します:
  1. |record| の recordType を "`smart-poster`" に設定します。
  2. |record| の mediaType を null に設定します。
  3. |ndefRecord| の PAYLOAD field が存在しない場合、 |record| の data を null に設定して |record| を返します。
  4. |buffer:byte sequence| を、|ndefRecords| の PAYLOAD fieldbyte sequence とします。
  5. |record| の data を |buffer| に設定します。
  6. |record| を返します。

    アプリケーションは data に対して toRecords() を呼び出して NDEF records にパースするか、自身でパースしてもかまいません。

|ndefRecord| を与えて parse a smart-poster size record を |record:NDEFRecord| に行うには、 次の手順を実行します:
  1. |record| の recordType を "`:s`" に設定します。
  2. |record| の mediaType を null に設定します。
  3. |ndefRecord| の PAYLOAD field がちょうど 4 バイトでない場合、 {{TypeError}} を [= exception/throw =] します。
  4. |buffer:byte sequence| を、|ndefRecords| の PAYLOAD fieldbyte sequence とします。
  5. |record| の data を |buffer| に設定します。

    アプリケーションはこの値を、smart-poster 内の URI レコードが参照する オブジェクトのサイズを示す 32 ビット符号なし整数としてパースできます。

  6. |record| を返します。
|ndefRecord| を与えて parse a smart-poster type record を |record:NDEFRecord| に行うには、 次の手順を実行します:
  1. |record| の recordType を "`:t`" に設定します。
  2. |record| の mediaType を null に設定します。
  3. |buffer:byte sequence| を、|ndefRecords| の PAYLOAD fieldbyte sequence とします。

    アプリケーションはこの値を、smart-poster 内の URI レコードが参照する オブジェクトのメディアタイプを示す [[RFC2048]] メディアタイプを含む文字列としてパースできます。

  4. |record| の data を |buffer| に設定します。
  5. |record| を返します。
|ndefRecord| を与えて parse a smart-poster action record を |record:NDEFRecord| に行うには、次の手順を実行します:
  1. |record| の recordType を "`:act`" に設定します。
  2. |record| の mediaType を null に設定します。
  3. |ndefRecord| の PAYLOAD field がちょうど 1 バイトでない場合、 {{TypeError}} を [= exception/throw =] します。
  4. |buffer:byte sequence| を、|ndefRecords| の PAYLOAD fieldbyte sequence とします。
  5. |record| の data を |buffer| に設定します。

    アプリケーションはこの値を 8 ビット符号なし整数としてパースでき、その値は ここで定義されています。

  6. |record| を返します。

ローカルタイプレコードのパース

|ndef| を与えて parse a local type record を |record:NDEFRecord| に行うには、 次の手順を実行します:
  1. |record| の recordType を "`:`"(`U+003A`)に |ndef| の |type:string| を連結したものに設定します。
  2. |record| の mediaType を null に設定します。
  3. |buffer:byte sequence| を、|ndefRecords| の PAYLOAD fieldbyte sequence とします。
  4. |record| の data を |buffer| に設定します。
  5. |record| を返します。

NDEF MIME タイプレコードのパース

|ndefRecord| を与えて parse an NDEF MIME type record を |record:NDEFRecord| に行うには、 次の手順を実行します:
  1. |record| の recordType を "`mime`" に設定します。
  2. |record| の mediaType を、|mimeType| を入力として serialize a MIME type を実行した結果に設定します。
  3. |buffer:byte sequence| を、存在する場合は |ndefRecords| の PAYLOAD fieldbyte sequence、そうでなければ null とします。
  4. |record| の data を |buffer| に設定します。
  5. |record| を返します。

NDEF absolute-URL レコードのパース

|ndefRecord| を与えて parse an NDEF absolute-URL record を |record:NDEFRecord| に行うには、次の手順を実行します:
  1. |record| の recordType を "`absolute-url`" に設定します。
  2. |record| の mediaType を null に設定します。
  3. |buffer:byte sequence| を、|ndefRecords| の TYPE fieldbyte sequence とします。
  4. |record| の data を |buffer| に設定します。
  5. |record| を返します。

NDEF 外部タイプレコードのパース

|ndefRecord| を与えて parse an NDEF external type record を |record:NDEFRecord| に行うには、次の手順を実行します:
  1. record の |ndefRecord| の TYPE field に対して [=validate external type=] を実行して false を返す場合、null を返します。
  2. |domain| と |type| を、|ndefRecord| の TYPE field の値に対して [=split external type=] を実行した結果とします。
  3. |domain| を、 Unicode ToUnicode を |domain_name| に |domain|、|CheckHyphens| に false、|CheckBidi| に true、 |CheckJoiners| に true、|UseSTD3ASCIIRules| に true、|Transitional_Processing| に false を それぞれ設定して実行した結果とします。結果にエラーが含まれる場合、null を返します。
  4. |record| の recordType を、|domain|、"`:`"、|type| を連結したものに設定します。
  5. |record| の mediaType を null に設定します。
  6. |buffer:byte sequence| を、存在する場合は |ndefRecords| の PAYLOAD fieldbyte sequence、そうでなければ null とします。
  7. |record| の data を |buffer| に設定します。
  8. |record| を返します。

NDEF 不明タイプレコードのパース

|ndefRecord| を与えて parse an NDEF unknown record を |record:NDEFRecord| に行うには、 次の手順を実行します:
  1. |record| の recordType を "`unknown`" に設定します。
  2. |record| の mediaType を null に設定します。
  3. |buffer:byte sequence| を、存在する場合は |ndefRecords| の PAYLOAD fieldbyte sequence、そうでなければ null とします。
  4. |record| の data を |buffer| に設定します。
  5. |record| を返します。

ブロックリスト

本仕様は、ウェブサイトがアクセスできる NFC デバイスの集合を制限するためにブロックリストファイルに依存します。

|url:URL| における parsing the blocklist の結果は、次のアルゴリズムによって生成される historical bytes の16進値のリストです:

  1. |url:URL| を取得し、その本文を UTF-8 としてデコードしたものを |contents:string| とします。
  2. |lines:array| を |contents| を `"\n"` で分割したものとします。
  3. |result:list| を空の list とします。
  4. |lines| 内の |line:string| について [= list/For each =]、次の副手順を実行します:
    1. |line| が空であれば、次の行に進みます。
    2. |line| が `"#"` で始まる場合、次の行に進みます。
    3. |line| に無効な16進値が含まれる場合、次の行に進みます。
    4. Append |line| を |result| に追加します。
  5. |result| を返します。

blocklist は、https://github.com/w3c/web-nfc/blob/gh-pages/blocklist.txt における parsing the blocklist の結果です。UA はブロックリストを定期的に再取得すべきですが、その頻度は規定されていません。

NFC device は、blocklist の値にそのデバイスの historical bytes の16進値が含まれている場合、 blocklisted とされます。ISO 14443-4 の用語では、historical bytes は RATS(Request for Answer To Select) 応答の一部集合です。

セキュリティとプライバシー

信頼の連鎖

実装は、ユーザーが Web NFC API の一部であるメソッドを許可したときに、副作用なくそのアクションのみが実行されることを保証する必要があります。

既定では、NDEF は、データを書き込んだ後にタグを恒久的な読み取り専用にできることを除いて、コンテンツを信頼できるようにする手段を提供しません。 これは工場出荷時設定からでも可能です。

この API によって書き込まれるデータは自動的に署名や暗号化はされず、これは既存のネイティブ NFC API に従います。 NDEF メッセージの完全性と真正性を保護するために、NFC Forum は [[NDEF-SIGNATURE]] を導入しました。 NDEF signature と鍵管理の使用はアプリケーションの責任です。

NFC 経由で交換されるデータの機密性を信頼するため、アプリケーションは暗号化された NFC content を使用しても構いません。

NFC 経由で交換されるデータの完全性を信頼するため、アプリケーションは PKI(公開鍵基盤)に基づく鍵管理とともに NDEF signature を使用しても構いません。

一般的な MIME タイプに関するセキュリティ上の考慮事項は [[RFC2048]] および [[RFC2046]] で議論されています。

プライバシーへの影響と実装上の考慮事項

NFC タグは、バーコードや QR コードと同様に、人間が読めない方法でデータを交換する別の手段であり、それらを共有することは、 予期しないプライバシーおよびセキュリティ上の影響をもたらす可能性があります。ウェブサイトが QR コードを読み取るには、 画像を取得して、その画像の内容(QR コードを含む)がウェブページで利用可能になることを明確に示す中断的な UI(カメラ)を 使用する必要があり、ユーザーにスキャンが行われていることを明確にします。

NFC でタグをスキャンするには、ユーザーがスキャンデバイス(例: 携帯電話)を NFC タグに近接(通常 5〜10cm、2〜4 インチ) させる必要があります。

Web NFC のスキャンがアクティブでないときにタグをスキャンすると、ホスト OS の処理がトリガーされます。 したがって、NFC タグのスキャンから URL やアプリを起動することは Web NFC 自身では処理もサポートもされません。

さらに、Web NFC のスキャンはユーザー操作から有効化される必要があり、サイトがフォーカスされていない場合や デバイスの画面が消灯(すなわちロック解除されていない)したときにはスキャンは一時停止されます。 これは、偶発的なスキャンが起こりにくいようにするためです。

Web NFC はさらに、スキャンデバイスを NFC タグに近づけたときにデータがスキャンされることを UX 上ユーザーに非常に明確に示すことを 実装に推奨します。基本的には QR コードのスキャンの UX フローを模倣します。

そのための方法は多数あります。例えば音を鳴らす、スキャン中に永続的な UI を表示する(任意の時点でキャンセルできるモーダルダイアログなど)。

実装は、アップロードしようとしているデータを表示し、ユーザーが承認するまで共有を延期し、どのレコードを共有するかをユーザーが選択できる UI を表示することもできます。

スキャン中の読み取りと書き込み

ユーザーがタグをスキャンすると、その時点でウェブアプリケーションはタグ上のデータを読み取るアクセス権を持ち、 読み取り専用でない場合はタグにデータを書き込むこともできます。私的利用向けの市販ステッカー(例: メイカーコミュニティ)では しばしばロックされておらず(読み取り+書き込み)、商用展開の NFC は読み取り専用であるのが一般的です。

以前のプロトコル SNEP(Simple NDEF Exchange Protocol)は、能動デバイス(例: 携帯電話)が別の能動デバイスから NDEF データを受信することを可能にしていましたが、Web NFC ではサポートされておらず、サポートされているネイティブプラットフォームでも 現在廃止されつつあります。

より新しいプロトコル TNEP(Tag NDEF Exchange Protocol)は、スキャナデバイス(例: 携帯電話)と能動的に給電された IoT デバイスの間で双方向通信を可能にします。これは現時点では Web NFC でサポートされておらず、また受け入れる入力に制限があり、 IoT デバイスは受け付けるレコードが有効であることを保証しなければなりません。

タグがプライバシーに敏感なデータを含む場合、そのようなデータはサイトと共有されます。UX がデータ交換の前にユーザーによる確認を 要求する場合は、すぐには共有されない可能性もあります。

場合によっては、タグ/デバイスがプライバシーに敏感なデータを含むことが明らかなこともあります。例えば NFC 対応の会議バッジや 名刺の場合です。これは、あなたや近親者が糖尿病患者であることを示しうる、NFC 対応のグルコースメータでも同様です。

他の場合には起こりうることが明確でないかもしれませんが、ユーザーがタグにデータを書き込むアプリやウェブサイトを使用し、 ユーザーの知らないうちにユーザー ID などをエンコードしてしまい、その後別のサイトが読み戻せることがあります。

私的で予期しないデータは、ファイル(例: ワープロ文書、PDF、カメラ画像)にも保存され、ファイルアップロード API を使って アップロードされることがあります。Web NFC API に関連する緩和策は、ファイルアップロードに関連するものよりも強力であり、 個人を特定できる可能性はより低くなります。

スキャン中の読み取りと書き込み

タグのスキャンは、ウェブサイトがタグを識別する方法を知っており、かつ現実世界でのタグの位置を知っている場合(例えば博物館内に 設置されているなど)、ユーザーの位置を明らかにすることがあります。また、FeliCa NFC タグは主に日本で使用されている等の理由から、 ある程度推測できるかもしれませんが、Web NFC は使用されているタグ技術を明らかにはしません。

これは、ユーザーのアクションを必要とし、バックグラウンドでトリガーされないため、ウェブの広告およびトラッキングモデルを 現実世界に持ち込むものではありません。適切な UX によって、スキャンがアクティブであることが明確になるべきです。

既存データの上書き

NFC タグに書き込むとタグを壊してしまう、いわゆる「文鎮化(brick)」してしまうのではないかという懸念もあります。 NFC タグは複数のユーザーアプリケーションによって読み取られるように設計されており、NDEF タグは簡単に恒久的な読み取り専用にできるように 設計されています。これは工場出荷時の設定でそのように構成することもできます。

NDEF はデータの読み書きのための単純な交換フォーマットであり、双方向通信のためのものではありません。NFC は下位技術に基づく複数の 通信フォーマットをサポートしており(そのため NDEF のように読み取り専用にロックされていません)が、これらはいずれも Web NFC では サポートされていません。

NFC 利用時にユーザーが認識すべき事項

本節では、NFC の使用時にユーザーが認識すべき事項のいくつかを詳述します。実装は、関連する NFC アクションが実行される前または実行時に、 これらの事実をユーザーに教育するのを助けることが推奨されます。

読み取られたデータはサイトと共有される

サイトが NFC コンテンツを読み取るアクセス権を持っている場合、スキャンされたタグのデータは、ファイルや画像をアップロードする場合と 同様にサイトと共有されます。どのサイトでも同じですが、そのデータを適切かつ意図した方法で扱うかどうかはユーザーの信頼に委ねられます。

読み取り専用でないタグのデータはサイトによって変更・上書きされ得る

店舗内のタグなど、展開された NFC ソリューションは、誤って、または悪意ある行為の一環として変更されないよう、常に読み取り専用に すべきです。

私用のタグやステッカーは工場出荷時にロック解除(書き込み可能)されていることが多く、そのようなタグはスキャンによって上書き/変更される 可能性があることをユーザーは認識すべきです。

固定(例: 設置)タグの読み取りは読み取り位置を露出し得る

固定タグは、そのデータ内に ID や位置をエンコードしている場合があり、そのため、タグの物理的な位置を把握しているサイトにその情報が 露出し、読み取りが行われた位置を推定できることがあります。これがサービスへのログインと組み合わさると、あなたの位置情報をサイトと 共有してしまう可能性があります。

書き込まれたデータは、読み取りアクセス権が付与された他のアプリやサイトから読み取れます。 タグ上の任意の NDEF データは、適切なアクセスを持つ任意のアプリやウェブサイトによって読み取られます。 その意図がない場合は、想定された読者のみが読み取れるように、データを安全な方法で暗号化すべきです。

同時に複数のタグが読み取りフィールド内に存在し得る

NFC は一度に 1 つのタグしか読み取れませんが、複数のタグを検出でき、そのうち 1 つを通信対象のタグとして選択できます。

このユースケースは、財布の中に複数のスマートカード(NFC ベース)があり、カードを取り出したくない場合などが考えられます。

これは主に外部ハードウェアによって読み取られる決済カードや交通系カードに有用であり、Web NFC のユースケースではありません。 Web NFC では、以下の攻撃ベクトルを防ぐため、複数のタグが利用可能な場合の読み取りを許可しません。

誰かが正規のタグの上に悪意のある NFC タグ/ステッカーを重ねて貼り、誤ったアプリ/サイトを読み込ませたり、 正しいアプリ/サイトに誤ったデータを注入したりする攻撃ベクトルがあります。これは、元のタグのデータをクローンして変更することで 実行できます。例えば URL を変更して悪意のあるアプリ/サイトを読み込ませる、あるいは正しいアプリ/サイトに悪意のあるデータを 注入するなどです。例: タグは本来 https://example.com に誘導するはずが、 https://exаmple.com(キリル文字の а を使用)に変更される——見た目は正規に見え、 機微なデータを悪意のあるサイトに渡してしまうかもしれません。

タグからウェブサイトを読み込むことは Web NFC の範囲外ですが、上記の攻撃ベクトルのため、複数のタグが利用可能な場合に ユーザーエージェントが URL を自動読み込みしないことが推奨されます。

複数のタグが利用可能なときに読み取りを禁止することで、Web NFC はサイトへの誤/悪意あるデータ注入に対して十分に保護します。 既存の NFC タグを遮蔽することは容易ではなく、目立つフェライトシールドが必要となるためです。金属は磁場に干渉し、 タグを読み取れなくします。

資産

保護すべき資産には以下が含まれます:
  • NDEF message 全体、および特に NDEF records(ペイロードとヘッダを含む)。 これらが Web NFC によってトリガーされた操作で上書きされる際、転送中または保存中の状態における データ漏えいとデータ改ざんからの保護。 これには、NFC タグと連携したソリューションに対するサービス不能(DoS)攻撃(例: ソリューションに紐づくタグを破壊) も含まれます。
  • ユーザーの識別子やその他のプライバシーに敏感な属性。 これは Web NFC、NFC content の作成者、または Web NFC を使用するウェブサイトによって 直接的または間接的に特定され得ます。 これらのデータは直接利用されたり、第三者に漏えいされたりする可能性があります。 例として、ユーザーの位置、デバイス識別子、ユーザー識別子などが挙げられます。
  • Web NFC を使用するウェブページに公開されるユーザーデータ。 ウェブページは Web NFC 以外の手段でもユーザーデータを収集し得ますが、このデータを NDEF レコードに 埋め込み、Web NFC 経由で共有する可能性があります。
  • ユーザーデバイスの完全性。 NFC タグの読み取りはユーザーデバイスの侵害を引き起こす可能性があり、さらに他の Web NFC や プラットフォーム資産の損失につながり得ます。

攻撃者モデル

次の攻撃者パターンが考慮されています:

脅威

NFC セキュリティの概要は こちら にあります。 Web NFC に対する潜在的な脅威は以下のとおりです。

フィンガープリンティングとデータ収集

脅威の説明
悪意のあるウェブページが、ユーザーの同意なしにユーザーデータ、アイデンティティ、またはその他の プライバシーに敏感な属性(位置情報など)を収集し、それを第三者に公開する(NFC タグに書き込む)こと。
影響を受ける資産
ユーザーデータ、ユーザーのアイデンティティ、その他のプライバシーに敏感な属性
アクター
Web NFC を使用する悪意のあるウェブページの所有者、悪意のあるタグの所有者。
緩和策・コメント
ユーザーは、与えられたウェブページから NFC を使用して共有され得るデータについて把握できるべきです(SHOULD)。 個人データへのアクセスには許可とユーザープロンプトを使用し、NFC に公開されるユーザーデータを最小化します。 NFC タグが、ユーザーの許可なく、ユーザーのデバイスにウェブサイトへの遷移を引き起こすべきではありません(SHOULD NOT)。 サイトがフォアグラウンドにある、またはフォアグラウンドに移されたうえで、許可が付与されている場合を除きます。 ユーザーエージェントは、Geolocation API に列挙された セキュリティとプライバシー対策を考慮すべきです。

NFC タグの改ざん

脅威の説明
ユーザーの同意なしに NFC タグが改ざんされ、場合によっては読み取り専用にされることで不可逆的な変更となる。 これにより、悪意のあるタグを用いたさらなる攻撃を可能にしたり、1つ以上のタグを使用不能にするサービス不能攻撃となり得る。
影響を受ける資産
保存中の NDEF メッセージレコード(ペイロードおよびヘッダを含む)。
アクター
悪意のあるウェブページ作成者、悪意のあるユーザー。
緩和策・コメント
タグへの書き込みや読み取り専用化には、許可とユーザープロンプトを必要とする。 あるいは、特定のウェブページが書き込めるタグを制御する(例えば、ウェブページは自らの origin に紐づけられるタグにのみ書き込める)。 または、上書きを許可する(書き込み対象ではないタグは読み取り専用にすることで保護できるため)。 NDEF signature を使用して NFC タグの改ざんを検出する。

転送中の NDEF レコード改ざん

脅威の説明
Web NFC と NFC adapter およびユーザーデバイス間で転送される NDEF record が改ざんされ、 さまざまな中間者攻撃やサービス不能(DoS)攻撃を引き起こす。 また、NDEF signature レコードが、変更されたコンテンツとともに削除・置換され得る。
影響を受ける資産
転送中の NDEF record
アクター
悪意のある中間者。
緩和策・コメント
この脅威は Web NFC 実装の範囲外です。アプリケーションは NDEF signature と適切なツール (署名アルゴリズム、証明書、セキュリティポリシー)を使用して NFC content を保護できます。 さらに、プラットフォームスタックを強化します。

NDEF レコードのペイロード漏えい

脅威の説明
保存中(NFC タグ上)または Web NFC と NFC adapter の間で転送中の NDEF record の機密ペイロードが、権限のない第三者に読み取られる。
影響を受ける資産
転送中および保存中の機密 NDEF メッセージペイロード。
アクター
悪意のある中間者、悪意のあるウェブページ作成者。
緩和策・コメント
機密性を確保するため、ペイロードの暗号化と、安全な通信、認証および認可を使用して、 Web NFC と NFC adapter 間のデータ交換を行います。

悪意のある NFC タグによる能動的攻撃

脅威の説明
悪意のあるタグが、意図的または非意図的にデバイスに読み取られ、読み取られたデータがユーザーエージェントに対する 攻撃ベクトルとなり得る。例えば、デバイス上でアクションをトリガーしようとする(悪意のあるウェブサイトの起動や、 デバイス攻撃用に準備された画像を開くなど)。
影響を受ける資産
ユーザーデバイスの完全性、その他すべての Web NFC 資産。
アクター
悪意のあるタグ作成者。
緩和策・コメント
これは既存のすべての NFC タグに共通する一般的な問題です。データはアプリケーション固有と見なされます。 実装にはセキュリティの強化が必要です。短距離と読み取りのための厳しい角度、そしてフォーカス要件により、 非意図的な接触の可能性は低いです。スマートポスターやその他のタグに対する自動アクションは許可すべきではありません。 ユーザーは、NFC 通信中に何が起こっているかを認識し、制御できるようにされなければなりません。 例えば smart poster からのコンテンツを開く、NFC handover による(悪意のある可能性のある)Wi-Fi への 自動接続など。信頼できない NFC タグからのアクションは許可しないでください。信頼は NDEF signature チェック によって確立できます。

実装のためのセキュリティメカニズム

Obtaining permission

実装は、例えばユーザーによる明示的な許可など、obtain permission するためのメカニズムを使用すべきです(SHOULD)。 NFC 関連の許可を実装するために、ユーザーエージェントが [[[PERMISSIONS]]] API を使用することが提案されています。

実装はセッションごと/エフェメラルな許可を使用しても構いません(MAY)。

Warning user during NFC operations

実装は、ウェブページが NFC アダプタにアクセスしている(例: スキャンが進行中)際に、ユーザーに警告するため、 オーバーレイダイアログを表示しても構いません(MAY)。

アプリケーションのためのセキュリティメカニズム

NFC content の暗号化

NFC 経由で交換されるデータの機密性を信頼するために、アプリケーションは PKI(公開鍵基盤)に基づく鍵管理とともに 暗号化された NFC content を使用しても構いません。鍵管理は Web NFC の範囲外です。

NDEF レコードへの署名

NFC 経由で交換されるデータの完全性を信頼するために、ユーザーエージェントは鍵管理に PKI を用いた NDEF signature を使用しても構いません(MAY)。

NDEF signature バージョン 1.0([[NFC-SECURITY]])で署名されたタグでは、署名は TYPE fieldID fieldPAYLOAD field にのみ適用され、NDEF ヘッダの先頭 1 バイトは 除外されるため、攻撃の余地が残ります。[[NFC-SECURITY]] バージョン 2.0 では、署名にタグのハードウェア属性を含め、 より短い証明書を許容しました。

1 つの NDEF signature は、次の NDEF signature または NDEF message の先頭に達するまでの 先行レコードをカバーします。

既知の脆弱性 を緩和するため、アプリケーションは常に 1 つの NDEF signatureNDEF message 全体に署名し、署名の作成と検証のために適切なツールチェーンとセキュリティポリシーを 使用することが推奨されます。

セキュリティポリシー

本節は実装に関する規範的なセキュリティポリシーを列挙します。

セキュアコンテキスト

secure contexts のみが NFC content にアクセスすることを許可されます。 ブラウザは開発目的に限りこの規則を無視しても構いません(MAY)。

可視のドキュメント

Web NFC の機能は、top-level browsing context の {{Document}} のうち、 その {{Document/visibilityState}} が `"visible"` の場合に限り許可されます。

これはまた、表示がオフである、またはデバイスがロックされている場合、UA は NFC 無線へのアクセスをブロックすべきであることを意味します。 バックグラウンドのウェブページについては、NFC content の受信および書き込みは suspended でなければなりません(MUST)。

許可の制御

NFC tag を恒久的に読み取り専用にするには、obtain permission を 必ず行わなければならず(MUST)、そうでなければ失敗します。 [[[#making-content-read-only]]] 節を参照してください。

NFC content を読み取るためのリスナー設定は、obtain permission を行うべきです(SHOULD)。

NFC contentNFC tag に書き込むには、obtain permission を 必ず行わなければなりません(MUST)。[[[#writing-content]]] 節を参照してください。

現在の閲覧セッションを超えて保存されるすべての許可は、取り消し可能でなければなりません(MUST)。

ブロックリスト

Web NFC には、ウェブサイトがそれらを悪用するのを防ぐための脆弱な NFC デバイスの blocklist が含まれます。

物理的な位置漏えいのリスクの警告

NFC content のリッスンおよび書き込み時に、与えられた origin が物理的位置を推測できる可能性について、 UA はユーザーに警告しても構いません(MAY)。

自動処理の制限

NFC content 上のペイロードデータが信頼できない場合、ユーザーが承認しない限り、 UA はそのコンテンツの自動処理(NFC tag 内の URL を用いたウェブページのオープン、アプリのインストール、 その他のアクションなど)に使用してはなりません(MUST NOT)。

NFC content の署名

次のポリシーは、アプリケーションによる実装が推奨されます。
  • smart poster は、同一発行者による単一の NDEF signature レコードで署名され、 メッセージ内の最初のレコードであるか、または別の NDEF signature に先行している場合にのみ信頼しても構いません(MAY)。
  • NDEF message は、同一発行者による単一の NDEF signature レコードで署名されている場合にのみ 信頼しても構いません(MAY)。
  • ユーザーエージェントは NDEF signature レコードを検証せずに公開します。署名の検証および NFC content の署名はアプリケーションの責任です。
  • アプリケーションは NDEF signature の作成と検証に適切な署名アルゴリズム、証明書、セキュリティポリシーを 使用すべきです(SHOULD)。また、NDEF signature に対する既知の攻撃(例えば NDEF signature の削除、NDEF signature を変更された NFC content とともに置換、 レコードの PAYLOAD LENGTH field を変更することでの順序入れ替えなど)を考慮してください。

謝辞

編集者は、本ドキュメントへの貢献に対し、Jeffrey Yasskin、Anne van Kesteren、 Anssi Kostiainen、Domenic Denicola、Daniel Ehrenberg、Jonas Sicking、 Don Coleman、Salvatore Iovene、Rijubrata Bhaumik、Wanming Lin、Han Leon、 Ryan Sleevi、Balázs Engedy、Theodore Olsauskas-Warren、Reilly Grant、 Diego González、および Daniel Appelquist に感謝します。

特別な感謝を、NFC をウェブプラットフォームに公開する取り組みの 初期のwork と現行アプローチの支援に対して Luc Yriarte と Samuel Ortiz に。また、セキュリティとプライバシーの節への貢献に対して Elena Reshetova に特別な感謝を表します。