近距離無線通信(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 アダプタを持つ場合があります。
メッセージの読み取り方法は、リーダーとタグが同一メーカーであることを要求する 独自技術を通じて行われる場合があります。それらはまた NDEF メッセージを公開することがあります。
現在の仕様では、ピアツーピアはサポートされていません。
NFC device は NFC peer または NFC tag のいずれかです。
NDEF は NFC Forum Data Exchange Format の略で、[[!NFC-NDEF]] に標準化された 軽量のバイナリメッセージ形式です。
NDEF message は1つ以上のアプリケーション定義の NDEF record をカプセル化します。 NDEF メッセージは NFC tag に保存されたり、NFC対応デバイス間で交換されたりします。
用語 NFC content は NFC tag へ送信または受信される全てのバイトを指します。 現在の API ではこれは NDEF message と同義です。
NFC は NFC Forum により標準化されており、[[NFC-STANDARDS]] に記述されています。
NFC Forum は、NFC デバイスで操作可能にするために 5 種類の異なるタグタイプのサポートを義務付けています。 同様の要件は Android のようなオペレーティングシステムにも求められます。
それに加えて、MIFARE Standard は古い MIFARE Standard 上で NDEF を動作させる方法を規定しており、 実装者によってはオプションでサポートされることがあります。
NDEF マッピングに関する注記は次にあります: MIFARE Classic as NFC Type MIFARE Classic Tag.
MIFARE Standard は NFC Forum のタイプではなく、NXP ハードウェアを使用するデバイスのみが読み取り可能です。 MIFARE Standard に基づくタグの読み書きのサポートは非標準的ですが、普及とレガシーシステムでの使用のために タイプに含まれています。
NFC Forum によって NDEF record 用に標準化されたデータタイプに加えて、バスカードやドアオープナーのような多くの商用製品は MIFARE Standard に基づいており、動作のために特定の NFC チップ(カードとリーダーが同じベンダーであること)を要求する場合があります。
NDEF record は NDEF message の一部です。各レコードはデータペイロードと関連する 型情報を含むバイナリ構造です。加えて、ペイロードサイズやデータが複数レコードに分割されているか等の データ構造に関する情報も含みます。
最初の3バイト(図の行)が必須です。最初にヘッダバイト、その後に TYPE LENGTH field と PAYLOAD LENGTH field が続き、どちらもゼロである場合があります。
| 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 field は TYPE field のバイト長を表す符号なし8ビット整数です。
TYPE field は PAYLOAD field の構造、エンコーディング、および形式を記述する グローバルに一意で管理された識別子であり、TNF field の値によって決定されます。
[[[!NFC-RTD]]] は TYPE field の名前を大文字小文字を区別せずに比較することを要求しています。
ID LENGTH field は ID field のバイト長を表す符号なし8ビット整数です。
ID field は URI 参照([[RFC3986]])の形式の識別子で、一意であり絶対または相対にすることができます (後者の場合、アプリケーションはベースURIを提供しなければなりません)。中間および終端のチャンクレコードは ID field を持つべきではなく、他のレコードは持つことができます。
PAYLOAD LENGTH field は PAYLOAD field のバイト長を示します。 SR field が `1` の場合、そのサイズは1バイトで、そうでなければ4バイトであり、 それぞれ8ビットまたは32ビットの符号なし整数を表します。
PAYLOAD field はアプリケーションのバイトを運びます。データの内部構造は NDEF にとって不透明です。 後述の特定のケースでは、このフィールドがデータとして NDEF message を含む場合があります。
empty record の TYPE LENGTH field、 ID LENGTH field、および PAYLOAD LENGTH field は `0` でなければならず、 したがって TYPE field、ID field、および PAYLOAD field は存在してはなりません。
NFC Forum は [[NFC-RTD]](Resource Type Definition 仕様)で、テキスト、URL、メディア等の 有用なサブレコードタイプの小さなセットを標準化しています。これらを well-known type record と呼びます。 さらに、スマートポスター(オプションで埋め込まれた URL、テキスト、署名、アクション用のレコードを含む)やハンドオーバーレコードのような より複雑な相互作用のために設計されたレコードタイプもあります。
well-known type records の TYPE field に格納される型情報は二種類あります: ローカル型 と グローバル型。
NFC Forum の local type は NFC Forum またはアプリケーションによって定義され、 常に小文字の文字または数字で始まります。これらは通常、包含するレコードのローカルコンテキスト内でのみ一意な短い文字列です。 型の意味が包含するレコードのローカルコンテキストの外で重要でない場合や、ストレージ使用量が厳しい制約である場合に使用されます。 Smart poster はローカル型の使用例です。
[=local type=] は包含レコードの型で定義されるため、ネームスペースを必要としません。 このため同じローカル型名が別のレコード型内で異なる意味や異なるペイロード型で使用されることがあります。
NFC Forum の global type は NFC Forum によって定義・管理され、通常は大文字で始まります。 例:テキストのための "`T`"、URL のための "`U`"、スマートポスターの "`Sp`"、 署名の "`Sig`"、ハンドオーバーキャリアの "`Hc`"、ハンドオーバー要求の "`Hr`"、ハンドオーバー選択の "`Hs`" など。
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 になり得ます。
[[NDEF-SMARTPOSTER]] は、アプリケーションが NDEF message 内に存在する場合、 他の URI record が同時に含まれていても、smart poster レコードのみを使用することを 規定しています。
| 値 | 説明 |
|---|---|
| 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 は値を提供するだけです。
NDEF Signature は [[NDEF-SIGNATURE]] で定義されています。 その TYPE field は "`Sig`"(`0x53`, `0x69`, `0x67`)を含み、 PAYLOAD field はバージョン、署名、証明書チェーンを含みます。
現在の仕様では、これはサポートされていません。
NFC handover は [[NFC-HANDOVER]] で定義されており、 Bluetooth や WiFi のような代替通信キャリアのネゴシエーションと有効化を可能にする対応するメッセージ構造です。 ネゴシエートされた通信キャリアは、その後(別途)両デバイス間で写真送信、Bluetooth プリンタへの印刷、 テレビへのビデオストリーミングなどの特定のアクティビティを行うために使用されます。
現在の仕様では、これはサポートされていません。
absolute-URL record では TYPE field が absolute-URL string を含み、ペイロードではありません。
注:Windows Phone のような一部プラットフォームはペイロードに追加データを格納していましたが、 これらのレコードの任意のペイロードデータは Android のような他のプラットフォームによって無視されます。 Android でそのようなレコードを読み取ると、Chrome で URL を読み込もうとし、クライアントアプリケーション向けには 意図されていません。
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 record は関連付けられた MIME type を持たない不透明なデータを格納するレコードであり、 `application/octet-stream` のデフォルト MIME type が想定され得ます。 [[NFC-NDEF]] 仕様は、NDEF パーサーがペイロードを処理せずに保存または転送することを推奨しています。
Web NFC の任意の実装は、チャンク化されたレコードを単一の論理レコードとして透過的に公開しなければなりません。
いくつかの NFC ユーザーシナリオは こちら および Web NFC Use Cases ドキュメントに列挙されています。基本的な Web NFC の相互作用は以下の通りです。
Web NFC を使用している top-level browsing context の {{Document}} が 可視状態のときに、NDEF message を含む NFC tag を読み取ります。 例えば、ウェブページがユーザーに NFC タグをタップするよう指示し、その後タグから情報を受け取る場合です。
注:NFC tag への書き込み操作は常に読み取り操作も伴う点に注意してください。
注:NFC tag を恒久的に読み取り専用にする操作は常に読み取り操作を伴います。
ユーザーは内蔵アダプタに加えて、1 台以上の外部 NFC adapter をデバイスに接続する場合があります。ユーザーは任意の NFC adapter を使用できます。
本セクションでは、開発者が本仕様のさまざまな機能をどのように利用できるかを示します。
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 タイプの 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`);
}
}
};
関連するデータソースのフィルタリングはカスタムレコード識別子(この場合 "`/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 データの保存と受信は、直列化と逆直列化で簡単に行えます。
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}.`);
}
};
{{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 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 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 タグを恒久的に読み取り専用にすることは簡単です。
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}`);
}
任意の NDEF message の内容は NDEFMessage インターフェイスによって公開されます:
[SecureContext, Exposed=Window]
interface NDEFMessage {
constructor(NDEFMessageInit messageInit);
readonly attribute FrozenArray<NDEFRecord> records;
};
dictionary NDEFMessageInit {
required sequence<NDEFRecordInit> records;
};
records プロパティは list の NDEF record を表し、 NDEF message を定義します。
NDEFMessageInit 辞書は NDEF message を初期化するために使用されます。
任意の 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 の種類を表します。
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 およびペイロードデータ data で NDEF record を初期化するために使用されます。
NDEFRecordInit のデータ型から NDEF record 型へのマッピングは、 アルゴリズム的な手順で示されており、[[[#steps-receiving]]] および [[[#writing-content]]] セクションで説明されています。
convert NDEFRecord.data bytes を実行するには、 |record:NDEFRecord| が与えられたとき、次の手順を実行してください:
この文字列は NDEFRecord に許可される record types を定義します。 [[[#data-mapping]]] セクションはそれが NDEF record 型にどのようにマッピングされるかを説明します。
標準化された well known type name は次のいずれかです:
[=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 record と external 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 は、タグなどのデバイスが磁気誘導フィールド内にあるときに browsing context に NFC 機能を公開し、NDEF messages を読み取ります。 また、到達範囲内の NFC tag に NDEF 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 プロパティは衝突回避と識別に用いられるデバイスのシリアル番号を表し、 利用できない場合は空文字列です。message は NDEFMessage オブジェクトです。
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 をサポートする browsing context の active document の relevant 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 に設定します。
内部スロットは本仕様における記法としてのみ使用され、実装がそれらを明示的な内部プロパティに対応付ける必要はありません。
Web NFC API は [=default powerful feature=] であり、[=powerful feature/name=] は
"nfc" です。
nfc " で
[=getting the current permission state=] を実行した結果とします。
nfc" の request permission to use を行います。
許可された場合は true を返します。
request permission の手順はまだ明確に定義されていません。
現時点では、UA は与えられた origin と global object に対する "nfc" の
ポリシーについてユーザーに問い合わせ、ユーザーが許可すれば true を返します。
本仕様における [=page visibility change steps=] は、文字列 |visibilityState| と {{Document}} |document| が与えられた場合、次のとおりです:
用語 suspended は、NFC 操作が一時停止された状態を指し、 一時停止中は NDEFReader によっていかなる NFC content も書き込まれず、 受信した NFC content もいかなる {{NDEFReader}} にも提示されません。
promise を reject すると pending write tuple はクリアされます。
promise を reject すると pending makeReadOnly tuple はクリアされます。
environment settings object 上で release NFC するには、次の手順を実行します:
UA はドキュメントの relevant settings object が与えられた場合、 追加の unloading document cleanup steps として release NFC しなければなりません。
dictionary NDEFWriteOptions {
boolean overwrite = true;
AbortSignal? signal;
};
overwrite プロパティの値が false の場合、write アルゴリズムは NFC tag を読み取り、その上に NDEF レコードがあるかどうかを確認し、 ある場合はいかなる保留中の書き込みも実行しません。
signal プロパティにより、{{NDEFReader/write()}} 操作を中止できます。
dictionary NDEFMakeReadOnlyOptions {
AbortSignal? signal;
};
signal プロパティにより、{{NDEFReader/makeReadOnly()}} 操作を中止できます。
dictionary NDEFScanOptions {
AbortSignal signal;
};
signal プロパティにより、{{NDEFReader/scan()}} 操作を中止できます。
本セクションは、タイマーが期限切れになる前に、次回近接範囲に入ったときに NFC tag に NDEF message を書き込む方法を説明します。 任意の時点で、現在のメッセージが送信されるか書き込みが中止されるまで、 1 つの origin につき設定可能な NDEF message は最大 1 つです。
UA はこの地点でメッセージ書き込みを中止することがあります。 終了の理由は実装詳細です。例えば、要求された操作をサポートできない場合があります。
書き込みは、それまでに構成された書き込み操作をすべて置き換えます。
NFC is suspended の場合、ユーザーによって promise が中止されるか、 NFC tag が通信範囲に入るまで待機を続けます。
近接範囲の NFC tag が未フォーマットで NDEF フォーマット可能な場合は、 それをフォーマットし、|output| をバッファとして書き込みます。
複数のアダプタはユーザーにより順次使用されるべきです。 異なる複数の接続された NFC adapter で同時にタップが起こる可能性は非常に低いです。 それが起きた場合、成功するまで(望ましくは 1 台ずつ)タップを繰り返す必要があるでしょう。 ここでのエラーは操作を繰り返す必要があることを示します。そうしないと、 すべての接続された NFC adapter で操作が成功したとユーザーが誤解する恐れがあります。
現時点の Web NFC は、smart poster 内で external type と local type のレコードを書き込むことを許可しています。 また、empty records も許可されます。 アプリケーションは smart poster 内の余分なレコードを無視してもかまいません。
アイコンレコードのメディアタイプは `"image/"` や `"video/"` に限定することもできますが、 [[NDEF-SMARTPOSTER]] 仕様では実際には他のメディアタイプレコードも smart poster 内で許容され、 例えば vCard の連絡先カードが関連する MIME types のいずれかを使用するなど、アプリケーション固有の方法で扱えます。
[[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 records は TNF FIELD を `0x04` に設定することで区別されるため、 型名の衝突リスクはないと考えられます。 また、Web で URN の使用を避けるよう求める W3C TAG の勧告もあります。 したがって、Web NFC は NDEF messages の読み取り・書き込みのいずれでも URN 接頭辞を使用しません。
|input:USVString| を与えて split external type するには、次の手順を実行します:
|input:USVString| を与えて validate external type するには、次の手順を実行します:
|input:USVString| を与えて validate local type するには、次の手順を実行します:
これは、クライアントが特に [=well-known type record=] としてテキストを書き込みたい場合に有用です。 他の選択肢として、明示的な MIME type のテキスト型を用いる "`mime`" の値を使用することもでき、 例えば "`text/xml`" や "`text/vcard`" を用いることで、より明確な区別が可能です。
[[NDEF-SMARTPOSTER]] 仕様では smart poster 内の URL は 1 つのみ許可され、 それは単一の URI record でなければなりません。
本セクションは、近接範囲内にあるときに NFC tag を恒久的に読み取り専用にする方法を説明します。 任意の時点で、NFC tag が恒久的に読み取り専用にされるか操作が中止されるまで、 1 つの origin につきリクエストは最大 1 件です。
UA はこの地点で中止することがあります。終了の理由は実装詳細です。 例えば、実装が要求された操作をサポートできない場合があります。
読み取り専用化操作は、それまでに構成された読み取り専用化操作をすべて置き換えます。
NFC is suspended の場合、ユーザーによって promise が中止されるか、 NFC tag が通信範囲に入るまで待機を続けます。
この操作は一方向であり、元に戻すことはできません。 一度 NFC タグを読み取り専用にすると、以後書き込みはできません。
NFC content をリッスンするには、クライアントは NDEFReader.scan() を呼び出して {{NDEFReader}} インスタンスをアクティブ化しなければなりません(MUST)。 その上で "`reading`" イベントのイベントリスナをアタッチすると、NFC content にアクセスできます。
activated reader objects に任意の {{NDEFReader}} インスタンスが存在する場合、 UA は接続されたすべての NFC アダプタで NDEF message をリッスンしなければなりません(MUST)。
受信する NFC content は {{NDEFReader}} インスタンスで照合されます。
UA は未フォーマットの NFC tag を、NDEF record を含まない NDEF message として(すなわち {{NDEFMessage/records}} プロパティが空配列)表現するべきです(SHOULD)。
型 serialNumber の |serialNumber:serialNumber| と、型 NDEFMessage の |message:NDEFMessage| を与えて dispatch NFC content するには、次の手順を実行します:
チャンク化されたレコードはサブレコードとして許可されないため、ビット 5(CF field)は無視します。
アプリケーションは data に対して toRecords() を呼び出して
NDEF records にパースするか、自身でパースしてもかまいません。
アプリケーションはこの値を、smart-poster 内の URI レコードが参照する オブジェクトのサイズを示す 32 ビット符号なし整数としてパースできます。
アプリケーションはこの値を、smart-poster 内の URI レコードが参照する オブジェクトのメディアタイプを示す [[RFC2048]] メディアタイプを含む文字列としてパースできます。
アプリケーションはこの値を 8 ビット符号なし整数としてパースでき、その値は ここで定義されています。
本仕様は、ウェブサイトがアクセスできる NFC デバイスの集合を制限するためにブロックリストファイルに依存します。
|url:URL| における parsing the blocklist の結果は、次のアルゴリズムによって生成される historical bytes の16進値のリストです:
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 ソリューションは、誤って、または悪意ある行為の一環として変更されないよう、常に読み取り専用に すべきです。
私用のタグやステッカーは工場出荷時にロック解除(書き込み可能)されていることが多く、そのようなタグはスキャンによって上書き/変更される 可能性があることをユーザーは認識すべきです。
固定タグは、そのデータ内に 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 タグを遮蔽することは容易ではなく、目立つフェライトシールドが必要となるためです。金属は磁場に干渉し、 タグを読み取れなくします。
次の攻撃者パターンが考慮されています:
NFC セキュリティの概要は こちら にあります。 Web NFC に対する潜在的な脅威は以下のとおりです。
実装は、例えばユーザーによる明示的な許可など、obtain permission するためのメカニズムを使用すべきです(SHOULD)。 NFC 関連の許可を実装するために、ユーザーエージェントが [[[PERMISSIONS]]] API を使用することが提案されています。
実装はセッションごと/エフェメラルな許可を使用しても構いません(MAY)。
実装は、ウェブページが NFC アダプタにアクセスしている(例: スキャンが進行中)際に、ユーザーに警告するため、 オーバーレイダイアログを表示しても構いません(MAY)。
NFC 経由で交換されるデータの機密性を信頼するために、アプリケーションは PKI(公開鍵基盤)に基づく鍵管理とともに 暗号化された NFC content を使用しても構いません。鍵管理は Web NFC の範囲外です。
NFC 経由で交換されるデータの完全性を信頼するために、ユーザーエージェントは鍵管理に PKI を用いた NDEF signature を使用しても構いません(MAY)。
NDEF signature バージョン 1.0([[NFC-SECURITY]])で署名されたタグでは、署名は TYPE field、ID field、PAYLOAD field にのみ適用され、NDEF ヘッダの先頭 1 バイトは 除外されるため、攻撃の余地が残ります。[[NFC-SECURITY]] バージョン 2.0 では、署名にタグのハードウェア属性を含め、 より短い証明書を許容しました。
1 つの NDEF signature は、次の NDEF signature または NDEF message の先頭に達するまでの 先行レコードをカバーします。
既知の脆弱性 を緩和するため、アプリケーションは常に 1 つの NDEF signature で NDEF 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 content を NFC tag に書き込むには、obtain permission を 必ず行わなければなりません(MUST)。[[[#writing-content]]] 節を参照してください。
現在の閲覧セッションを超えて保存されるすべての許可は、取り消し可能でなければなりません(MUST)。
Web NFC には、ウェブサイトがそれらを悪用するのを防ぐための脆弱な NFC デバイスの blocklist が含まれます。
NFC content のリッスンおよび書き込み時に、与えられた origin が物理的位置を推測できる可能性について、 UA はユーザーに警告しても構いません(MAY)。
NFC content 上のペイロードデータが信頼できない場合、ユーザーが承認しない限り、 UA はそのコンテンツの自動処理(NFC tag 内の URL を用いたウェブページのオープン、アプリのインストール、 その他のアクションなど)に使用してはなりません(MUST NOT)。
編集者は、本ドキュメントへの貢献に対し、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 に特別な感謝を表します。