UNPKG

ndef

Version:

Library to create and parse NDEF messages.

565 lines (480 loc) 16.9 kB
// ndef.js // Copyright 2013 Don Coleman // // This code is from phonegap-nfc.js https://github.com/don/phonegap-nfc var util = require('./ndef-util'), textHelper = require('./ndef-text'), uriHelper = require('./ndef-uri'); var ndef = { // see android.nfc.NdefRecord for documentation about constants // http://developer.android.com/reference/android/nfc/NdefRecord.html TNF_EMPTY: 0x0, TNF_WELL_KNOWN: 0x01, TNF_MIME_MEDIA: 0x02, TNF_ABSOLUTE_URI: 0x03, TNF_EXTERNAL_TYPE: 0x04, TNF_UNKNOWN: 0x05, TNF_UNCHANGED: 0x06, TNF_RESERVED: 0x07, RTD_TEXT: "T", // [0x54] RTD_URI: "U", // [0x55] RTD_SMART_POSTER: "Sp", // [0x53, 0x70] RTD_ALTERNATIVE_CARRIER: "ac", //[0x61, 0x63] RTD_HANDOVER_CARRIER: "Hc", // [0x48, 0x63] RTD_HANDOVER_REQUEST: "Hr", // [0x48, 0x72] RTD_HANDOVER_SELECT: "Hs", // [0x48, 0x73] /** * Creates a JSON representation of a NDEF Record. * * @tnf 3-bit TNF (Type Name Format) - use one of the TNF_* constants * @type byte array, containing zero to 255 bytes, must not be null * @id byte array, containing zero to 255 bytes, must not be null * @payload byte array, containing zero to (2 ** 32 - 1) bytes, must not be null * * @returns JSON representation of a NDEF record * * @see Ndef.textRecord, Ndef.uriRecord and Ndef.mimeMediaRecord for examples */ record: function (tnf, type, id, payload) { // handle null values if (!tnf) { tnf = ndef.TNF_EMPTY; } if (!type) { type = []; } if (!id) { id = []; } if (!payload) { payload = []; } // store type as String so it's easier to compare if(type instanceof Array) { type = util.bytesToString(type); } // in the future, id could be a String if (!(id instanceof Array)) { id = util.stringToBytes(id); } // Payload must be binary if (!(payload instanceof Array)) { payload = util.stringToBytes(payload); } var record = { tnf: tnf, type: type, id: id, payload: payload }; // Experimental feature // Convert payload to text for Text and URI records if (tnf === ndef.TNF_WELL_KNOWN) { switch(record.type) { case ndef.RTD_TEXT: record.value = ndef.text.decodePayload(record.payload); break; case ndef.RTD_URI: record.value = ndef.uri.decodePayload(record.payload); break; } } return record; }, /** * Helper that creates an NDEF record containing plain text. * * @text String of text to encode * @languageCode ISO/IANA language code. Examples: “fi”, “en-US”, “fr-CA”, “jp”. (optional) * @id byte[] (optional) */ textRecord: function (text, languageCode, id) { var payload = textHelper.encodePayload(text, languageCode); if (!id) { id = []; } return ndef.record(ndef.TNF_WELL_KNOWN, ndef.RTD_TEXT, id, payload); }, /** * Helper that creates a NDEF record containing a URI. * * @uri String * @id byte[] (optional) */ uriRecord: function (uri, id) { var payload = uriHelper.encodePayload(uri); if (!id) { id = []; } return ndef.record(ndef.TNF_WELL_KNOWN, ndef.RTD_URI, id, payload); }, /** * Helper that creates a NDEF record containing an absolute URI. * * An Absolute URI record means the URI describes the payload of the record. * * For example a SOAP message could use "http://schemas.xmlsoap.org/soap/envelope/" * as the type and XML content for the payload. * * Absolute URI can also be used to write LaunchApp records for Windows. * * See 2.4.2 Payload Type of the NDEF Specification * http://www.nfc-forum.org/specs/spec_list#ndefts * * Note that by default, Android will open the URI defined in the type * field of an Absolute URI record (TNF=3) and ignore the payload. * BlackBerry and Windows do not open the browser for TNF=3. * * To write a URI as the payload use ndef.uriRecord(uri) * * @uri String * @payload byte[] or String * @id byte[] (optional) */ absoluteUriRecord: function (uri, payload, id) { if (!id) { id = []; } if (!payload) { payload = []; } return ndef.record(ndef.TNF_ABSOLUTE_URI, uri, id, payload); }, /** * Helper that creates a NDEF record containing an mimeMediaRecord. * * @mimeType String * @payload byte[] * @id byte[] (optional) */ mimeMediaRecord: function (mimeType, payload, id) { if (!id) { id = []; } return ndef.record(ndef.TNF_MIME_MEDIA, mimeType, id, payload); }, /** * Helper that creates an NDEF record containing an Smart Poster. * * @ndefRecords array of NDEF Records * @id byte[] (optional) */ smartPoster: function (ndefRecords, id) { var payload = []; if (!id) { id = []; } if (ndefRecords) { // make sure we have an array of something like NDEF records before encoding if (ndefRecords[0] instanceof Object && ndefRecords[0].hasOwnProperty('tnf')) { payload = ndef.encodeMessage(ndefRecords); } else { // assume the caller has already encoded the NDEF records into a byte array payload = ndefRecords; } } else { console.log("WARNING: Expecting an array of NDEF records"); } return ndef.record(ndef.TNF_WELL_KNOWN, ndef.RTD_SMART_POSTER, id, payload); }, /** * Helper that creates an empty NDEF record. * */ emptyRecord: function() { return ndef.record(ndef.TNF_EMPTY, [], [], []); }, /** * Helper that creates an Android Application Record (AAR). * http://developer.android.com/guide/topics/connectivity/nfc/nfc.html#aar * */ androidApplicationRecord: function(packageName) { return ndef.record(ndef.TNF_EXTERNAL_TYPE, "android.com:pkg", [], packageName); }, /** * Encodes an NDEF Message into bytes that can be written to a NFC tag. * * @ndefRecords an Array of NDEF Records * * @returns byte array * * @see NFC Data Exchange Format (NDEF) http://www.nfc-forum.org/specs/spec_list/ */ encodeMessage: function (ndefRecords) { var encoded = [], tnf_byte, record_type, payload_length, id_length, i, mb, me, // messageBegin, messageEnd cf = false, // chunkFlag TODO implement sr, // boolean shortRecord il; // boolean idLengthFieldIsPresent for(i = 0; i < ndefRecords.length; i++) { mb = (i === 0); me = (i === (ndefRecords.length - 1)); sr = (ndefRecords[i].payload.length < 0xFF); il = (ndefRecords[i].id.length > 0); tnf_byte = ndef.encodeTnf(mb, me, cf, sr, il, ndefRecords[i].tnf); encoded.push(tnf_byte); // type is stored as String, converting to bytes for storage record_type = util.stringToBytes(ndefRecords[i].type); encoded.push(record_type.length); if (sr) { payload_length = ndefRecords[i].payload.length; encoded.push(payload_length); } else { payload_length = ndefRecords[i].payload.length; // 4 bytes encoded.push((payload_length >> 24)); encoded.push((payload_length >> 16)); encoded.push((payload_length >> 8)); encoded.push((payload_length & 0xFF)); } if (il) { id_length = ndefRecords[i].id.length; encoded.push(id_length); } encoded = encoded.concat(record_type); if (il) { encoded = encoded.concat(ndefRecords[i].id); } encoded = encoded.concat(ndefRecords[i].payload); } return encoded; }, /** * Decodes an array bytes into an NDEF Message * * @ndefBytes an array bytes or Buffer that was read from a NFC tag * * @returns array of NDEF Records * * @see NFC Data Exchange Format (NDEF) http://www.nfc-forum.org/specs/spec_list/ */ decodeMessage: function (ndefBytes) { // ndefBytes can be an array of bytes e.g. [0x03, 0x31, 0xd1] or a Buffer var bytes; if (ndefBytes instanceof Buffer) { // get an array of bytes bytes = Array.prototype.slice.call(ndefBytes, 0); } else if (ndefBytes instanceof Array) { bytes = ndefBytes.slice(0); } else { throw new Error('ndef.decodeMessage requires a Buffer or an Array of bytes'); } var bytes = bytes.slice(0), // clone since parsing is destructive ndef_message = [], tnf_byte, header, type_length = 0, payload_length = 0, id_length = 0, record_type = [], id = [], payload = []; while(bytes.length) { tnf_byte = bytes.shift(); header = ndef.decodeTnf(tnf_byte); type_length = bytes.shift(); if (header.sr) { payload_length = bytes.shift(); } else { // next 4 bytes are length payload_length = ((0xFF & bytes.shift()) << 24) | ((0xFF & bytes.shift()) << 16) | ((0xFF & bytes.shift()) << 8) | (0xFF & bytes.shift()); } if (header.il) { id_length = bytes.shift(); } record_type = bytes.splice(0, type_length); id = bytes.splice(0, id_length); payload = bytes.splice(0, payload_length); ndef_message.push( ndef.record(header.tnf, record_type, id, payload) ); if (header.me) break; // last message } return ndef_message; }, /** * Decode the bit flags from a TNF Byte. * * @returns object with decoded data * * See NFC Data Exchange Format (NDEF) Specification Section 3.2 RecordLayout */ decodeTnf: function (tnf_byte) { return { mb: (tnf_byte & 0x80) !== 0, me: (tnf_byte & 0x40) !== 0, cf: (tnf_byte & 0x20) !== 0, sr: (tnf_byte & 0x10) !== 0, il: (tnf_byte & 0x8) !== 0, tnf: (tnf_byte & 0x7) }; }, /** * Encode NDEF bit flags into a TNF Byte. * * @returns tnf byte * * See NFC Data Exchange Format (NDEF) Specification Section 3.2 RecordLayout */ encodeTnf: function (mb, me, cf, sr, il, tnf) { var value = tnf; if (mb) { value = value | 0x80; } if (me) { value = value | 0x40; } // note if cf: me, mb, li must be false and tnf must be 0x6 if (cf) { value = value | 0x20; } if (sr) { value = value | 0x10; } if (il) { value = value | 0x8; } return value; }, // TODO test with byte[] and string isType: function(record, tnf, type) { if (record.tnf === tnf) { return (s(record) === s(type)); } return false; } }; function tnfToString(tnf) { var value = tnf; switch (tnf) { case ndef.TNF_EMPTY: value = "Empty"; break; case ndef.TNF_WELL_KNOWN: value = "Well Known"; break; case ndef.TNF_MIME_MEDIA: value = "Mime Media"; break; case ndef.TNF_ABSOLUTE_URI: value = "Absolute URI"; break; case ndef.TNF_EXTERNAL_TYPE: value = "External"; break; case ndef.TNF_UNKNOWN: value = "Unknown"; break; case ndef.TNF_UNCHANGED: value = "Unchanged"; break; case ndef.TNF_RESERVED: value = "Reserved"; break; } return value; } // Convert NDEF records and messages to strings // This works OK for demos, but real code proably needs // a custom implementation. It would be nice to make // smarter record objects that can print themselves var stringifier = { stringify: function (data, separator) { if (Array.isArray(data)) { if (typeof data[0] === 'number') { // guessing this message bytes data = ndef.decodeMessage(data); } return stringifier.printRecords(data, separator); } else { return stringifier.printRecord(data, separator); } }, // @message - NDEF Message (array of NDEF Records) // @separator - line separator, optional, defaults to \n // @returns string with NDEF Message printRecords: function (message, separator) { if(!separator) { separator = "\n"; } result = ""; // Print out the payload for each record message.forEach(function(record) { result += stringifier.printRecord(record, separator); result += separator; }); return result.slice(0, (-1 * separator.length)); }, // @record - NDEF Record // @separator - line separator, optional, defaults to \n // @returns string with NDEF Record printRecord: function (record, separator) { var result = ""; if(!separator) { separator = "\n"; } switch(record.tnf) { case ndef.TNF_EMPTY: result += "Empty Record"; result += separator; break; case ndef.TNF_WELL_KNOWN: result += stringifier.printWellKnown(record, separator); break; case ndef.TNF_MIME_MEDIA: result += "MIME Media"; result += separator; result += s(record.type); result += separator; result += s(record.payload); // might be binary break; case ndef.TNF_ABSOLUTE_URI: result += "Absolute URI"; result += separator; result += s(record.type); // the URI is the type result += separator; result += s(record.payload); // might be binary break;; case ndef.TNF_EXTERNAL_TYPE: // AAR contains strings, other types could // contain binary data result += "External"; result += separator; result += s(record.type); result += separator; result += s(record.payload); break; default: result += s("Can't process TNF " + record.tnf); } result += separator; return result; }, printWellKnown: function (record, separator) { var result = ""; if (record.tnf !== ndef.TNF_WELL_KNOWN) { return "ERROR expecting TNF Well Known"; } switch(record.type) { case ndef.RTD_TEXT: result += "Text Record"; result += separator; result += (ndef.text.decodePayload(record.payload)); break; case ndef.RTD_URI: result += "URI Record"; result += separator; result += (ndef.uri.decodePayload(record.payload)); break; case ndef.RTD_SMART_POSTER: result += "Smart Poster"; result += separator; // the payload of a smartposter is a NDEF message result += stringifier.printRecords(ndef.decodeMessage(record.payload)); break; default: // attempt to display other types result += record.type + " Record"; result += separator; result += s(record.payload); } return result; } }; // convert bytes to a String function s(bytes) { return new Buffer(bytes).toString(); } // expose helper objects ndef.text = textHelper; ndef.uri = uriHelper; ndef.tnfToString = tnfToString; ndef.util = util; ndef.stringify = stringifier.stringify; module.exports = ndef;