근거리 무선 통신(NFC)은 일반적으로 몇 센티미터 이내의 근접 거리에서 두 장치 간 무선 통신을 가능하게 합니다. NFC는 13.56 MHz에서 동작하는 근접 결합 장치의 단순 무선 상호 연결을 위한 인터페이스와 프로토콜을 정의하는 국제 표준(ISO/IEC 18092)입니다.
하드웨어 표준은 [[[NFC-STANDARDS]]]에 정의되어 있습니다.
이 문서는 NFC 기술을 기반으로 한 선택된 사용 사례를 가능하게 하는 API를 정의합니다. 이 사양의 현재 범위는 NDEF입니다.
저수준 I/O 연산(예: ISO-DEP, NFC-A/B, NFC-F)과 호스트 기반 카드 에뮬레이션(HCE)은 현재 범위 내에서 지원되지 않습니다.
구현자는 이 사양이 불안정한 것으로 간주된다는 점을 인지해야 합니다. 논의에 참여하지 않는 구현자들은 사양이 호환되지 않는 방식으로 변경되는 것을 경험할 수 있습니다. 이 사양이 결국 후보 권고(Candidate Recommendation) 단계에 도달하기 전에 구현을 고려하는 벤더는 GitHub의 저장소를 구독하고 논의에 참여해야 합니다.
이 문서는 단일 제품에 적용되는 적합성(conformance) 기준을 정의합니다: 해당 인터페이스를 구현하는 UA(사용자 에이전트).
Web NFC 사용자 시나리오는 다음과 같습니다: 장치를 플라스틱 카드나 스티커와 같은 수동 전원 NFC 태그에 근접시켜 데이터를 읽거나 쓰는 것.
NFC는 자기 유도를 사용하여 작동합니다. 즉 판독기(능동적이고 전원이 공급되는 장치)가 작은 전하를 방출하여 자기장을 생성합니다. 이 자기장은 수동 장치에 전원을 공급하여 데이터를 통신하기 위한 전기적 임펄스로 변환합니다. 따라서 장치들이 범위 내에 있을 때 항상 읽기가 수행됩니다(NFC 아날로그 사양 및 NFC 디지털 프로토콜, NFC Forum, 2006 참조). P2P(peer-to-peer) 연결도 유사하게 작동하며, 장치는 주기적으로 소위 이니시에이터 모드로 전환하여 대상을 스캔한 다음 대상 모드로 되돌아갑니다. 대상이 발견되면 데이터는 태그의 경우와 동일한 방식으로 읽힙니다.
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 기술 사양을 참조하십시오.
NFC adapter는 주어진 하드웨어 요소(NFC 칩)에 구현된 NFC 기능에 대한 접근을 제공하는 기본 플랫폼의 소프트웨어 엔터티입니다. 장치는 내장형 어댑터와 USB를 통해 연결된 하나 이상의 어댑터 등 여러 개의 NFC 어댑터를 가질 수 있습니다.
메시지를 읽는 방식은 동일 제조사의 판독기와 태그가 필요로 하는 독점 기술을 통해 발생할 수 있습니다. 또한 이러한 방식은 NDEF 메시지를 노출할 수도 있습니다.
현재 명세상으로는 peer-to-peer는 지원되지 않습니다。
NFC device는 NFC peer 또는 NFC tag입니다。
NDEF는 NFC Forum Data Exchange Format의 약어로, [[!NFC-NDEF]]에 표준화된 경량 이진 메시지 형식입니다。
NDEF message는 하나 이상의 애플리케이션 정의 NDEF record를 캡슐화합니다. NDEF 메시지는 NFC tag에 저장되거나 NFC 지원 장치 간에 교환될 수 있습니다。
용어 NFC content는 NFC tag로 전송되거나 그로부터 수신되는 모든 바이트를 나타냅니다. 현재 API에서 이는 NDEF message와 동의어입니다。
NFC는 NFC Forum에서 표준화되며 [[NFC-STANDARDS]]에 설명되어 있습니다۔
NFC Forum은 NFC 장치와 함께 동작하기 위해 다섯 가지 서로 다른 태그 유형의 지원을 의무화했습니다. 운영 체제(예: 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의 일부입니다. 각 레코드는 데이터 페이로드와 관련 타입 정보를 포함하는 이진 구조입니다. 이 외에도 페이로드 크기, 데이터가 여러 레코드에 걸쳐 청크화되어 있는지 등의 데이터 구조에 관한 정보를 포함합니다。
처음 세 바이트(도형의 줄)만 필수입니다. 첫째는 헤더 바이트, 그 다음이 TYPE LENGTH field와 PAYLOAD LENGTH field이며, 둘 다 0일 수 있습니다。
| TNF 값 | 설명 |
|---|---|
| 0 | 빈 레코드 |
| 1 | NFC Forum [=well-known type record=] |
| 2 | MIME 타입 레코드 |
| 3 | 절대-URL 레코드 |
| 4 | NFC Forum 외부 타입 레코드 |
| 5 | 알 수 없는 레코드 |
| 6 | 변경되지 않은 레코드 |
| 7 | 미래 사용을 위해 예약됨 |
IL field(비트 `3`, id 길이)는 ID LENGTH field가 존재하는지를 나타냅니다. IL field가 `0`이면 ID field도 존재하지 않습니다。
SR field(비트 `4`, short record)는 페이로드 길이가 `255` 바이트 이하인 짧은 레코드를 나타냅니다. 일반 레코드는 페이로드 길이가 `255` 바이트를 초과하여 최대 `4` GB까지 가능할 수 있습니다. 짧은 레코드는 길이를 표시하기 위해 한 바이트만 사용하고, 일반 레코드는 길이를 표시하기 위해 `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는 TNF field의 값에 의해 결정되는 구조, 인코딩 및 형식을 통해 PAYLOAD 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`이면 그 크기는 한 바이트이고, 그렇지 않으면 4바이트로, 각각 8비트 또는 32비트 부호 없는 정수를 나타냅니다。
PAYLOAD field는 애플리케이션 바이트를 담습니다. 데이터의 내부 구조는 NDEF에 대해 불투명합니다. 이후 논의되는 특정 경우에는 이 필드가 데이터로서 NDEF message를 포함할 수도 있습니다。
빈 레코드의 TYPE LENGTH field, ID LENGTH field 및 PAYLOAD LENGTH field는 `0`이어야 하며, 따라서 TYPE field, ID field 및 PAYLOAD field는 존재해서는 안 됩니다。
NFC Forum은 [[NFC-RTD]](리소스 타입 정의 사양)에서 텍스트, URL, 미디어 등과 같은 유용한 하위 레코드 타입의 소규모 집합을 표준화했습니다. 또한 스마트 포스터(선택적 임베디드 레코드 포함: url, text, signature 및 actions)와 핸오버 레코드와 같은 보다 복잡한 상호작용을 위해 설계된 레코드 타입도 있습니다。
well-known type records의 TYPE field에 저장된 타입 정보는 두 가지 종류일 수 있습니다: 로컬 타입과 글로벌 타입입니다。
NFC Forum의 로컬 타입은 NFC Forum 또는 애플리케이션에 의해 정의되며, 항상 소문자 문자나 숫자로 시작합니다. 이들은 보통 포함된 레코드의 로컬 컨텍스트 내에서만 고유한 짧은 문자열입니다. 타입의 의미가 포함된 레코드의 로컬 컨텍스트 밖에서는 중요하지 않고 저장 공간이 제한된 경우에 사용됩니다. 로컬 타입이 어떻게 사용되는지에 대한 예는 Smart poster를 참조하십시오。
[=로컬 타입=]은 따라서 포함 레코드 타입의 관점에서 정의되며 네임스페이스가 필요하지 않습니다. 이런 이유로 동일한 로컬 타입 이름이 다른 레코드 타입 내에서 다른 의미와 다른 페이로드 타입으로 사용될 수 있습니다。
NFC Forum의 글로벌 타입은 NFC Forum에 의해 정의되고 관리되며 보통 대문자 문자로 시작합니다. 예: 텍스트는 "`T`", URL은 "`U`", 스마트 포스터는 "`Sp`", 서명은 "`Sig`", 핸오버 캐리어는 "`Hc`", 핸오버 요청은 "`Hr`", 핸오버 선택은 "`Hs`" 등。
URI 레코드는 [[NDEF-URI]]에 정의되어 있습니다. TNF field는 `1`이고 TYPE field는 "`U`"(`0x55`)입니다. PAYLOAD field의 첫 바이트는 URI 식별자 코드로, 약어 테이블의 인덱스이며 값들이 URI의 나머지 앞에 덧붙여집니다. 예를 들어 값 `0`은 덧붙임이 없음을 나타내고, `1`은 "`http://www.`", `0x04`는 "`https://`" 등을 나타냅니다. 페이로드의 나머지 부분은 나머지 URI를 UTF-8 문자열로 포함합니다(첫 바이트가 `0`이면 전체 URI를 의미합니다).
URI는 [[RFC3987]]에 정의되어 있으며 실제로 URN 또는 URL이 될 수 있는 UTF-8 인코딩 IRI입니다。
[[NDEF-SMARTPOSTER]]는 애플리케이션이 동일한 NDEF message에 추가 URI records가 포함되어 있을 때 smart poster 레코드만을 사용해야 한다고 규정합니다。
| 값 | 설명 |
|---|---|
| 0 | 행동을 수행 |
| 1 | 나중을 위해 저장 |
| 2 | 편집을 위해 열기 |
| 3..0xFF | 미래 사용을 위해 예약됨 |
action record가 없으면 smart poster 콘텐츠에 대한 기본 동작은 없습니다。
NDEF 표준화 당시 값 `0`("행동 수행")은 SMS 전송, 통화 또는 브라우저 실행과 같은 사용 사례를 의도했습니다. 유사하게 값 `1`("나중을 위해 저장")은 SMS를 받은편지함에 저장, URL을 북마크에 저장 또는 전화번호를 연락처에 저장 등의 사용 사례를 의도했습니다. 또한 값 `2`("편집을 위해 열기")는 스마트 포스터 콘텐츠를 편집을 위한 기본 애플리케이션으로 열도록 의도되었습니다。
구현은 여기서 정의된 동작에 대해 표준화된 동작을 구현할 필요가 없습니다. 이 API에서는 애플리케이션이 어떤 동작을 정의할지(위의 사용 사례를 포함할 수 있음)를 결정합니다. 다만 Web NFC는 값들만 제공합니다。
NDEF Signature는 [[NDEF-SIGNATURE]]에 정의되어 있습니다。 그 TYPE field는 "`Sig`"(`0x53`, `0x69`, `0x67`)를 포함하고 PAYLOAD field는 버전, 서명 및 인증서 체인을 포함합니다。
현재 명세상에서는 이것이 지원되지 않습니다。
NFC handover는 [[NFC-HANDOVER]]에 정의되어 있으며 Bluetooth 또는 WiFi와 같은 대체 통신 매체의 협상 및 활성화를 허용하는 해당 메시지 구조를 포함합니다。 협상된 통신 매체는 이후(별도로) 두 장치 간에 사진 전송, Bluetooth 프린터로의 인쇄 또는 텔레비전으로의 비디오 스트리밍과 같은 특정 활동을 수행하는 데 사용될 것입니다。
현재 명세상으로는 이것이 지원되지 않습니다。
절대-URL 레코드에서 TYPE field는 페이로드가 아니라 절대-URL 문자열을 포함합니다。
주: 일부 플랫폼(예: Windows Phone)은 페이로드에 추가 데이터를 저장했지만, 이러한 레코드의 모든 페이로드 데이터는 Android와 같은 다른 플랫폼에서는 무시됩니다. Android에서 이러한 레코드를 읽으면 Chrome에서 URL을 로드하려 시도하며, 클라이언트 애플리케이션용으로 의도된 것은 아닙니다。
NFC Forum의 외부 타입 레코드는 애플리케이션 지정 데이터 타입을 위한 것이며 [[[NFC-RTD]]]에 정의되어 있습니다。
외부 타입은 접두사 `"urn:nfc:ext:"` 뒤에 소유자 [=도메인=] 이름을 붙이고 `U+003A` (`:`)를 추가한 URN이며, 그 뒤에 0이 아닌 타입 이름이 옵니다. 예: `"urn:nfc:ext:w3.org:atype"`는 TYPE field에 `"w3.org:atype"`로 저장됩니다。
알 수 없는 레코드는 관련 MIME type 없이 불투명한 데이터를 저장하는 레코드로, 기본 MIME type로 `application/octet-stream`을 가정할 수 있습니다. [[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 태그에 대한 쓰기 작업은 항상 읽기 작업도 수반된다는 점에 유의하십시오.
NFC tag를 영구적으로 읽기 전용으로 만드는 작업은 항상 읽기 작업을 수반한다는 점에 유의하십시오.
사용자는 내장 어댑터 외에 하나 이상의 외부 NFC adapter를 디바이스에 연결할 수 있습니다. 사용자는 어느 NFC adapter든 사용할 수 있습니다.
이 섹션은 개발자가 이 사양의 다양한 기능을 어떻게 활용할 수 있는지 보여줍니다.
Web NFC 지원 여부는 {{NDEFReader}} 객체를 확인하여 감지할 수 있습니다. 이 확인이 NFC 하드웨어의 사용 가능성을 보장하지는 않는다는 점에 유의하십시오.
if ("NDEFReader" in window) { /* Scan and write NDEF Tags */ }
데이터 쓰기는 일반적으로 간단하지만, NFC에서의 쓰기 동작 방식에는 몇 가지 주의점이 있습니다.
NFC 리더는 폴링(polling) 방식으로 동작하므로 태그에 쓰거나 영구적으로 읽기 전용으로 만들려면 먼저 태그를 찾아 읽어야 하며, 이는 폴링을 먼저 초기화해야 함을 의미합니다.
만약 `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.");
};
쓰려면 태그를 찾아 읽어야 합니다. 이를 통해 기존 데이터나 일련번호를 확인하여 실제로 쓰고자 하는 태그인지 판단할 수 있습니다.
이런 이유로 `reading` 이벤트에서 `write()`를 호출하는 것이 권장됩니다. `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 records를 포함하는 NDEF message를 페이로드로 가질 수 있습니다. 이때 애플리케이션 컨텍스트에서 사용되는 local types를 포함할 수 있습니다.
스마트 포스터 레코드 타입도 페이로드로 NDEF message를 포함한다는 점에 유의하십시오.
NDEF는 레코드의 순서를 보장하지 않으므로, 관련 데이터를 캡슐화하기 위해 페이로드로 NDEF message를 갖는 외부 타입 레코드를 사용하는 것이 유용할 수 있습니다.
다음 예제는 텍스트 레코드와 스마트 포스터에서 차용한 정의의 로컬 타입 "act"(액션)를 포함하는 NDEF message를 페이로드로 갖는 소셜 포스트용 외부 레코드를 읽는 방법을 보여줍니다.
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 타입을 지정하지 않아도 되는 경우, 외부 타입 레코드 내부에 unknown 타입 레코드를 사용하는 것이 유용할 수 있습니다.
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 속성은 NDEF message를 정의하는 list의 NDEF record들을 나타냅니다.
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 속성은 NDEF record 페이로드의 MIME type을 나타냅니다.
recordType 속성은 NDEF record 타입을 나타냅니다.
NFC NDEF 사양은 record identifier 대신 "message identifier"와 "payload identifier"라는 용어를 사용하지만, 식별자는 메시지(레코드들의 집합)가 아니라 각 레코드에 연결되며 페이로드가 없을 때도 존재할 수 있습니다.
encoding 속성은 페이로드가 텍스트 데이터인 경우에 페이로드 인코딩에 사용된 [=encoding/name|encoding name=]을 나타냅니다.
lang 속성은 페이로드가 인코딩된 경우의 [=language tag=]를 나타냅니다.
language tag는 [[BCP47]] 사양에 정의된 Language-Tag 생성 규칙과 일치하는 string입니다
(가능한 값의 권위 있는 목록은 IANA Language
Subtag Registry 참조).
즉, 언어 범위는 하나 이상의 subtags로 구성되며 U+002D HYPHEN-MINUS("-")로 구분됩니다.
예를 들어 '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의 허용되는 레코드 타입을 정의합니다. [[[#data-mapping]]] 섹션은 이것이 NDEF record 타입으로 어떻게 매핑되는지 설명합니다.
표준화된 well known type name은 다음 중 하나일 수 있습니다:
[=well known type names=] 외에도 조직이 커스텀 external type name을 생성할 수 있습니다. 이는 도메인 이름과 커스텀 타입 이름을 콜론 `U+003A` (`:`)로 구분하여 이어붙인 문자열입니다.
애플리케이션은 또한 local type name을 사용할 수 있으며, 이는 반드시 소문자 문자나 숫자로 시작해야 하는 문자열로, NFC Forum의 [=local type=]에 대한 타입을 나타냅니다. 이는 일반적으로 상위 NDEFRecord의 페이로드인 NDEFMessage 내의 레코드에서 사용됩니다(예: smart poster). local type의 컨텍스트는 이 레코드가 속한 페이로드인 NDEFMessage의 부모 레코드이며, local type name은 해당 컨텍스트에서 사용되는 다른 타입 이름들과 충돌하지 않아야 합니다.
Web NFC의 어떤 구현도 청크된 레코드를 단일 논리 레코드로 투명하게 노출해야 하므로, unchanged record는 명시적으로 표현되지 않습니다.
두 개의 well-known type records (모든 NFC Forum의 local type 및 global type 포함)는 문자 단위로 대소문자 구분 방식으로 비교되어야 합니다.
두 개의 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 record인 "`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로의 매핑은 [[[#steps-receiving]]] 섹션에 설명된 대로 다음과 같습니다.
| 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}} 객체는 다음과 같은 내부 슬롯을 가집니다:
| 내부 슬롯 | 초기값 | 설명 (비규범적) |
|---|---|---|
| [[\WriteOptions]] | null | 쓰기를 위한 {{NDEFWriteOptions}} 값. |
| [[\WriteMessage]] | null | 쓰여질 {{NDEFMessage}}. 초기에는 설정되지 않음. |
onreading은 새로운 판독이 가능함을 알리기 위해 호출되는 {{EventHandler}}입니다.
onreadingerror는 판독 중 오류가 발생했음을 알리기 위해 호출되는 {{EventHandler}}입니다.
NFC를 지원하는 browsing context의 active document의 relevant settings object에는 다음의 내부 슬롯을 갖는 연관된 NFC state 레코드가 있습니다:
| 내부 슬롯 | 초기값 | 설명 (비규범적) |
|---|---|---|
| [[\Suspended]] | false | NFC 기능이 일시 중단되었는지를 나타내는 불리언 플래그로, 초기값은 false입니다. |
| [[\ActivatedReaderList]] | 비어 있는 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}}에도 제공되지 않습니다.
프라미스를 거부하면 pending write tuple이 정리됩니다.
프라미스를 거부하면 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이면, 쓰기 알고리즘은 NFC tag를 읽어 그 위에 NDEF 레코드가 있는지 확인하며, 있다면 대기 중인 쓰기를 실행하지 않습니다.
signal 속성은 {{NDEFReader/write()}} 작업을 중단할 수 있도록 합니다.
dictionary NDEFMakeReadOnlyOptions {
AbortSignal? signal;
};
signal 속성은 {{NDEFReader/makeReadOnly()}} 작업을 중단할 수 있도록 합니다.
dictionary NDEFScanOptions {
AbortSignal signal;
};
signal 속성은 {{NDEFReader/scan()}} 작업을 중단할 수 있도록 합니다.
이 섹션은 타이머가 만료되기 전에 다음으로 근접 범위에 들어왔을 때 NFC tag에 NDEF message를 쓰는 방법을 설명합니다. 어느 시점이든 현재 메시지가 전송되거나 쓰기가 중단될 때까지 하나의 origin에 대해 쓰기로 설정될 수 있는 NDEF message는 최대 하나뿐입니다.
UA는 이 시점에서 메시지 쓰기를 중단할 수 있습니다. 종료 사유는 구현 상세에 따릅니다. 예를 들어, 구현이 요청된 작업을 지원할 수 없을 수 있습니다.
쓰기는 이전에 설정된 모든 쓰기 작업을 대체합니다.
NFC is suspended인 경우, 사용자가 프라미스를 중단하거나 NFC tag가 통신 범위에 들어올 때까지 계속 대기합니다.
근접 범위의 NFC tag가 비포맷 상태이고 NDEF 포맷이 가능하면, 포맷한 뒤 |output|을 버퍼로써 기록합니다.
여러 어댑터는 사용자가 순차적으로 사용하는 것이 좋습니다. 서로 다른 연결된 NFC adapter 두 개 이상에서 동시에 탭이 발생할 가능성은 매우 낮습니다. 만약 발생한다면, 사용자는 바람직하게는 한 번에 하나의 장치에 대해 성공할 때까지 탭을 반복해야 할 수 있습니다. 여기서의 오류는 작업을 반복할 필요가 있음을 나타냅니다. 그렇지 않으면 사용자가 모든 연결된 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:nfc:ext:`” URN 접두사를 규정하지만, 이는 NDEF record에 저장되지 않으므로 Web NFC 애플리케이션은 external type records를 생성할 때 URN 접두사를 지정하지 않아야 합니다.
[[NFC-RTD]]는 예를 들어 NDEF messages를 읽을 때처럼 외부 타입 이름이 “`urn:nfc:ext:`” URN 접두사로 표현될 것을 요구합니다. 그러나 external type records는 TNF FIELD가 `0x04`로 설정되어 구분되므로, 타입 이름 충돌에 대한 위험은 보이지 않습니다. 또한 웹에서 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만 허용하며, 이는 단일 URI record여야 합니다.
이 섹션은 근접 범위에 있을 때 NFC tag를 영구적으로 읽기 전용으로 만드는 방법을 설명합니다. 어느 시점이든 NFC tag가 영구적으로 읽기 전용으로 되거나 작업이 중단될 때까지 하나의 origin에 대해 최대 하나의 요청만 존재할 수 있습니다.
UA는 이 시점에서 작업을 중단할 수 있습니다. 종료 사유는 구현 상세에 따릅니다. 예를 들어, 구현이 요청된 작업을 지원할 수 없을 수 있습니다.
읽기 전용 설정 작업은 이전에 설정된 모든 읽기 전용 설정 작업을 대체합니다.
NFC is suspended인 경우, 사용자가 프라미스를 중단하거나 NFC tag가 통신 범위에 들어올 때까지 계속 대기합니다.
이 작업은 일방향이며 되돌릴 수 없습니다. NFC 태그가 읽기 전용으로 설정되면 더 이상 쓸 수 없습니다.
NFC content를 수신하려면, 클라이언트는 NDEFReader.scan()을 호출하여 {{NDEFReader}} 인스턴스를 활성화해야 합니다. 해당 인스턴스에 "`reading`" 이벤트 리스너를 부착하면, NFC content에 접근할 수 있습니다.
activated reader objects 안에 어떤 {{NDEFReader}} 인스턴스라도 있다면, UA는 연결된 모든 NFC 어댑터에서 NDEF message를 수신 대기해야 합니다.
들어오는 NFC content는 {{NDEFReader}} 인스턴스를 사용하여 매칭됩니다.
UA는 비포맷 NFC tag를 NDEF record가 없는 NDEF message로 표현해야 하며, 즉 {{NDEFMessage/records}} 속성에 대해 빈 배열입니다.
serialNumber 타입의 |serialNumber:serialNumber|와 NDEFMessage 타입의 |message:NDEFMessage|가 주어졌을 때 dispatch NFC content를 수행하려면, 다음 단계들을 실행합니다:
청크된 레코드는 하위 레코드로 허용되지 않으므로, 비트 5 (CF field)는 무시됩니다.
애플리케이션은 data에 대해 toRecords()를 호출해
이를 NDEF records로 파싱하거나, 직접 파싱할 수 있습니다.
애플리케이션은 이 값을 32비트 부호 없는 정수로 파싱하여, smart-poster의 URI 레코드가 참조하는 객체의 크기를 나타내는 값으로 사용할 수 있습니다.
애플리케이션은 이를 [[RFC2048]] 미디어 타입을 담은 문자열로 파싱하여, smart-poster의 URI 레코드가 참조하는 객체의 미디어 타입을 나타내게 할 수 있습니다.
애플리케이션은 이 값을 8비트 부호 없는 정수로 파싱할 수 있으며, 값의 정의는 여기에 있습니다.
이 규격은 웹사이트가 접근할 수 있는 NFC 기기 집합을 제한하기 위해 차단 목록 파일에 의존합니다.
|url:URL|에서 차단 목록을 파싱한 결과는 다음 알고리즘으로 생성된 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 코드를 읽으려면 이미지(카메라)를 캡처하는 방해적 UI가 사용되며, 이 이미지의 내용(QR 코드 포함)이 웹 페이지에 제공됨을 사용자에게 명확히 하여 스캔이 수행되고 있음을 분명히 합니다.
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에서는 지원되지 않으며 지원되는 네이티브 플랫폼에서도 현재 폐기(deprecate) 중입니다.
더 최신 프로토콜인 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는 한 번에 오직 하나의 태그만 읽을 수 있지만, 여러 태그를 감지하고 그중 하나를 통신할 태그로 선택할 수 있습니다.
이 사용 사례는 지갑 안에 여러 스마트 카드(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 해야 합니다. NFC 관련 권한을 구현하기 위해 UA가 [[[PERMISSIONS]]] API를 사용하는 것이 제안됩니다.
구현은 세션별/휘발성 권한을 사용할 수 있습니다.
구현은 웹 페이지가 NFC 어댑터에 접근할 때마다(예: 스캔이 진행 중일 때) 오버레이 대화상자를 표시하여 사용자에게 경고할 수 있습니다.
NFC를 통해 교환되는 데이터의 기밀성을 신뢰하려면, 애플리케이션은 공개키 기반구조(PKI)에 기반한 키 관리와 함께 암호화된 NFC content를 사용할 수 있습니다. 키 관리는 Web NFC의 범위를 벗어납니다.
NFC를 통해 교환되는 데이터의 무결성을 신뢰하려면, 사용자 에이전트는 키 관리에 공개키 기반구조를 사용하는 NDEF signature를 사용할 수 있습니다.
NDEF signature 버전 1.0([[NFC-SECURITY]])으로 서명된 태그의 경우, 서명은 NDEF 헤더의 첫 바이트를 제외하고 TYPE field, ID field, PAYLOAD field에만 적용되어 공격 표면을 남깁니다. [[NFC-SECURITY]] 버전 2.0은 서명에 태그 하드웨어 속성을 포함하고 더 짧은 인증서를 허용합니다.
하나의 NDEF signature는 이후에 또 다른 NDEF signature가 나타나거나 NDEF message의 시작에 도달할 때까지 앞선 레코드를 포괄합니다.
알려진 취약성을 완화하기 위해, 애플리케이션이 항상 단일 NDEF signature로 전체 NDEF message에 서명하고, 서명 생성 및 검증을 위한 적절한 도구 체인과 보안 정책을 사용할 것을 권장합니다.
이 섹션은 구현을 위한 규범적 보안 정책을 나열합니다.
오직 secure contexts만이 NFC content에 접근할 수 있습니다. 브라우저는 개발 목적에 한해 이 규칙을 무시할 수 있습니다.
Web NFC 기능은 top-level browsing context의 {{Document}}에서만 허용되며, 해당 {{Document/visibilityState}}는 `"visible"`이어야 합니다.
이는 또한 디스플레이가 꺼져 있거나 장치가 잠겨 있는 경우 UA가 NFC 라디오 접근을 차단해야 함을 의미합니다. 백그라운드 웹 페이지의 경우, NFC content 수신 및 쓰기는 suspended 되어야 합니다.
NFC tag를 영구적으로 읽기 전용으로 만드는 작업은 반드시 obtain permission 해야 하며, 그렇지 않으면 실패해야 합니다. [[[#making-content-read-only]]] 섹션을 참조하세요.
NFC content 읽기를 위한 리스너 설정은 obtain permission 하는 것이 좋습니다.
NFC content를 NFC tag에 쓰는 작업은 반드시 obtain permission 해야 합니다. [[[#writing-content]]] 섹션을 참조하세요.
현재 탐색 세션을 넘어 보존되는 모든 권한은 반드시 철회 가능해야 합니다.
Web NFC는 취약한 NFC 장치에 대해 웹사이트가 이를 악용하지 못하도록 blocklist를 포함합니다.
NFC content를 수신하거나 기록할 때, UA는 해당 origin이 물리적 위치를 추론할 수 있음을 사용자에게 경고할 수 있습니다.
NFC content의 페이로드 데이터가 신뢰할 수 없을 때, UA는 해당 콘텐츠의 자동 처리(예: NFC tag에서 발견된 URL로 웹 페이지 열기, 애플리케이션 설치 또는 기타 동작)를 사용자 승인 없이 수행해서는 안 됩니다.
편집자는 이 문서에 기여한 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에게 특별한 감사를 전합니다.