├── .gitignore ├── LICENSE.txt ├── README.md ├── bower.json ├── dist ├── ndef.js └── ndef.min.js ├── gulpfile.js ├── package.json ├── src └── ndef.js └── test └── testparser.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/linux,windows,osx,git,node,bower 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | 17 | ### Windows ### 18 | # Windows image file caches 19 | Thumbs.db 20 | ehthumbs.db 21 | 22 | # Folder config file 23 | Desktop.ini 24 | 25 | # Recycle Bin used on file shares 26 | $RECYCLE.BIN/ 27 | 28 | # Windows Installer files 29 | *.cab 30 | *.msi 31 | *.msm 32 | *.msp 33 | 34 | # Windows shortcuts 35 | *.lnk 36 | 37 | 38 | ### OSX ### 39 | *.DS_Store 40 | .AppleDouble 41 | .LSOverride 42 | 43 | # Icon must end with two \r 44 | Icon 45 | 46 | 47 | # Thumbnails 48 | ._* 49 | 50 | # Files that might appear in the root of a volume 51 | .DocumentRevisions-V100 52 | .fseventsd 53 | .Spotlight-V100 54 | .TemporaryItems 55 | .Trashes 56 | .VolumeIcon.icns 57 | .com.apple.timemachine.donotpresent 58 | 59 | # Directories potentially created on remote AFP share 60 | .AppleDB 61 | .AppleDesktop 62 | Network Trash Folder 63 | Temporary Items 64 | .apdisk 65 | 66 | 67 | ### Git ### 68 | *.orig 69 | 70 | 71 | ### Node ### 72 | # Logs 73 | logs 74 | *.log 75 | npm-debug.log* 76 | 77 | # Runtime data 78 | pids 79 | *.pid 80 | *.seed 81 | 82 | # Directory for instrumented libs generated by jscoverage/JSCover 83 | lib-cov 84 | 85 | # Coverage directory used by tools like istanbul 86 | coverage 87 | 88 | # nyc test coverage 89 | .nyc_output 90 | 91 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 92 | .grunt 93 | 94 | # node-waf configuration 95 | .lock-wscript 96 | 97 | # Compiled binary addons (http://nodejs.org/api/addons.html) 98 | build/Release 99 | 100 | # Dependency directories 101 | node_modules 102 | jspm_packages 103 | 104 | # Optional npm cache directory 105 | .npm 106 | 107 | # Optional REPL history 108 | .node_repl_history 109 | 110 | 111 | ### Bower ### 112 | bower_components 113 | .bower-cache 114 | .bower-registry 115 | .bower-tmp 116 | 117 | ### VIM ### 118 | *.swp 119 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | pache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) 2016. Papyrus Electronics, Inc 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NdefJS 2 | Library for creating and parsing NDEF messages. 3 | 4 | ## Installing 5 | ### Bower 6 | ``` 7 | bower install ndef 8 | ``` 9 | ### NPM 10 | ``` 11 | npm install @taptrack/ndef 12 | ``` 13 | 14 | ## Creating Records 15 | ### Text records 16 | ```javascript 17 | var ndef = require('@taptrack/ndef'); 18 | 19 | // language code is optional, defaults to 'en' 20 | var textRecord = ndef.Utils.createTextRecord("sirop","fr"); 21 | var message = new ndef.Message([textRecord]); 22 | 23 | // Uint8Array for storing, writing to a tag, etc. 24 | var bytes = message.toByteArray(); 25 | ``` 26 | ### URI records 27 | ```javascript 28 | // NDEF uri prefixes will be automatically applied 29 | var uriRecord = ndef.Utils.createUriRecord("http://www.google.com"); 30 | var message = new ndef.Message([uriRecord]); 31 | 32 | // Uint8Array for storing, writing to a tag, etc. 33 | var bytes = message.toByteArray(); 34 | ``` 35 | ### Custom records 36 | ```javascript 37 | // creating message to be carried inside smartposter record 38 | var uri = ndef.Utils.createUriRecord("http://www.google.com"); 39 | var payloadMsg = new ndef.Message([uri]); 40 | 41 | var chunked = false; // non-chunked record 42 | var tnf = new Uint8Array([0x01]); // TNF well known 43 | var type = new Uint8Array([0x53,0x70]); 44 | var payload = payloadMessage.toByteArray(); 45 | 46 | var spRecord = new ndef.Record(chunked, tnf, type, id, payload); 47 | var spMessage = new ndef.Message([record]); 48 | 49 | // Uint8Array for storing, writing to a tag, etc. 50 | var bytes = spMessage.toByteArray(); 51 | ``` 52 | 53 | ## Parsing messages 54 | ### Text records 55 | ```javascript 56 | var message = ndef.Message.fromBytes(byteArray); 57 | var records = message.getRecords(); 58 | 59 | var recordContents = ndef.Utils.resolveTextRecord(parsedRecords[0]); 60 | console.log("Language: " + recordContents.language); 61 | console.log("Content: " + recordContents.content); 62 | ``` 63 | 64 | ### URI records 65 | ```javascript 66 | var message = ndef.Message.fromBytes(byteArray); 67 | var parsedRecords = message.getRecords(); 68 | 69 | var uri = ndef.Utils.resolveUriRecordToString(parsedRecords[0]); 70 | console.log("URI: " + uri); 71 | ``` 72 | 73 | ### Other records 74 | ```javascript 75 | // takes a Uint8Array containing a valid, complete message 76 | var message = ndef.Message.fromBytes(byteArray); 77 | 78 | // array of all the records 79 | var records = message.getRecords(); 80 | 81 | for(var i = 0; i < records.length; i++) { 82 | var record = records[i]; 83 | console.log("Chunked: " + (record.isChunked ? "Yes" : "No")); 84 | console.log("TNF: " + record.getTnf().toString()); 85 | console.log("Type: " + record.getType().toString()); 86 | console.log("ID: " + record.getId().toString()); 87 | console.log("Payload: " + record.getPayload().toString()); 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ndef", 3 | "description": "Ndef message parsing and composition", 4 | "main": [ 5 | "dist/ndef.js" 6 | ], 7 | "authors": [ 8 | "TapTrack" 9 | ], 10 | "license": "Apache-2.0", 11 | "homepage": "", 12 | "ignore": [ 13 | "**/.*", 14 | "gulpfile.js", 15 | "node_modules", 16 | "bower_components", 17 | "test", 18 | "tests" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/TapTrack/NdefJS.git" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /dist/ndef.js: -------------------------------------------------------------------------------- 1 | 2 | (function (root, factory) { 3 | if (typeof define === 'function' && define.amd) { 4 | // AMD 5 | define([], factory); 6 | } else if (typeof exports === 'object') { 7 | module.exports = factory(); 8 | } else { 9 | // Browser globals (root is window) 10 | root.Ndef= factory(); 11 | } 12 | }(this, function () { 13 | /** 14 | * Utility functions used for working with NDEF 15 | * 16 | * @constructor 17 | * @throws if you attempt to intsantiate NdefUtils 18 | */ 19 | var NdefUtils = function() { 20 | throw "Dont construct NdefUtils!"; 21 | }; 22 | 23 | /** 24 | * Check if a value has a bit flag 25 | * 26 | * @param {!integer} value 27 | * @param {!integer} flag 28 | * @return {!boolean} true if flag is set, false otherwise 29 | */ 30 | NdefUtils.hasFlag = function(value,flag) { 31 | return (value & flag) === flag; 32 | }; 33 | 34 | /** 35 | * Get the type from this NDEF Record 36 | * 37 | * @constructor 38 | * @this {NdefRecord} 39 | * @param {!boolean} chunked if this record has a chunked flag 40 | * @param {!integer} tnf the Type Name Format for this record 41 | * @param {!Uint8Array} type the type data for this record 42 | * @param {?Uint8Array} id the id for this record, if present 43 | * @param {!payload} payload the payload contained in this record 44 | */ 45 | var NdefRecord = function (chunked,tnf,type,id,payload) { 46 | if(typeof chunked == "undefined" || 47 | typeof tnf == "undefined" || 48 | typeof type == "undefined" || 49 | typeof id == "undefined" || 50 | typeof payload == "undefined") { 51 | throw "Parameters missing from NdefRecord constructor call"; 52 | } 53 | 54 | if(chunked === null || 55 | tnf === null || 56 | type === null || 57 | payload === null) { 58 | throw "Passed null to non-nullable constructor parameter in NdefRecord"; 59 | } 60 | 61 | 62 | this.chunked = chunked; 63 | this.tnf = tnf; 64 | this.type = type; 65 | this.id = id; 66 | this.payload = payload; 67 | }; 68 | 69 | /** 70 | * These flags and masks are used for packing/unpacking 71 | * the record header 72 | */ 73 | NdefRecord.HDR_FLAG_MESSAGE_BEGIN = 0x80; 74 | NdefRecord.HDR_FLAG_MESSAGE_END = 0x40; 75 | NdefRecord.HDR_FLAG_CHUNKED = 0x20; 76 | NdefRecord.HDR_FLAG_SHORT_RECORD = 0x10; 77 | NdefRecord.HDR_FLAG_ID_LENGTH_PRESENT = 0x08; 78 | NdefRecord.HDR_MASK_TNF = 0x07; 79 | 80 | /** 81 | * Type Name Format values 82 | */ 83 | NdefRecord.TNF_EMPTY = 0x00; 84 | NdefRecord.TNF_WELL_KNOWN = 0x01; 85 | NdefRecord.TNF_MEDIA = 0x02; 86 | NdefRecord.TNF_ABSOLUTE_URI = 0x03; 87 | NdefRecord.TNF_EXTERNAL = 0x04; 88 | NdefRecord.TNF_UNKNOWN = 0x05; 89 | NdefRecord.TNF_UNCHANGED = 0x06; 90 | NdefRecord.TNF_RESERVED = 0x07; 91 | 92 | /** 93 | * NDEF URI Prefixes 94 | */ 95 | NdefRecord.URI_PRE_NONE = 0x00; 96 | NdefRecord.URI_PRE_HTTP_WWW = 0x01; 97 | NdefRecord.URI_PRE_HTTPS_WWW = 0x02; 98 | NdefRecord.URI_PRE_HTTP = 0x03; 99 | NdefRecord.URI_PRE_HTTPS = 0x04; 100 | NdefRecord.URI_PRE_TEL = 0x05; 101 | NdefRecord.URI_PRE_MAILTO = 0x06; 102 | NdefRecord.URI_PRE_FTP_ANON = 0x07; 103 | NdefRecord.URI_PRE_FTP_FTP = 0x08; // ftp://ftp 104 | NdefRecord.URI_PRE_FTPS = 0x09; 105 | NdefRecord.URI_PRE_SFTP = 0x0A; 106 | NdefRecord.URI_PRE_SMB = 0x0B; 107 | NdefRecord.URI_PRE_NFS = 0x0C; 108 | NdefRecord.URI_PRE_FTP = 0x0D; 109 | NdefRecord.URI_PRE_DAV = 0x0E; 110 | NdefRecord.URI_PRE_NEWS = 0x0F; 111 | NdefRecord.URI_PRE_TELNET = 0x10; 112 | NdefRecord.URI_PRE_IMAP = 0x11; 113 | NdefRecord.URI_PRE_RTSP = 0x12; 114 | NdefRecord.URI_PRE_URN = 0x13; 115 | NdefRecord.URI_PRE_POP = 0x14; 116 | NdefRecord.URI_PRE_SIP = 0x15; 117 | NdefRecord.URI_PRE_SIPS = 0x16; 118 | NdefRecord.URI_PRE_TFTP = 0x17; 119 | NdefRecord.URI_PRE_BTSPP = 0x18; 120 | NdefRecord.URI_PRE_BTL2CAP = 0x19; 121 | NdefRecord.URI_PRE_BTGOEP = 0x1A; 122 | NdefRecord.URI_PRE_TCPOBEX = 0x1B; 123 | NdefRecord.URI_PRE_IRDAOBEX = 0x1C; 124 | NdefRecord.URI_PRE_FILE = 0x1D; 125 | NdefRecord.URI_PRE_URN_EPC_ID = 0x1E; 126 | NdefRecord.URI_PRE_URN_EPC_TAG = 0x1F; 127 | NdefRecord.URI_PRE_URN_EPC_PAT = 0x20; 128 | NdefRecord.URI_PRE_URN_EPC_RAW = 0x21; 129 | NdefRecord.URI_PRE_URN_EPC = 0x22; 130 | NdefRecord.URI_PRE_URN_NFC = 0x23; 131 | 132 | /** 133 | * Indicate if this record has its chunked flag set 134 | * 135 | * @return {!boolean} isChunked; 136 | */ 137 | NdefRecord.prototype.isChunked = function() { 138 | return this.chunked; 139 | }; 140 | 141 | /** 142 | * Get the TNF for this record 143 | * 144 | * @return {!integer} tnf; 145 | */ 146 | NdefRecord.prototype.getTnf = function() { 147 | return this.tnf; 148 | }; 149 | 150 | /** 151 | * Get the type from this NDEF Record 152 | * 153 | * @return {!Uint8Array} type; 154 | */ 155 | NdefRecord.prototype.getType = function() { 156 | return this.type; 157 | }; 158 | 159 | /** 160 | * Get the id from this NDEF Record 161 | * 162 | * @return {?Uint8Array} id; 163 | */ 164 | NdefRecord.prototype.getId = function() { 165 | return this.id; 166 | }; 167 | 168 | /** 169 | * Get the payload from this NDEF Record 170 | * 171 | * @return {!Uint8Array} payload; 172 | */ 173 | NdefRecord.prototype.getPayload = function() { 174 | return this.payload; 175 | }; 176 | 177 | /** 178 | * Convert an NDEF Record to a byte array for writing 179 | * 180 | * @param {boolean} isBegin if this record starts a message 181 | * @param {boolean} isEnd if this record concludes a message 182 | * @return {Uint8Array} byte array representation 183 | */ 184 | NdefRecord.prototype.toByteArray = function(isBegin, isEnd) { 185 | var shortRecord = true; 186 | var hasId = true; 187 | 188 | if(this.payload.length >= 255) { 189 | shortRecord = false; 190 | } 191 | 192 | if(typeof this.id === "undefined" || this.id === null || this.id.length === 0) { 193 | hasId = false; 194 | } 195 | 196 | var messageSize = 2; // control byte, type length 197 | 198 | messageSize += this.type.length; 199 | if(shortRecord) { 200 | messageSize += 1; // single byte length 201 | } 202 | else { 203 | messageSize += 4; // four byte length 204 | } 205 | 206 | if(hasId) { 207 | messageSize += 1; // id length 208 | messageSize += this.id.length; 209 | } 210 | 211 | messageSize += this.payload.length; 212 | 213 | var result = new Uint8Array(messageSize); 214 | 215 | // this mask should be unecessary 216 | var firstByte = (this.tnf & NdefRecord.HDR_MASK_TNF); 217 | 218 | if(shortRecord) { 219 | firstByte |= NdefRecord.HDR_FLAG_SHORT_RECORD; 220 | } 221 | 222 | if(hasId) { 223 | firstByte |= NdefRecord.HDR_FLAG_ID_LENGTH_PRESENT; 224 | } 225 | 226 | if(isBegin) { 227 | firstByte |= NdefRecord.HDR_FLAG_MESSAGE_BEGIN; 228 | } 229 | 230 | if(isEnd) { 231 | firstByte |= NdefRecord.HDR_FLAG_MESSAGE_END; 232 | } 233 | 234 | if(this.isChunked()) { 235 | firstByte |= NdefRecord.HDR_FLAG_CHUNKED; 236 | } 237 | 238 | var count = 0; 239 | 240 | result[count++] = firstByte; 241 | result[count++] = this.type.length; 242 | 243 | if(shortRecord) { 244 | result[count++] = 0xff & this.payload.length; 245 | } 246 | else { 247 | result[count++] = 0xff & (this.payload.length >>> 24); 248 | result[count++] = 0xff & (this.payload.length >>> 16); 249 | result[count++] = 0xff & (this.payload.length >>> 8); 250 | result[count++] = 0xff & (this.payload.length >>> 0); 251 | } 252 | 253 | if(hasId) { 254 | result[count++] = 0xff & this.id.length; 255 | } 256 | 257 | //add type bytes 258 | for(var typeCntr = 0; typeCntr < this.type.length; typeCntr++) { 259 | result[count++] = this.type[typeCntr]; 260 | } 261 | 262 | if(hasId) { 263 | for(var idCntr = 0; idCntr < this.id.length; idCntr++) { 264 | result[count++] = this.id[idCntr]; 265 | } 266 | } 267 | 268 | for(var pldCntr = 0; pldCntr < this.payload.length; pldCntr++) { 269 | result[count++] = this.payload[pldCntr]; 270 | } 271 | 272 | if(result.length !== count) { 273 | throw "NDEF Record was not successfully generated"; 274 | } 275 | 276 | return result; 277 | }; 278 | 279 | /** 280 | * Constructs an NdefMessage from an array of NdefRecord 281 | * 282 | * @constructor 283 | * @param {!NdefRecord[]} ndefRecords the records this message contains 284 | */ 285 | var NdefMessage = function(ndefRecords) { 286 | if(typeof ndefRecords === "undefined" || 287 | ndefRecords === null || 288 | ndefRecords.length === 0) { 289 | throw "You must supply a non-zero length array of NdefRecords to construct an NdefMessage"; 290 | } 291 | this.ndefRecords = ndefRecords; 292 | }; 293 | 294 | 295 | /** 296 | * Returns the array of records this NdefMessage contains 297 | * 298 | * @return {!NdefRecord[]} the NDEF records contained 299 | */ 300 | NdefMessage.prototype.getRecords = function() { 301 | return this.ndefRecords; 302 | }; 303 | 304 | /** 305 | * Returns a byte array of the message ready to be written to a tag 306 | * 307 | * @return {!Uint8Array} byte array representation 308 | */ 309 | NdefMessage.prototype.toByteArray = function() { 310 | var result = new Uint8Array(0); 311 | for(var i = 0; i < this.ndefRecords.length; i++) { 312 | // this is very inefficient, but it shouldn't really matter 313 | // unless very large multi-record messages are being constructed 314 | var recordBytes = this.ndefRecords[i].toByteArray(i===0, i===(this.ndefRecords.length - 1)); 315 | var grownArray = new Uint8Array(result.length + recordBytes.length); 316 | grownArray.set(result); 317 | grownArray.set(recordBytes,result.length); 318 | result = grownArray; 319 | } 320 | return result; 321 | }; 322 | 323 | /** 324 | * Creates an NdefMessage from a byte array 325 | * 326 | * @param {!Uint8Array} bytes 327 | * @return {?NdefMessage} resulting message, null if a parse error occured 328 | */ 329 | NdefMessage.fromBytes = function(bytes) { 330 | if(typeof bytes === "undefined" || 331 | bytes === null) { 332 | throw "Bytes must be defined and non-null"; 333 | } 334 | // fixes odd issue when normal arrays are passed 335 | bytes = new Uint8Array(bytes); 336 | // theoretical minimum is 3 bytes 337 | if(bytes.length < 3) { 338 | throw "Byte array is too short to contain any kind of NDEF message"; 339 | } 340 | 341 | 342 | var throwOnMsgTooShort = function(bytes, idx) { 343 | if(bytes.length < (idx+1)) { 344 | throw "Message ended abruptly, trying to access index "+idx+" from an array of "+bytes.length+" items."; 345 | } 346 | }; 347 | 348 | var ndefRecords = []; 349 | var done = false; 350 | var foundStart = false; 351 | 352 | var currentIndex = 0; 353 | while(!done) { 354 | if(currentIndex >= bytes.length) { 355 | // ran out of bytes before message ended 356 | throw "Ran out of bytes before message started"; 357 | } 358 | 359 | var recordFirstByte = bytes[currentIndex]; 360 | 361 | 362 | // check for message beginning 363 | foundStart = foundStart || NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_MESSAGE_BEGIN); 364 | 365 | if(foundStart) { 366 | done = NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_MESSAGE_END); 367 | var isChunked = NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_CHUNKED); 368 | var isShortRecord = NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_SHORT_RECORD); 369 | var hasIdLength = NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_ID_LENGTH_PRESENT); 370 | 371 | var tnf = recordFirstByte & NdefRecord.HDR_MASK_TNF; 372 | 373 | var typeIdx = currentIndex + 1; 374 | throwOnMsgTooShort(bytes,typeIdx); 375 | var typeLength = bytes[typeIdx]; 376 | 377 | var payloadLength = 0; 378 | var payloadLengthLength = isShortRecord ? 1 : 4; 379 | var payloadLengthStartIdx = typeIdx+1; 380 | 381 | if(isShortRecord) { 382 | throwOnMsgTooShort(bytes,payloadLengthStartIdx); 383 | payloadLength = bytes[payloadLengthStartIdx]; 384 | } 385 | else { 386 | throwOnMsgTooShort(bytes,payloadLengthStartIdx+3); 387 | var valueArr = new Uint32Array(bytes.slice(payloadLengthStartIdx,payloadStartIdx+4)); 388 | payloadLength = valueArr[0]; 389 | } 390 | 391 | var idLength = 0; 392 | var idLengthStartIdx = payloadLengthStartIdx+payloadLengthLength; 393 | 394 | throwOnMsgTooShort(bytes,idLengthStartIdx); 395 | if(hasIdLength) { 396 | idLength = bytes[idLengthStartIdx]; 397 | } 398 | 399 | var idLengthEndIdx = idLengthStartIdx + (hasIdLength ? 1 : 0); 400 | var typeEndIdx = idLengthEndIdx + typeLength; 401 | throwOnMsgTooShort(bytes,typeEndIdx); 402 | var type = new Uint8Array(bytes.buffer,idLengthEndIdx,typeLength); 403 | 404 | var idStartIdx = typeEndIdx; 405 | throwOnMsgTooShort(bytes,idStartIdx+idLength); 406 | var id = new Uint8Array(bytes.buffer,idStartIdx,idLength); 407 | 408 | var payloadStartIdx = idStartIdx+idLength; 409 | throwOnMsgTooShort(bytes,payloadStartIdx+payloadLength-1); 410 | var payload = new Uint8Array(bytes.buffer,payloadStartIdx,payloadLength); 411 | 412 | currentIndex = payloadStartIdx+payloadLength; 413 | ndefRecords.push(new NdefRecord(isChunked,tnf,type,id,payload)); 414 | } 415 | else { 416 | currentIndex++; 417 | } 418 | } 419 | 420 | return new NdefMessage(ndefRecords); 421 | }; 422 | 423 | /** 424 | * Utility functions for Ndef operations and debugging 425 | * 426 | * @version 1.0.2 427 | */ 428 | var NdefRecordUtils = function() { 429 | throw "Do not instantiate ndef record utils"; 430 | }; 431 | 432 | /** 433 | * Resolves a URI into the appropriate NDEF record using 434 | * the standard URI prefixes 435 | * 436 | * @param {string} uri uri to write 437 | * @returns {NdefRecord} record with the uri prefix extracted 438 | */ 439 | NdefRecordUtils.createUriRecord = function(uri) { 440 | var parsed = NdefRecordUtils.resolveUriToPrefix(uri); 441 | var prefixCode = parsed.prefixCode; 442 | var content = parsed.content; 443 | 444 | var contentArray = NdefRecordUtils.stringToUint8Array(content); 445 | 446 | var payload = new Uint8Array(contentArray.length+1); 447 | payload[0] = prefixCode; 448 | payload.set(contentArray,1); 449 | 450 | return new NdefRecord(false,NdefRecord.TNF_WELL_KNOWN,new Uint8Array([0x55]),null,payload); 451 | }; 452 | 453 | /** 454 | * Resolves a string into the appropriate UTF8 455 | * text record 456 | * 457 | * @param {string} content record contents 458 | * @param {?string} language language code for record 459 | * @returns {NdefRecord} 460 | */ 461 | NdefRecordUtils.createTextRecord = function(content, language) { 462 | 463 | var contentArray = NdefRecordUtils.stringToUint8Array(content); 464 | if(typeof language === "undefined") { 465 | language = "en"; 466 | } 467 | var languageArray = NdefRecordUtils.stringToUint8Array(language); 468 | 469 | 470 | var payload = new Uint8Array(contentArray.length+languageArray.length+1); 471 | payload[0] = languageArray.length; 472 | payload.set(languageArray,1); 473 | payload.set(contentArray,1+languageArray.length); 474 | 475 | return new NdefRecord(false,NdefRecord.TNF_WELL_KNOWN,new Uint8Array([0x54]),null,payload); 476 | }; 477 | 478 | /** 479 | * Internal function for converting a string 480 | * into a Uint8Array for creating NdefRecords 481 | * 482 | * @param {string} string record contents 483 | * @return {Uint8Array} binary representation 484 | */ 485 | NdefRecordUtils.stringToUint8Array = function(string) { 486 | var escstr = encodeURIComponent(string); 487 | var binstr = escstr.replace(/%([0-9A-F]{2})/g, function(match, p1) { 488 | return String.fromCharCode('0x' + p1); 489 | }); 490 | var ua = new Uint8Array(binstr.length); 491 | Array.prototype.forEach.call(binstr, function (ch, i) { 492 | ua[i] = ch.charCodeAt(0); 493 | }); 494 | return ua; 495 | }; 496 | 497 | /** 498 | * Internal function for converting a Uint8Array 499 | * into a string for parsing NdefRecords 500 | * 501 | * @param {Uint8Array} binary representation 502 | * @return {string} string record contents 503 | */ 504 | NdefRecordUtils.uint8ArrayToString = function(arr) { 505 | var binstr = Array.prototype.map.call(arr, function (ch) { 506 | return String.fromCharCode(ch); 507 | }).join(''); 508 | 509 | var escstr = binstr.replace(/(.)/g, function (m, p) { 510 | var code = p.charCodeAt(0).toString(16).toUpperCase(); 511 | if (code.length < 2) { 512 | code = '0' + code; 513 | } 514 | return '%' + code; 515 | }); 516 | return decodeURIComponent(escstr); 517 | }; 518 | 519 | /** 520 | * Resolves a URI into the appropriate prefix/data 521 | * pair for NDEF writing 522 | * 523 | * @param {string} uri string of supposed uri 524 | * @returns { prefixCode: int, prefix: string, content: string, fullUri: string} 525 | */ 526 | NdefRecordUtils.resolveUriString = function(uri) { 527 | return NdefRecordUtils.resolveUriToPrefix(uri); 528 | }; 529 | 530 | /** 531 | * Resolves a URI into the appropriate prefix/data 532 | * pair for NDEF writing 533 | * 534 | * @deprecated Use NdefRecordUtils.resolveUriString instead 535 | * @param uri string of supposed uri 536 | * @returns { prefixCode: int, prefix: string, content: string, fullUri: string} 537 | */ 538 | NdefRecordUtils.resolveUriToPrefix = function(url) { 539 | var u = url; 540 | var stripper = function(prefix,prefixCode,fullUri) { 541 | return { 542 | "prefixCode": prefixCode, 543 | "prefix": prefix, 544 | "content": fullUri.slice(prefix.length), 545 | "fullUri": fullUri 546 | }; 547 | }; 548 | 549 | if(u.startsWith("http")) { 550 | if(u.startsWith("https://www.")) { 551 | return stripper("https://www.",NdefRecord.URI_PRE_HTTPS_WWW,u); 552 | } else if(u.startsWith("https://")) { 553 | return stripper("https://",NdefRecord.URI_PRE_HTTPS,u); 554 | } else if(u.startsWith("http://www.")) { 555 | return stripper("http://www.",NdefRecord.URI_PRE_HTTP_WWW,u); 556 | } else if(u.startsWith("http://")) { 557 | return stripper("http://",NdefRecord.URI_PRE_HTTP,u); 558 | } 559 | } 560 | else if(u.startsWith("ftp")) { 561 | if(u.startsWith("ftp://ftp.")) { 562 | return stripper("ftp://ftp.",NdefRecord.URI_PRE_FTP_FTP,u); 563 | } 564 | else if (u.startsWith("ftps://")) { 565 | return stripper("ftps://",NdefRecord.URI_PRE_FTPS,u); 566 | } 567 | else if (u.startsWith("ftp://anonymous:anonymous@")) { 568 | return stripper("ftp://anonymous:anonymous@",NdefRecord.URI_PRE_FTP_ANON,u); 569 | } 570 | else if(u.startsWith("ftp://")) { 571 | return stripper("ftp://",NdefRecord.URI_PRE_FTP,u); 572 | } 573 | } 574 | else if (u.startsWith("mailto:")) { 575 | return stripper("mailto:",NdefRecord.URI_PRE_MAILTO,u); 576 | } 577 | else if (u.startsWith("tel:")) { 578 | return stripper("tel:",NdefRecord.URI_PRE_TEL,u); 579 | } 580 | else if (u.startsWith("sftp://")) { 581 | return stripper("sftp://",NdefRecord.URI_PRE_SFTP,u); 582 | } 583 | else if (u.startsWith("smb://")) { 584 | return stripper("smb://",NdefRecord.URI_PRE_SMB,u); 585 | } 586 | else if (u.startsWith("nfs://")) { 587 | return stripper("nfs://",NdefRecord.URI_PRE_NFS,u); 588 | } 589 | else if (u.startsWith("dav://")) { 590 | return stripper("dav://",NdefRecord.URI_PRE_DAV,u); 591 | } 592 | else if (u.startsWith("news:")) { 593 | return stripper("news:",NdefRecord.URI_PRE_NEWS,u); 594 | } 595 | else if (u.startsWith("telnet://")) { 596 | return stripper("telnet://",NdefRecord.URI_PRE_TELNET,u); 597 | } 598 | else if (u.startsWith("imap:")) { 599 | return stripper("imap:",NdefRecord.URI_PRE_IMAP,u); 600 | } 601 | else if (u.startsWith("rtsp://")) { 602 | return stripper("rtsp://",NdefRecord.URI_PRE_RTSP,u); 603 | } 604 | else if (u.startsWith("pop:")) { 605 | return stripper("pop:",NdefRecord.URI_PRE_POP,u); 606 | } 607 | else if (u.startsWith("sip:")) { 608 | return stripper("sip:",NdefRecord.URI_PRE_SIP,u); 609 | } 610 | else if (u.startsWith("sips:")) { 611 | return stripper("sips:",NdefRecord.URI_PRE_SIPS,u); 612 | } 613 | else if (u.startsWith("tftp:")) { 614 | return stripper("tftp:",NdefRecord.URI_PRE_TFTP,u); 615 | } 616 | else if (u.startsWith("btspp://")) { 617 | return stripper("btspp://",NdefRecord.URI_PRE_BTSPP,u); 618 | } 619 | else if (u.startsWith("btl2cap://")) { 620 | return stripper("btl2cap://",NdefRecord.URI_PRE_BTL2CAP,u); 621 | } 622 | else if (u.startsWith("btgoep://")) { 623 | return stripper("btgoep://",NdefRecord.URI_PRE_BTGOEP,u); 624 | } 625 | else if (u.startsWith("tcpobex://")) { 626 | return stripper("tcpobex://",NdefRecord.URI_PRE_TCPOBEX,u); 627 | } 628 | else if (u.startsWith("irdaobex://")) { 629 | return stripper("irdaobex://",NdefRecord.URI_PRE_IRDAOBEX,u); 630 | } 631 | else if (u.startsWith("file://")) { 632 | return stripper("file://",NdefRecord.URI_PRE_FILE,u); 633 | } 634 | else if (u.startsWith("urn:epc:id:")) { 635 | return stripper("urn:epc:id:",NdefRecord.URI_PRE_URN_EPC_ID,u); 636 | } 637 | else if (u.startsWith("urn:epc:tag:")) { 638 | return stripper("urn:epc:tag:",NdefRecord.URI_PRE_URN_EPC_TAG,u); 639 | } 640 | else if (u.startsWith("urn:epc:pat:")) { 641 | return stripper("urn:epc:pat:",NdefRecord.URI_PRE_URN_EPC_PAT,u); 642 | } 643 | else if (u.startsWith("urn:epc:raw:")) { 644 | return stripper("urn:epc:raw:",NdefRecord.URI_PRE_EPC_RAW,u); 645 | } 646 | else if (u.startsWith("urn:epc:")) { 647 | return stripper("urn:epc:",NdefRecord.URI_PRE_URN_EPC,u); 648 | } 649 | else if (u.startsWith("urn:nfc:")) { 650 | return stripper("urn:nfc:",NdefRecord.URI_PRE_URN_NFC,u); 651 | } 652 | else if (u.startsWith("urn:")) { 653 | return stripper("urn:",NdefRecord.URI_PRE_URN,u); 654 | } 655 | 656 | return stripper("",NdefRecord.URI_PRE_NONE,url); 657 | }; 658 | 659 | /** 660 | * Resolves the completely from a WELL_KNOWN URI record 661 | * by prepending the appropriate URI prefix. 662 | * 663 | * @throws if record isn't a WELL_KNOWN URI 664 | * @throws if the record prefix isn't known 665 | * @param {NdefRecord} record to extract string 666 | * @return {string} resulting URI string 667 | */ 668 | NdefRecordUtils.resolveUriRecordToString = function(ndefRecord) { 669 | return NdefRecordUtils.resolveUrlFromPrefix(ndefRecord); 670 | }; 671 | 672 | /** 673 | * Resolves the completely from a WELL_KNOWN URI record 674 | * by prepending the appropriate URI prefix. 675 | * 676 | * @deprecated use NdefRecordUtils.resolveUriToString instead 677 | * @throws if record isn't a WELL_KNOWN URI 678 | * @throws if the record prefix isn't known 679 | * @param {NdefRecord} record to extract string 680 | * @return {string} resulting URI string 681 | */ 682 | NdefRecordUtils.resolveUrlFromPrefix = function(ndefRecord) { 683 | if(ndefRecord.getTnf() !== NdefRecord.TNF_WELL_KNOWN || (ndefRecord.getType())[0] !== 0x55) { 684 | throw "Not a WELL_KNOWN URI record"; 685 | } 686 | else { 687 | var payload = ndefRecord.getPayload(); 688 | var prefixCode = payload[0]; 689 | var data = payload.slice(1); 690 | var prefix = ""; 691 | 692 | switch(prefixCode) { 693 | case NdefRecord.URI_PRE_NONE: 694 | break; 695 | case NdefRecord.URI_PRE_HTTP_WWW: 696 | prefix = "http://www."; 697 | break; 698 | case NdefRecord.URI_PRE_HTTPS_WWW: 699 | prefix = "https://www."; 700 | break; 701 | case NdefRecord.URI_PRE_HTTP: 702 | prefix = "http://"; 703 | break; 704 | case NdefRecord.URI_PRE_HTTPS: 705 | prefix = "https://"; 706 | break; 707 | case NdefRecord.URI_PRE_TEL: 708 | prefix = "tel:"; 709 | break; 710 | case NdefRecord.URI_PRE_MAILTO: 711 | prefix = "mailto:"; 712 | break; 713 | case NdefRecord.URI_PRE_FTP_ANON: 714 | prefix = "ftp://anonymous:anonymous@"; 715 | break; 716 | case NdefRecord.URI_PRE_FTP_FTP: 717 | prefix = "ftp://ftp."; 718 | break; // ftp://ftp 719 | case NdefRecord.URI_PRE_FTPS: 720 | prefix = "ftps://"; 721 | break; 722 | case NdefRecord.URI_PRE_SFTP: 723 | prefix = "sftp://"; 724 | break; 725 | case NdefRecord.URI_PRE_SMB: 726 | prefix = "smb://"; 727 | break; 728 | case NdefRecord.URI_PRE_NFS: 729 | prefix = "nfs://"; 730 | break; 731 | case NdefRecord.URI_PRE_FTP: 732 | prefix = "ftp://"; 733 | break; 734 | case NdefRecord.URI_PRE_DAV: 735 | prefix = "dav://"; 736 | break; 737 | case NdefRecord.URI_PRE_NEWS: 738 | prefix = "news:"; 739 | break; 740 | case NdefRecord.URI_PRE_TELNET: 741 | prefix = "telnet://"; 742 | break; 743 | case NdefRecord.URI_PRE_IMAP: 744 | prefix = "imap:"; 745 | break; 746 | case NdefRecord.URI_PRE_RTSP: 747 | prefix = "rtsp://"; 748 | break; 749 | case NdefRecord.URI_PRE_URN: 750 | prefix = "urn:"; 751 | break; 752 | case NdefRecord.URI_PRE_POP: 753 | prefix = "pop:"; 754 | break; 755 | case NdefRecord.URI_PRE_SIP: 756 | prefix = "sip:"; 757 | break; 758 | case NdefRecord.URI_PRE_SIPS: 759 | prefix = "sips:"; 760 | break; 761 | case NdefRecord.URI_PRE_TFTP: 762 | prefix = "tftp:"; 763 | break; 764 | case NdefRecord.URI_PRE_BTSPP: 765 | prefix = "btspp://"; 766 | break; 767 | case NdefRecord.URI_PRE_BTL2CAP: 768 | prefix = "btl2cap://"; 769 | break; 770 | case NdefRecord.URI_PRE_BTGOEP: 771 | prefix = "btgoep://"; 772 | break; 773 | case NdefRecord.URI_PRE_TCPOBEX: 774 | prefix = "tcpobex://"; 775 | break; 776 | case NdefRecord.URI_PRE_IRDAOBEX: 777 | prefix = "irdaobex://"; 778 | break; 779 | case NdefRecord.URI_PRE_FILE: 780 | prefix = "file://"; 781 | break; 782 | case NdefRecord.URI_PRE_URN_EPC_ID: 783 | prefix = "urn:epc:id:"; 784 | break; 785 | case NdefRecord.URI_PRE_URN_EPC_TAG: 786 | prefix = "urn:epc:tag:"; 787 | break; 788 | case NdefRecord.URI_PRE_URN_EPC_PAT: 789 | prefix = "urn:epc:pat:"; 790 | break; 791 | case NdefRecord.URI_PRE_URN_EPC_RAW: 792 | prefix = "urn:epc:raw:"; 793 | break; 794 | case NdefRecord.URI_PRE_URN_EPC: 795 | prefix = "urn:epc:"; 796 | break; 797 | case NdefRecord.URI_PRE_URN_NFC: 798 | prefix = "urn:nfc:"; 799 | break; 800 | default: 801 | throw "Invalid URI code"; 802 | } 803 | 804 | return prefix + NdefRecordUtils.uint8ArrayToString(data); 805 | } 806 | }; 807 | 808 | /** 809 | * Resolves a WELL_KNOWN text record 810 | * extracting the language code and content 811 | * 812 | * @throws if record isn't a WELL_KNOWN TEXT 813 | * @throws if the language code bytes are invalid 814 | * @returns {language: language code string, content: text contained} or null 815 | */ 816 | NdefRecordUtils.resolveTextRecord = function(ndefRecord) { 817 | if(ndefRecord.getTnf() !== NdefRecord.TNF_WELL_KNOWN || (ndefRecord.getType())[0] !== 0x54) { 818 | throw "Not a WELL_KNOWN text record"; 819 | } 820 | else { 821 | var payload = ndefRecord.getPayload(); 822 | if(payload.length === 0) { 823 | throw "Missing error code"; 824 | } 825 | 826 | //masking out the utf bit 827 | var languageCodeLength = payload[0] & 0x3F; 828 | if(payload.length < (languageCodeLength+1)) { 829 | throw "Payload too short to contain language code"; 830 | } 831 | 832 | var language = NdefRecordUtils.uint8ArrayToString(payload.slice(1,1+languageCodeLength)); 833 | var content = ""; 834 | 835 | if(payload.length > 1+languageCodeLength) { 836 | content = NdefRecordUtils.uint8ArrayToString(payload.slice(1+languageCodeLength)); 837 | } 838 | 839 | return {language: language, content: content}; 840 | } 841 | }; 842 | 843 | var Ndef = {}; 844 | Ndef.Message = NdefMessage; 845 | Ndef.Record = NdefRecord; 846 | Ndef.Utils = NdefRecordUtils; 847 | return Ndef; 848 | })); 849 | -------------------------------------------------------------------------------- /dist/ndef.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.Ndef=e()}(this,function(){var t=function(){throw"Dont construct NdefUtils!"};t.hasFlag=function(t,e){return(t&e)===e};var e=function(t,e,r,n,_){if("undefined"==typeof t||"undefined"==typeof e||"undefined"==typeof r||"undefined"==typeof n||"undefined"==typeof _)throw"Parameters missing from NdefRecord constructor call";if(null===t||null===e||null===r||null===_)throw"Passed null to non-nullable constructor parameter in NdefRecord";this.chunked=t,this.tnf=e,this.type=r,this.id=n,this.payload=_};e.HDR_FLAG_MESSAGE_BEGIN=128,e.HDR_FLAG_MESSAGE_END=64,e.HDR_FLAG_CHUNKED=32,e.HDR_FLAG_SHORT_RECORD=16,e.HDR_FLAG_ID_LENGTH_PRESENT=8,e.HDR_MASK_TNF=7,e.TNF_EMPTY=0,e.TNF_WELL_KNOWN=1,e.TNF_MEDIA=2,e.TNF_ABSOLUTE_URI=3,e.TNF_EXTERNAL=4,e.TNF_UNKNOWN=5,e.TNF_UNCHANGED=6,e.TNF_RESERVED=7,e.URI_PRE_NONE=0,e.URI_PRE_HTTP_WWW=1,e.URI_PRE_HTTPS_WWW=2,e.URI_PRE_HTTP=3,e.URI_PRE_HTTPS=4,e.URI_PRE_TEL=5,e.URI_PRE_MAILTO=6,e.URI_PRE_FTP_ANON=7,e.URI_PRE_FTP_FTP=8,e.URI_PRE_FTPS=9,e.URI_PRE_SFTP=10,e.URI_PRE_SMB=11,e.URI_PRE_NFS=12,e.URI_PRE_FTP=13,e.URI_PRE_DAV=14,e.URI_PRE_NEWS=15,e.URI_PRE_TELNET=16,e.URI_PRE_IMAP=17,e.URI_PRE_RTSP=18,e.URI_PRE_URN=19,e.URI_PRE_POP=20,e.URI_PRE_SIP=21,e.URI_PRE_SIPS=22,e.URI_PRE_TFTP=23,e.URI_PRE_BTSPP=24,e.URI_PRE_BTL2CAP=25,e.URI_PRE_BTGOEP=26,e.URI_PRE_TCPOBEX=27,e.URI_PRE_IRDAOBEX=28,e.URI_PRE_FILE=29,e.URI_PRE_URN_EPC_ID=30,e.URI_PRE_URN_EPC_TAG=31,e.URI_PRE_URN_EPC_PAT=32,e.URI_PRE_URN_EPC_RAW=33,e.URI_PRE_URN_EPC=34,e.URI_PRE_URN_NFC=35,e.prototype.isChunked=function(){return this.chunked},e.prototype.getTnf=function(){return this.tnf},e.prototype.getType=function(){return this.type},e.prototype.getId=function(){return this.id},e.prototype.getPayload=function(){return this.payload},e.prototype.toByteArray=function(t,r){var n=!0,_=!0;this.payload.length>=255&&(n=!1),"undefined"!=typeof this.id&&null!==this.id&&0!==this.id.length||(_=!1);var R=2;R+=this.type.length,R+=n?1:4,_&&(R+=1,R+=this.id.length),R+=this.payload.length;var a=new Uint8Array(R),i=this.tnf&e.HDR_MASK_TNF;n&&(i|=e.HDR_FLAG_SHORT_RECORD),_&&(i|=e.HDR_FLAG_ID_LENGTH_PRESENT),t&&(i|=e.HDR_FLAG_MESSAGE_BEGIN),r&&(i|=e.HDR_FLAG_MESSAGE_END),this.isChunked()&&(i|=e.HDR_FLAG_CHUNKED);var s=0;a[s++]=i,a[s++]=this.type.length,n?a[s++]=255&this.payload.length:(a[s++]=255&this.payload.length>>>24,a[s++]=255&this.payload.length>>>16,a[s++]=255&this.payload.length>>>8,a[s++]=255&this.payload.length>>>0),_&&(a[s++]=255&this.id.length);for(var o=0;o=n.length)throw"Ran out of bytes before message started";var o=n[s];if(i=i||t.hasFlag(o,e.HDR_FLAG_MESSAGE_BEGIN)){a=t.hasFlag(o,e.HDR_FLAG_MESSAGE_END);var P=t.hasFlag(o,e.HDR_FLAG_CHUNKED),E=t.hasFlag(o,e.HDR_FLAG_SHORT_RECORD),f=t.hasFlag(o,e.HDR_FLAG_ID_LENGTH_PRESENT),u=o&e.HDR_MASK_TNF,U=s+1;_(n,U);var h=n[U],p=0,c=E?1:4,I=U+1;if(E)_(n,I),p=n[I];else{_(n,I+3);var l=new Uint32Array(n.slice(I,F+4));p=l[0]}var T=0,d=I+c;_(n,d),f&&(T=n[d]);var N=d+(f?1:0),y=N+h;_(n,y);var g=new Uint8Array(n.buffer,N,h),A=y;_(n,A+T);var W=new Uint8Array(n.buffer,A,T),F=A+T;_(n,F+p-1);var S=new Uint8Array(n.buffer,F,p);s=F+p,R.push(new e(P,u,g,W,S))}else s++}return new r(R)};var n=function(){throw"Do not instantiate ndef record utils"};n.createUriRecord=function(t){var r=n.resolveUriToPrefix(t),_=r.prefixCode,R=r.content,a=n.stringToUint8Array(R),i=new Uint8Array(a.length+1);return i[0]=_,i.set(a,1),new e(!1,e.TNF_WELL_KNOWN,new Uint8Array([85]),null,i)},n.createTextRecord=function(t,r){var _=n.stringToUint8Array(t);"undefined"==typeof r&&(r="en");var R=n.stringToUint8Array(r),a=new Uint8Array(_.length+R.length+1);return a[0]=R.length,a.set(R,1),a.set(_,1+R.length),new e(!1,e.TNF_WELL_KNOWN,new Uint8Array([84]),null,a)},n.stringToUint8Array=function(t){var e=encodeURIComponent(t),r=e.replace(/%([0-9A-F]{2})/g,function(t,e){return String.fromCharCode("0x"+e)}),n=new Uint8Array(r.length);return Array.prototype.forEach.call(r,function(t,e){n[e]=t.charCodeAt(0)}),n},n.uint8ArrayToString=function(t){var e=Array.prototype.map.call(t,function(t){return String.fromCharCode(t)}).join(""),r=e.replace(/(.)/g,function(t,e){var r=e.charCodeAt(0).toString(16).toUpperCase();return r.length<2&&(r="0"+r),"%"+r});return decodeURIComponent(r)},n.resolveUriString=function(t){return n.resolveUriToPrefix(t)},n.resolveUriToPrefix=function(t){var r=t,n=function(t,e,r){return{prefixCode:e,prefix:t,content:r.slice(t.length),fullUri:r}};if(r.startsWith("http")){if(r.startsWith("https://www."))return n("https://www.",e.URI_PRE_HTTPS_WWW,r);if(r.startsWith("https://"))return n("https://",e.URI_PRE_HTTPS,r);if(r.startsWith("http://www."))return n("http://www.",e.URI_PRE_HTTP_WWW,r);if(r.startsWith("http://"))return n("http://",e.URI_PRE_HTTP,r)}else if(r.startsWith("ftp")){if(r.startsWith("ftp://ftp."))return n("ftp://ftp.",e.URI_PRE_FTP_FTP,r);if(r.startsWith("ftps://"))return n("ftps://",e.URI_PRE_FTPS,r);if(r.startsWith("ftp://anonymous:anonymous@"))return n("ftp://anonymous:anonymous@",e.URI_PRE_FTP_ANON,r);if(r.startsWith("ftp://"))return n("ftp://",e.URI_PRE_FTP,r)}else{if(r.startsWith("mailto:"))return n("mailto:",e.URI_PRE_MAILTO,r);if(r.startsWith("tel:"))return n("tel:",e.URI_PRE_TEL,r);if(r.startsWith("sftp://"))return n("sftp://",e.URI_PRE_SFTP,r);if(r.startsWith("smb://"))return n("smb://",e.URI_PRE_SMB,r);if(r.startsWith("nfs://"))return n("nfs://",e.URI_PRE_NFS,r);if(r.startsWith("dav://"))return n("dav://",e.URI_PRE_DAV,r);if(r.startsWith("news:"))return n("news:",e.URI_PRE_NEWS,r);if(r.startsWith("telnet://"))return n("telnet://",e.URI_PRE_TELNET,r);if(r.startsWith("imap:"))return n("imap:",e.URI_PRE_IMAP,r);if(r.startsWith("rtsp://"))return n("rtsp://",e.URI_PRE_RTSP,r);if(r.startsWith("pop:"))return n("pop:",e.URI_PRE_POP,r);if(r.startsWith("sip:"))return n("sip:",e.URI_PRE_SIP,r);if(r.startsWith("sips:"))return n("sips:",e.URI_PRE_SIPS,r);if(r.startsWith("tftp:"))return n("tftp:",e.URI_PRE_TFTP,r);if(r.startsWith("btspp://"))return n("btspp://",e.URI_PRE_BTSPP,r);if(r.startsWith("btl2cap://"))return n("btl2cap://",e.URI_PRE_BTL2CAP,r);if(r.startsWith("btgoep://"))return n("btgoep://",e.URI_PRE_BTGOEP,r);if(r.startsWith("tcpobex://"))return n("tcpobex://",e.URI_PRE_TCPOBEX,r);if(r.startsWith("irdaobex://"))return n("irdaobex://",e.URI_PRE_IRDAOBEX,r);if(r.startsWith("file://"))return n("file://",e.URI_PRE_FILE,r);if(r.startsWith("urn:epc:id:"))return n("urn:epc:id:",e.URI_PRE_URN_EPC_ID,r);if(r.startsWith("urn:epc:tag:"))return n("urn:epc:tag:",e.URI_PRE_URN_EPC_TAG,r);if(r.startsWith("urn:epc:pat:"))return n("urn:epc:pat:",e.URI_PRE_URN_EPC_PAT,r);if(r.startsWith("urn:epc:raw:"))return n("urn:epc:raw:",e.URI_PRE_EPC_RAW,r);if(r.startsWith("urn:epc:"))return n("urn:epc:",e.URI_PRE_URN_EPC,r);if(r.startsWith("urn:nfc:"))return n("urn:nfc:",e.URI_PRE_URN_NFC,r);if(r.startsWith("urn:"))return n("urn:",e.URI_PRE_URN,r)}return n("",e.URI_PRE_NONE,t)},n.resolveUriRecordToString=function(t){return n.resolveUrlFromPrefix(t)},n.resolveUrlFromPrefix=function(t){if(t.getTnf()!==e.TNF_WELL_KNOWN||85!==t.getType()[0])throw"Not a WELL_KNOWN URI record";var r=t.getPayload(),_=r[0],R=r.slice(1),a="";switch(_){case e.URI_PRE_NONE:break;case e.URI_PRE_HTTP_WWW:a="http://www.";break;case e.URI_PRE_HTTPS_WWW:a="https://www.";break;case e.URI_PRE_HTTP:a="http://";break;case e.URI_PRE_HTTPS:a="https://";break;case e.URI_PRE_TEL:a="tel:";break;case e.URI_PRE_MAILTO:a="mailto:";break;case e.URI_PRE_FTP_ANON:a="ftp://anonymous:anonymous@";break;case e.URI_PRE_FTP_FTP:a="ftp://ftp.";break;case e.URI_PRE_FTPS:a="ftps://";break;case e.URI_PRE_SFTP:a="sftp://";break;case e.URI_PRE_SMB:a="smb://";break;case e.URI_PRE_NFS:a="nfs://";break;case e.URI_PRE_FTP:a="ftp://";break;case e.URI_PRE_DAV:a="dav://";break;case e.URI_PRE_NEWS:a="news:";break;case e.URI_PRE_TELNET:a="telnet://";break;case e.URI_PRE_IMAP:a="imap:";break;case e.URI_PRE_RTSP:a="rtsp://";break;case e.URI_PRE_URN:a="urn:";break;case e.URI_PRE_POP:a="pop:";break;case e.URI_PRE_SIP:a="sip:";break;case e.URI_PRE_SIPS:a="sips:";break;case e.URI_PRE_TFTP:a="tftp:";break;case e.URI_PRE_BTSPP:a="btspp://";break;case e.URI_PRE_BTL2CAP:a="btl2cap://";break;case e.URI_PRE_BTGOEP:a="btgoep://";break;case e.URI_PRE_TCPOBEX:a="tcpobex://";break;case e.URI_PRE_IRDAOBEX:a="irdaobex://";break;case e.URI_PRE_FILE:a="file://";break;case e.URI_PRE_URN_EPC_ID:a="urn:epc:id:";break;case e.URI_PRE_URN_EPC_TAG:a="urn:epc:tag:";break;case e.URI_PRE_URN_EPC_PAT:a="urn:epc:pat:";break;case e.URI_PRE_URN_EPC_RAW:a="urn:epc:raw:";break;case e.URI_PRE_URN_EPC:a="urn:epc:";break;case e.URI_PRE_URN_NFC:a="urn:nfc:";break;default:throw"Invalid URI code"}return a+n.uint8ArrayToString(R)},n.resolveTextRecord=function(t){if(t.getTnf()!==e.TNF_WELL_KNOWN||84!==t.getType()[0])throw"Not a WELL_KNOWN text record";var r=t.getPayload();if(0===r.length)throw"Missing error code";var _=63&r[0];if(r.length<_+1)throw"Payload too short to contain language code";var R=n.uint8ArrayToString(r.slice(1,1+_)),a="";return r.length>1+_&&(a=n.uint8ArrayToString(r.slice(1+_))),{language:R,content:a}};var _={};return _.Message=r,_.Record=e,_.Utils=n,_}); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | uglify = require('gulp-uglify'), 3 | rename = require('gulp-rename'), 4 | del = require('del'), 5 | jshint = require('gulp-jshint'), 6 | debug = require('gulp-debug'), 7 | seq = require('run-sequence'), 8 | jasmine = require('gulp-jasmine'), 9 | merge = require('merge-stream'); 10 | 11 | gulp.task('compile', function() { 12 | var min = gulp.src('src/ndef.js') 13 | .pipe(uglify()) 14 | .pipe(rename("ndef.min.js")) 15 | .pipe(gulp.dest('dist')); 16 | 17 | var std = gulp.src('src/ndef.js') 18 | .pipe(rename("ndef.js")) 19 | .pipe(gulp.dest('dist')); 20 | 21 | return merge(min,std); 22 | }); 23 | 24 | gulp.task('clean',function() { 25 | del(['dist/**/*']); 26 | }); 27 | 28 | gulp.task('lint',function() { 29 | return gulp.src('src/**/*.js') 30 | .pipe(jshint()) 31 | .pipe(jshint.reporter('jshint-stylish')) 32 | .pipe(jshint.reporter('fail')); 33 | }); 34 | 35 | gulp.task('test:lint',function() { 36 | return gulp.src('test/**/*.js') 37 | .pipe(jshint()) 38 | .pipe(jshint.reporter('jshint-stylish')) 39 | .pipe(jshint.reporter('fail')); 40 | }); 41 | 42 | gulp.task('test:run',function() { 43 | return gulp.src('test/**/*.js') 44 | .pipe(jasmine({ 45 | includeStackTrace: true 46 | })); 47 | }); 48 | 49 | gulp.task('test', function() { 50 | seq('test:lint','test:run') 51 | }); 52 | 53 | gulp.task('build',function(cb) { 54 | seq(['lint','clean'],'test','compile',cb); 55 | }); 56 | 57 | gulp.task('watch',function() { 58 | gulp.watch(['src/**/*'],['build']); 59 | gulp.watch(['test/**/*.js'],['test']); 60 | }); 61 | 62 | gulp.task('default',['build','watch']); 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@taptrack/ndef", 3 | "version": "1.0.3", 4 | "description": "Ndef message parser and composer", 5 | "main": "dist/ndef.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "TapTrack", 10 | "license": "Apache-2.0", 11 | "repository": { 12 | "type":"git", 13 | "url": "https://github.com/TapTrack/NdefJS.git" 14 | }, 15 | "devDependencies": { 16 | "del": "^2.2.0", 17 | "gulp": "^3.9.1", 18 | "gulp-debug": "^2.1.2", 19 | "gulp-jasmine": "^2.3.0", 20 | "gulp-jshint": "^2.0.1", 21 | "gulp-rename": "^1.2.2", 22 | "gulp-uglify": "^1.5.3", 23 | "jshint": "^2.9.2", 24 | "jshint-stylish": "^2.2.0", 25 | "merge-stream": "^1.0.0", 26 | "run-sequence": "^1.2.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ndef.js: -------------------------------------------------------------------------------- 1 | 2 | (function (root, factory) { 3 | if (typeof define === 'function' && define.amd) { 4 | // AMD 5 | define([], factory); 6 | } else if (typeof exports === 'object') { 7 | module.exports = factory(); 8 | } else { 9 | // Browser globals (root is window) 10 | root.Ndef= factory(); 11 | } 12 | }(this, function () { 13 | /** 14 | * Utility functions used for working with NDEF 15 | * 16 | * @constructor 17 | * @throws if you attempt to intsantiate NdefUtils 18 | */ 19 | var NdefUtils = function() { 20 | throw "Dont construct NdefUtils!"; 21 | }; 22 | 23 | /** 24 | * Check if a value has a bit flag 25 | * 26 | * @param {!integer} value 27 | * @param {!integer} flag 28 | * @return {!boolean} true if flag is set, false otherwise 29 | */ 30 | NdefUtils.hasFlag = function(value,flag) { 31 | return (value & flag) === flag; 32 | }; 33 | 34 | /** 35 | * Get the type from this NDEF Record 36 | * 37 | * @constructor 38 | * @this {NdefRecord} 39 | * @param {!boolean} chunked if this record has a chunked flag 40 | * @param {!integer} tnf the Type Name Format for this record 41 | * @param {!Uint8Array} type the type data for this record 42 | * @param {?Uint8Array} id the id for this record, if present 43 | * @param {!payload} payload the payload contained in this record 44 | */ 45 | var NdefRecord = function (chunked,tnf,type,id,payload) { 46 | if(typeof chunked == "undefined" || 47 | typeof tnf == "undefined" || 48 | typeof type == "undefined" || 49 | typeof id == "undefined" || 50 | typeof payload == "undefined") { 51 | throw "Parameters missing from NdefRecord constructor call"; 52 | } 53 | 54 | if(chunked === null || 55 | tnf === null || 56 | type === null || 57 | payload === null) { 58 | throw "Passed null to non-nullable constructor parameter in NdefRecord"; 59 | } 60 | 61 | 62 | this.chunked = chunked; 63 | this.tnf = tnf; 64 | this.type = type; 65 | this.id = id; 66 | this.payload = payload; 67 | }; 68 | 69 | /** 70 | * These flags and masks are used for packing/unpacking 71 | * the record header 72 | */ 73 | NdefRecord.HDR_FLAG_MESSAGE_BEGIN = 0x80; 74 | NdefRecord.HDR_FLAG_MESSAGE_END = 0x40; 75 | NdefRecord.HDR_FLAG_CHUNKED = 0x20; 76 | NdefRecord.HDR_FLAG_SHORT_RECORD = 0x10; 77 | NdefRecord.HDR_FLAG_ID_LENGTH_PRESENT = 0x08; 78 | NdefRecord.HDR_MASK_TNF = 0x07; 79 | 80 | /** 81 | * Type Name Format values 82 | */ 83 | NdefRecord.TNF_EMPTY = 0x00; 84 | NdefRecord.TNF_WELL_KNOWN = 0x01; 85 | NdefRecord.TNF_MEDIA = 0x02; 86 | NdefRecord.TNF_ABSOLUTE_URI = 0x03; 87 | NdefRecord.TNF_EXTERNAL = 0x04; 88 | NdefRecord.TNF_UNKNOWN = 0x05; 89 | NdefRecord.TNF_UNCHANGED = 0x06; 90 | NdefRecord.TNF_RESERVED = 0x07; 91 | 92 | /** 93 | * NDEF URI Prefixes 94 | */ 95 | NdefRecord.URI_PRE_NONE = 0x00; 96 | NdefRecord.URI_PRE_HTTP_WWW = 0x01; 97 | NdefRecord.URI_PRE_HTTPS_WWW = 0x02; 98 | NdefRecord.URI_PRE_HTTP = 0x03; 99 | NdefRecord.URI_PRE_HTTPS = 0x04; 100 | NdefRecord.URI_PRE_TEL = 0x05; 101 | NdefRecord.URI_PRE_MAILTO = 0x06; 102 | NdefRecord.URI_PRE_FTP_ANON = 0x07; 103 | NdefRecord.URI_PRE_FTP_FTP = 0x08; // ftp://ftp 104 | NdefRecord.URI_PRE_FTPS = 0x09; 105 | NdefRecord.URI_PRE_SFTP = 0x0A; 106 | NdefRecord.URI_PRE_SMB = 0x0B; 107 | NdefRecord.URI_PRE_NFS = 0x0C; 108 | NdefRecord.URI_PRE_FTP = 0x0D; 109 | NdefRecord.URI_PRE_DAV = 0x0E; 110 | NdefRecord.URI_PRE_NEWS = 0x0F; 111 | NdefRecord.URI_PRE_TELNET = 0x10; 112 | NdefRecord.URI_PRE_IMAP = 0x11; 113 | NdefRecord.URI_PRE_RTSP = 0x12; 114 | NdefRecord.URI_PRE_URN = 0x13; 115 | NdefRecord.URI_PRE_POP = 0x14; 116 | NdefRecord.URI_PRE_SIP = 0x15; 117 | NdefRecord.URI_PRE_SIPS = 0x16; 118 | NdefRecord.URI_PRE_TFTP = 0x17; 119 | NdefRecord.URI_PRE_BTSPP = 0x18; 120 | NdefRecord.URI_PRE_BTL2CAP = 0x19; 121 | NdefRecord.URI_PRE_BTGOEP = 0x1A; 122 | NdefRecord.URI_PRE_TCPOBEX = 0x1B; 123 | NdefRecord.URI_PRE_IRDAOBEX = 0x1C; 124 | NdefRecord.URI_PRE_FILE = 0x1D; 125 | NdefRecord.URI_PRE_URN_EPC_ID = 0x1E; 126 | NdefRecord.URI_PRE_URN_EPC_TAG = 0x1F; 127 | NdefRecord.URI_PRE_URN_EPC_PAT = 0x20; 128 | NdefRecord.URI_PRE_URN_EPC_RAW = 0x21; 129 | NdefRecord.URI_PRE_URN_EPC = 0x22; 130 | NdefRecord.URI_PRE_URN_NFC = 0x23; 131 | 132 | /** 133 | * Indicate if this record has its chunked flag set 134 | * 135 | * @return {!boolean} isChunked; 136 | */ 137 | NdefRecord.prototype.isChunked = function() { 138 | return this.chunked; 139 | }; 140 | 141 | /** 142 | * Get the TNF for this record 143 | * 144 | * @return {!integer} tnf; 145 | */ 146 | NdefRecord.prototype.getTnf = function() { 147 | return this.tnf; 148 | }; 149 | 150 | /** 151 | * Get the type from this NDEF Record 152 | * 153 | * @return {!Uint8Array} type; 154 | */ 155 | NdefRecord.prototype.getType = function() { 156 | return this.type; 157 | }; 158 | 159 | /** 160 | * Get the id from this NDEF Record 161 | * 162 | * @return {?Uint8Array} id; 163 | */ 164 | NdefRecord.prototype.getId = function() { 165 | return this.id; 166 | }; 167 | 168 | /** 169 | * Get the payload from this NDEF Record 170 | * 171 | * @return {!Uint8Array} payload; 172 | */ 173 | NdefRecord.prototype.getPayload = function() { 174 | return this.payload; 175 | }; 176 | 177 | /** 178 | * Convert an NDEF Record to a byte array for writing 179 | * 180 | * @param {boolean} isBegin if this record starts a message 181 | * @param {boolean} isEnd if this record concludes a message 182 | * @return {Uint8Array} byte array representation 183 | */ 184 | NdefRecord.prototype.toByteArray = function(isBegin, isEnd) { 185 | var shortRecord = true; 186 | var hasId = true; 187 | 188 | if(this.payload.length >= 255) { 189 | shortRecord = false; 190 | } 191 | 192 | if(typeof this.id === "undefined" || this.id === null || this.id.length === 0) { 193 | hasId = false; 194 | } 195 | 196 | var messageSize = 2; // control byte, type length 197 | 198 | messageSize += this.type.length; 199 | if(shortRecord) { 200 | messageSize += 1; // single byte length 201 | } 202 | else { 203 | messageSize += 4; // four byte length 204 | } 205 | 206 | if(hasId) { 207 | messageSize += 1; // id length 208 | messageSize += this.id.length; 209 | } 210 | 211 | messageSize += this.payload.length; 212 | 213 | var result = new Uint8Array(messageSize); 214 | 215 | // this mask should be unecessary 216 | var firstByte = (this.tnf & NdefRecord.HDR_MASK_TNF); 217 | 218 | if(shortRecord) { 219 | firstByte |= NdefRecord.HDR_FLAG_SHORT_RECORD; 220 | } 221 | 222 | if(hasId) { 223 | firstByte |= NdefRecord.HDR_FLAG_ID_LENGTH_PRESENT; 224 | } 225 | 226 | if(isBegin) { 227 | firstByte |= NdefRecord.HDR_FLAG_MESSAGE_BEGIN; 228 | } 229 | 230 | if(isEnd) { 231 | firstByte |= NdefRecord.HDR_FLAG_MESSAGE_END; 232 | } 233 | 234 | if(this.isChunked()) { 235 | firstByte |= NdefRecord.HDR_FLAG_CHUNKED; 236 | } 237 | 238 | var count = 0; 239 | 240 | result[count++] = firstByte; 241 | result[count++] = this.type.length; 242 | 243 | if(shortRecord) { 244 | result[count++] = 0xff & this.payload.length; 245 | } 246 | else { 247 | result[count++] = 0xff & (this.payload.length >>> 24); 248 | result[count++] = 0xff & (this.payload.length >>> 16); 249 | result[count++] = 0xff & (this.payload.length >>> 8); 250 | result[count++] = 0xff & (this.payload.length >>> 0); 251 | } 252 | 253 | if(hasId) { 254 | result[count++] = 0xff & this.id.length; 255 | } 256 | 257 | //add type bytes 258 | for(var typeCntr = 0; typeCntr < this.type.length; typeCntr++) { 259 | result[count++] = this.type[typeCntr]; 260 | } 261 | 262 | if(hasId) { 263 | for(var idCntr = 0; idCntr < this.id.length; idCntr++) { 264 | result[count++] = this.id[idCntr]; 265 | } 266 | } 267 | 268 | for(var pldCntr = 0; pldCntr < this.payload.length; pldCntr++) { 269 | result[count++] = this.payload[pldCntr]; 270 | } 271 | 272 | if(result.length !== count) { 273 | throw "NDEF Record was not successfully generated"; 274 | } 275 | 276 | return result; 277 | }; 278 | 279 | /** 280 | * Constructs an NdefMessage from an array of NdefRecord 281 | * 282 | * @constructor 283 | * @param {!NdefRecord[]} ndefRecords the records this message contains 284 | */ 285 | var NdefMessage = function(ndefRecords) { 286 | if(typeof ndefRecords === "undefined" || 287 | ndefRecords === null || 288 | ndefRecords.length === 0) { 289 | throw "You must supply a non-zero length array of NdefRecords to construct an NdefMessage"; 290 | } 291 | this.ndefRecords = ndefRecords; 292 | }; 293 | 294 | 295 | /** 296 | * Returns the array of records this NdefMessage contains 297 | * 298 | * @return {!NdefRecord[]} the NDEF records contained 299 | */ 300 | NdefMessage.prototype.getRecords = function() { 301 | return this.ndefRecords; 302 | }; 303 | 304 | /** 305 | * Returns a byte array of the message ready to be written to a tag 306 | * 307 | * @return {!Uint8Array} byte array representation 308 | */ 309 | NdefMessage.prototype.toByteArray = function() { 310 | var result = new Uint8Array(0); 311 | for(var i = 0; i < this.ndefRecords.length; i++) { 312 | // this is very inefficient, but it shouldn't really matter 313 | // unless very large multi-record messages are being constructed 314 | var recordBytes = this.ndefRecords[i].toByteArray(i===0, i===(this.ndefRecords.length - 1)); 315 | var grownArray = new Uint8Array(result.length + recordBytes.length); 316 | grownArray.set(result); 317 | grownArray.set(recordBytes,result.length); 318 | result = grownArray; 319 | } 320 | return result; 321 | }; 322 | 323 | /** 324 | * Creates an NdefMessage from a byte array 325 | * 326 | * @param {!Uint8Array} bytes 327 | * @return {?NdefMessage} resulting message, null if a parse error occured 328 | */ 329 | NdefMessage.fromBytes = function(bytes) { 330 | if(typeof bytes === "undefined" || 331 | bytes === null) { 332 | throw "Bytes must be defined and non-null"; 333 | } 334 | // fixes odd issue when normal arrays are passed 335 | bytes = new Uint8Array(bytes); 336 | // theoretical minimum is 3 bytes 337 | if(bytes.length < 3) { 338 | throw "Byte array is too short to contain any kind of NDEF message"; 339 | } 340 | 341 | 342 | var throwOnMsgTooShort = function(bytes, idx) { 343 | if(bytes.length < (idx+1)) { 344 | throw "Message ended abruptly, trying to access index "+idx+" from an array of "+bytes.length+" items."; 345 | } 346 | }; 347 | 348 | var ndefRecords = []; 349 | var done = false; 350 | var foundStart = false; 351 | 352 | var currentIndex = 0; 353 | while(!done) { 354 | if(currentIndex >= bytes.length) { 355 | // ran out of bytes before message ended 356 | throw "Ran out of bytes before message started"; 357 | } 358 | 359 | var recordFirstByte = bytes[currentIndex]; 360 | 361 | 362 | // check for message beginning 363 | foundStart = foundStart || NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_MESSAGE_BEGIN); 364 | 365 | if(foundStart) { 366 | done = NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_MESSAGE_END); 367 | var isChunked = NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_CHUNKED); 368 | var isShortRecord = NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_SHORT_RECORD); 369 | var hasIdLength = NdefUtils.hasFlag(recordFirstByte,NdefRecord.HDR_FLAG_ID_LENGTH_PRESENT); 370 | 371 | var tnf = recordFirstByte & NdefRecord.HDR_MASK_TNF; 372 | 373 | var typeIdx = currentIndex + 1; 374 | throwOnMsgTooShort(bytes,typeIdx); 375 | var typeLength = bytes[typeIdx]; 376 | 377 | var payloadLength = 0; 378 | var payloadLengthLength = isShortRecord ? 1 : 4; 379 | var payloadLengthStartIdx = typeIdx+1; 380 | 381 | if(isShortRecord) { 382 | throwOnMsgTooShort(bytes,payloadLengthStartIdx); 383 | payloadLength = bytes[payloadLengthStartIdx]; 384 | } 385 | else { 386 | throwOnMsgTooShort(bytes,payloadLengthStartIdx+3); 387 | var valueArr = new Uint32Array(bytes.slice(payloadLengthStartIdx,payloadStartIdx+4)); 388 | payloadLength = valueArr[0]; 389 | } 390 | 391 | var idLength = 0; 392 | var idLengthStartIdx = payloadLengthStartIdx+payloadLengthLength; 393 | 394 | throwOnMsgTooShort(bytes,idLengthStartIdx); 395 | if(hasIdLength) { 396 | idLength = bytes[idLengthStartIdx]; 397 | } 398 | 399 | var idLengthEndIdx = idLengthStartIdx + (hasIdLength ? 1 : 0); 400 | var typeEndIdx = idLengthEndIdx + typeLength; 401 | throwOnMsgTooShort(bytes,typeEndIdx); 402 | var type = new Uint8Array(bytes.buffer,idLengthEndIdx,typeLength); 403 | 404 | var idStartIdx = typeEndIdx; 405 | throwOnMsgTooShort(bytes,idStartIdx+idLength); 406 | var id = new Uint8Array(bytes.buffer,idStartIdx,idLength); 407 | 408 | var payloadStartIdx = idStartIdx+idLength; 409 | throwOnMsgTooShort(bytes,payloadStartIdx+payloadLength-1); 410 | var payload = new Uint8Array(bytes.buffer,payloadStartIdx,payloadLength); 411 | 412 | currentIndex = payloadStartIdx+payloadLength; 413 | ndefRecords.push(new NdefRecord(isChunked,tnf,type,id,payload)); 414 | } 415 | else { 416 | currentIndex++; 417 | } 418 | } 419 | 420 | return new NdefMessage(ndefRecords); 421 | }; 422 | 423 | /** 424 | * Utility functions for Ndef operations and debugging 425 | * 426 | * @version 1.0.2 427 | */ 428 | var NdefRecordUtils = function() { 429 | throw "Do not instantiate ndef record utils"; 430 | }; 431 | 432 | /** 433 | * Resolves a URI into the appropriate NDEF record using 434 | * the standard URI prefixes 435 | * 436 | * @param {string} uri uri to write 437 | * @returns {NdefRecord} record with the uri prefix extracted 438 | */ 439 | NdefRecordUtils.createUriRecord = function(uri) { 440 | var parsed = NdefRecordUtils.resolveUriToPrefix(uri); 441 | var prefixCode = parsed.prefixCode; 442 | var content = parsed.content; 443 | 444 | var contentArray = NdefRecordUtils.stringToUint8Array(content); 445 | 446 | var payload = new Uint8Array(contentArray.length+1); 447 | payload[0] = prefixCode; 448 | payload.set(contentArray,1); 449 | 450 | return new NdefRecord(false,NdefRecord.TNF_WELL_KNOWN,new Uint8Array([0x55]),null,payload); 451 | }; 452 | 453 | /** 454 | * Resolves a string into the appropriate UTF8 455 | * text record 456 | * 457 | * @param {string} content record contents 458 | * @param {?string} language language code for record 459 | * @returns {NdefRecord} 460 | */ 461 | NdefRecordUtils.createTextRecord = function(content, language) { 462 | 463 | var contentArray = NdefRecordUtils.stringToUint8Array(content); 464 | if(typeof language === "undefined") { 465 | language = "en"; 466 | } 467 | var languageArray = NdefRecordUtils.stringToUint8Array(language); 468 | 469 | 470 | var payload = new Uint8Array(contentArray.length+languageArray.length+1); 471 | payload[0] = languageArray.length; 472 | payload.set(languageArray,1); 473 | payload.set(contentArray,1+languageArray.length); 474 | 475 | return new NdefRecord(false,NdefRecord.TNF_WELL_KNOWN,new Uint8Array([0x54]),null,payload); 476 | }; 477 | 478 | /** 479 | * Internal function for converting a string 480 | * into a Uint8Array for creating NdefRecords 481 | * 482 | * @param {string} string record contents 483 | * @return {Uint8Array} binary representation 484 | */ 485 | NdefRecordUtils.stringToUint8Array = function(string) { 486 | var escstr = encodeURIComponent(string); 487 | var binstr = escstr.replace(/%([0-9A-F]{2})/g, function(match, p1) { 488 | return String.fromCharCode('0x' + p1); 489 | }); 490 | var ua = new Uint8Array(binstr.length); 491 | Array.prototype.forEach.call(binstr, function (ch, i) { 492 | ua[i] = ch.charCodeAt(0); 493 | }); 494 | return ua; 495 | }; 496 | 497 | /** 498 | * Internal function for converting a Uint8Array 499 | * into a string for parsing NdefRecords 500 | * 501 | * @param {Uint8Array} binary representation 502 | * @return {string} string record contents 503 | */ 504 | NdefRecordUtils.uint8ArrayToString = function(arr) { 505 | var binstr = Array.prototype.map.call(arr, function (ch) { 506 | return String.fromCharCode(ch); 507 | }).join(''); 508 | 509 | var escstr = binstr.replace(/(.)/g, function (m, p) { 510 | var code = p.charCodeAt(0).toString(16).toUpperCase(); 511 | if (code.length < 2) { 512 | code = '0' + code; 513 | } 514 | return '%' + code; 515 | }); 516 | return decodeURIComponent(escstr); 517 | }; 518 | 519 | /** 520 | * Resolves a URI into the appropriate prefix/data 521 | * pair for NDEF writing 522 | * 523 | * @param {string} uri string of supposed uri 524 | * @returns { prefixCode: int, prefix: string, content: string, fullUri: string} 525 | */ 526 | NdefRecordUtils.resolveUriString = function(uri) { 527 | return NdefRecordUtils.resolveUriToPrefix(uri); 528 | }; 529 | 530 | /** 531 | * Resolves a URI into the appropriate prefix/data 532 | * pair for NDEF writing 533 | * 534 | * @deprecated Use NdefRecordUtils.resolveUriString instead 535 | * @param uri string of supposed uri 536 | * @returns { prefixCode: int, prefix: string, content: string, fullUri: string} 537 | */ 538 | NdefRecordUtils.resolveUriToPrefix = function(url) { 539 | var u = url; 540 | var stripper = function(prefix,prefixCode,fullUri) { 541 | return { 542 | "prefixCode": prefixCode, 543 | "prefix": prefix, 544 | "content": fullUri.slice(prefix.length), 545 | "fullUri": fullUri 546 | }; 547 | }; 548 | 549 | if(u.startsWith("http")) { 550 | if(u.startsWith("https://www.")) { 551 | return stripper("https://www.",NdefRecord.URI_PRE_HTTPS_WWW,u); 552 | } else if(u.startsWith("https://")) { 553 | return stripper("https://",NdefRecord.URI_PRE_HTTPS,u); 554 | } else if(u.startsWith("http://www.")) { 555 | return stripper("http://www.",NdefRecord.URI_PRE_HTTP_WWW,u); 556 | } else if(u.startsWith("http://")) { 557 | return stripper("http://",NdefRecord.URI_PRE_HTTP,u); 558 | } 559 | } 560 | else if(u.startsWith("ftp")) { 561 | if(u.startsWith("ftp://ftp.")) { 562 | return stripper("ftp://ftp.",NdefRecord.URI_PRE_FTP_FTP,u); 563 | } 564 | else if (u.startsWith("ftps://")) { 565 | return stripper("ftps://",NdefRecord.URI_PRE_FTPS,u); 566 | } 567 | else if (u.startsWith("ftp://anonymous:anonymous@")) { 568 | return stripper("ftp://anonymous:anonymous@",NdefRecord.URI_PRE_FTP_ANON,u); 569 | } 570 | else if(u.startsWith("ftp://")) { 571 | return stripper("ftp://",NdefRecord.URI_PRE_FTP,u); 572 | } 573 | } 574 | else if (u.startsWith("mailto:")) { 575 | return stripper("mailto:",NdefRecord.URI_PRE_MAILTO,u); 576 | } 577 | else if (u.startsWith("tel:")) { 578 | return stripper("tel:",NdefRecord.URI_PRE_TEL,u); 579 | } 580 | else if (u.startsWith("sftp://")) { 581 | return stripper("sftp://",NdefRecord.URI_PRE_SFTP,u); 582 | } 583 | else if (u.startsWith("smb://")) { 584 | return stripper("smb://",NdefRecord.URI_PRE_SMB,u); 585 | } 586 | else if (u.startsWith("nfs://")) { 587 | return stripper("nfs://",NdefRecord.URI_PRE_NFS,u); 588 | } 589 | else if (u.startsWith("dav://")) { 590 | return stripper("dav://",NdefRecord.URI_PRE_DAV,u); 591 | } 592 | else if (u.startsWith("news:")) { 593 | return stripper("news:",NdefRecord.URI_PRE_NEWS,u); 594 | } 595 | else if (u.startsWith("telnet://")) { 596 | return stripper("telnet://",NdefRecord.URI_PRE_TELNET,u); 597 | } 598 | else if (u.startsWith("imap:")) { 599 | return stripper("imap:",NdefRecord.URI_PRE_IMAP,u); 600 | } 601 | else if (u.startsWith("rtsp://")) { 602 | return stripper("rtsp://",NdefRecord.URI_PRE_RTSP,u); 603 | } 604 | else if (u.startsWith("pop:")) { 605 | return stripper("pop:",NdefRecord.URI_PRE_POP,u); 606 | } 607 | else if (u.startsWith("sip:")) { 608 | return stripper("sip:",NdefRecord.URI_PRE_SIP,u); 609 | } 610 | else if (u.startsWith("sips:")) { 611 | return stripper("sips:",NdefRecord.URI_PRE_SIPS,u); 612 | } 613 | else if (u.startsWith("tftp:")) { 614 | return stripper("tftp:",NdefRecord.URI_PRE_TFTP,u); 615 | } 616 | else if (u.startsWith("btspp://")) { 617 | return stripper("btspp://",NdefRecord.URI_PRE_BTSPP,u); 618 | } 619 | else if (u.startsWith("btl2cap://")) { 620 | return stripper("btl2cap://",NdefRecord.URI_PRE_BTL2CAP,u); 621 | } 622 | else if (u.startsWith("btgoep://")) { 623 | return stripper("btgoep://",NdefRecord.URI_PRE_BTGOEP,u); 624 | } 625 | else if (u.startsWith("tcpobex://")) { 626 | return stripper("tcpobex://",NdefRecord.URI_PRE_TCPOBEX,u); 627 | } 628 | else if (u.startsWith("irdaobex://")) { 629 | return stripper("irdaobex://",NdefRecord.URI_PRE_IRDAOBEX,u); 630 | } 631 | else if (u.startsWith("file://")) { 632 | return stripper("file://",NdefRecord.URI_PRE_FILE,u); 633 | } 634 | else if (u.startsWith("urn:epc:id:")) { 635 | return stripper("urn:epc:id:",NdefRecord.URI_PRE_URN_EPC_ID,u); 636 | } 637 | else if (u.startsWith("urn:epc:tag:")) { 638 | return stripper("urn:epc:tag:",NdefRecord.URI_PRE_URN_EPC_TAG,u); 639 | } 640 | else if (u.startsWith("urn:epc:pat:")) { 641 | return stripper("urn:epc:pat:",NdefRecord.URI_PRE_URN_EPC_PAT,u); 642 | } 643 | else if (u.startsWith("urn:epc:raw:")) { 644 | return stripper("urn:epc:raw:",NdefRecord.URI_PRE_EPC_RAW,u); 645 | } 646 | else if (u.startsWith("urn:epc:")) { 647 | return stripper("urn:epc:",NdefRecord.URI_PRE_URN_EPC,u); 648 | } 649 | else if (u.startsWith("urn:nfc:")) { 650 | return stripper("urn:nfc:",NdefRecord.URI_PRE_URN_NFC,u); 651 | } 652 | else if (u.startsWith("urn:")) { 653 | return stripper("urn:",NdefRecord.URI_PRE_URN,u); 654 | } 655 | 656 | return stripper("",NdefRecord.URI_PRE_NONE,url); 657 | }; 658 | 659 | /** 660 | * Resolves the completely from a WELL_KNOWN URI record 661 | * by prepending the appropriate URI prefix. 662 | * 663 | * @throws if record isn't a WELL_KNOWN URI 664 | * @throws if the record prefix isn't known 665 | * @param {NdefRecord} record to extract string 666 | * @return {string} resulting URI string 667 | */ 668 | NdefRecordUtils.resolveUriRecordToString = function(ndefRecord) { 669 | return NdefRecordUtils.resolveUrlFromPrefix(ndefRecord); 670 | }; 671 | 672 | /** 673 | * Resolves the completely from a WELL_KNOWN URI record 674 | * by prepending the appropriate URI prefix. 675 | * 676 | * @deprecated use NdefRecordUtils.resolveUriToString instead 677 | * @throws if record isn't a WELL_KNOWN URI 678 | * @throws if the record prefix isn't known 679 | * @param {NdefRecord} record to extract string 680 | * @return {string} resulting URI string 681 | */ 682 | NdefRecordUtils.resolveUrlFromPrefix = function(ndefRecord) { 683 | if(ndefRecord.getTnf() !== NdefRecord.TNF_WELL_KNOWN || (ndefRecord.getType())[0] !== 0x55) { 684 | throw "Not a WELL_KNOWN URI record"; 685 | } 686 | else { 687 | var payload = ndefRecord.getPayload(); 688 | var prefixCode = payload[0]; 689 | var data = payload.slice(1); 690 | var prefix = ""; 691 | 692 | switch(prefixCode) { 693 | case NdefRecord.URI_PRE_NONE: 694 | break; 695 | case NdefRecord.URI_PRE_HTTP_WWW: 696 | prefix = "http://www."; 697 | break; 698 | case NdefRecord.URI_PRE_HTTPS_WWW: 699 | prefix = "https://www."; 700 | break; 701 | case NdefRecord.URI_PRE_HTTP: 702 | prefix = "http://"; 703 | break; 704 | case NdefRecord.URI_PRE_HTTPS: 705 | prefix = "https://"; 706 | break; 707 | case NdefRecord.URI_PRE_TEL: 708 | prefix = "tel:"; 709 | break; 710 | case NdefRecord.URI_PRE_MAILTO: 711 | prefix = "mailto:"; 712 | break; 713 | case NdefRecord.URI_PRE_FTP_ANON: 714 | prefix = "ftp://anonymous:anonymous@"; 715 | break; 716 | case NdefRecord.URI_PRE_FTP_FTP: 717 | prefix = "ftp://ftp."; 718 | break; // ftp://ftp 719 | case NdefRecord.URI_PRE_FTPS: 720 | prefix = "ftps://"; 721 | break; 722 | case NdefRecord.URI_PRE_SFTP: 723 | prefix = "sftp://"; 724 | break; 725 | case NdefRecord.URI_PRE_SMB: 726 | prefix = "smb://"; 727 | break; 728 | case NdefRecord.URI_PRE_NFS: 729 | prefix = "nfs://"; 730 | break; 731 | case NdefRecord.URI_PRE_FTP: 732 | prefix = "ftp://"; 733 | break; 734 | case NdefRecord.URI_PRE_DAV: 735 | prefix = "dav://"; 736 | break; 737 | case NdefRecord.URI_PRE_NEWS: 738 | prefix = "news:"; 739 | break; 740 | case NdefRecord.URI_PRE_TELNET: 741 | prefix = "telnet://"; 742 | break; 743 | case NdefRecord.URI_PRE_IMAP: 744 | prefix = "imap:"; 745 | break; 746 | case NdefRecord.URI_PRE_RTSP: 747 | prefix = "rtsp://"; 748 | break; 749 | case NdefRecord.URI_PRE_URN: 750 | prefix = "urn:"; 751 | break; 752 | case NdefRecord.URI_PRE_POP: 753 | prefix = "pop:"; 754 | break; 755 | case NdefRecord.URI_PRE_SIP: 756 | prefix = "sip:"; 757 | break; 758 | case NdefRecord.URI_PRE_SIPS: 759 | prefix = "sips:"; 760 | break; 761 | case NdefRecord.URI_PRE_TFTP: 762 | prefix = "tftp:"; 763 | break; 764 | case NdefRecord.URI_PRE_BTSPP: 765 | prefix = "btspp://"; 766 | break; 767 | case NdefRecord.URI_PRE_BTL2CAP: 768 | prefix = "btl2cap://"; 769 | break; 770 | case NdefRecord.URI_PRE_BTGOEP: 771 | prefix = "btgoep://"; 772 | break; 773 | case NdefRecord.URI_PRE_TCPOBEX: 774 | prefix = "tcpobex://"; 775 | break; 776 | case NdefRecord.URI_PRE_IRDAOBEX: 777 | prefix = "irdaobex://"; 778 | break; 779 | case NdefRecord.URI_PRE_FILE: 780 | prefix = "file://"; 781 | break; 782 | case NdefRecord.URI_PRE_URN_EPC_ID: 783 | prefix = "urn:epc:id:"; 784 | break; 785 | case NdefRecord.URI_PRE_URN_EPC_TAG: 786 | prefix = "urn:epc:tag:"; 787 | break; 788 | case NdefRecord.URI_PRE_URN_EPC_PAT: 789 | prefix = "urn:epc:pat:"; 790 | break; 791 | case NdefRecord.URI_PRE_URN_EPC_RAW: 792 | prefix = "urn:epc:raw:"; 793 | break; 794 | case NdefRecord.URI_PRE_URN_EPC: 795 | prefix = "urn:epc:"; 796 | break; 797 | case NdefRecord.URI_PRE_URN_NFC: 798 | prefix = "urn:nfc:"; 799 | break; 800 | default: 801 | throw "Invalid URI code"; 802 | } 803 | 804 | return prefix + NdefRecordUtils.uint8ArrayToString(data); 805 | } 806 | }; 807 | 808 | /** 809 | * Resolves a WELL_KNOWN text record 810 | * extracting the language code and content 811 | * 812 | * @throws if record isn't a WELL_KNOWN TEXT 813 | * @throws if the language code bytes are invalid 814 | * @returns {language: language code string, content: text contained} or null 815 | */ 816 | NdefRecordUtils.resolveTextRecord = function(ndefRecord) { 817 | if(ndefRecord.getTnf() !== NdefRecord.TNF_WELL_KNOWN || (ndefRecord.getType())[0] !== 0x54) { 818 | throw "Not a WELL_KNOWN text record"; 819 | } 820 | else { 821 | var payload = ndefRecord.getPayload(); 822 | if(payload.length === 0) { 823 | throw "Missing error code"; 824 | } 825 | 826 | //masking out the utf bit 827 | var languageCodeLength = payload[0] & 0x3F; 828 | if(payload.length < (languageCodeLength+1)) { 829 | throw "Payload too short to contain language code"; 830 | } 831 | 832 | var language = NdefRecordUtils.uint8ArrayToString(payload.slice(1,1+languageCodeLength)); 833 | var content = ""; 834 | 835 | if(payload.length > 1+languageCodeLength) { 836 | content = NdefRecordUtils.uint8ArrayToString(payload.slice(1+languageCodeLength)); 837 | } 838 | 839 | return {language: language, content: content}; 840 | } 841 | }; 842 | 843 | var Ndef = {}; 844 | Ndef.Message = NdefMessage; 845 | Ndef.Record = NdefRecord; 846 | Ndef.Utils = NdefRecordUtils; 847 | return Ndef; 848 | })); 849 | -------------------------------------------------------------------------------- /test/testparser.js: -------------------------------------------------------------------------------- 1 | var Ndef = require('../src/ndef.js'); 2 | 3 | describe("Parser/composer self-test",function() { 4 | it("Should create and parse a basic text record",function() { 5 | var data = "TEST DATA"; 6 | var record = Ndef.Utils.createTextRecord(data,"en"); 7 | var message = new Ndef.Message([record]); 8 | var rawBytes = message.toByteArray(); 9 | var parsedMessage = Ndef.Message.fromBytes(rawBytes); 10 | var parsedRecords = parsedMessage.getRecords(); 11 | expect(parsedRecords.length).toEqual(1); 12 | expect(Ndef.Utils.resolveTextRecord(parsedRecords[0]).content).toEqual(data); 13 | }); 14 | 15 | it("Should create and parse a text record containing numbers",function() { 16 | var data = "TEST2"; 17 | var record = Ndef.Utils.createTextRecord(data,"en"); 18 | var message = new Ndef.Message([record]); 19 | var rawBytes = message.toByteArray(); 20 | var parsedMessage = Ndef.Message.fromBytes(rawBytes); 21 | var parsedRecords = parsedMessage.getRecords(); 22 | expect(parsedRecords.length).toEqual(1); 23 | expect(Ndef.Utils.resolveTextRecord(parsedRecords[0]).content).toEqual(data); 24 | }); 25 | 26 | it("Should create and parse a unicode text record",function() { 27 | var data = "寿司"; 28 | var record = Ndef.Utils.createTextRecord(data,"en"); 29 | var message = new Ndef.Message([record]); 30 | var rawBytes = message.toByteArray(); 31 | var parsedMessage = Ndef.Message.fromBytes(rawBytes); 32 | var parsedRecords = parsedMessage.getRecords(); 33 | expect(parsedRecords.length).toEqual(1); 34 | expect(Ndef.Utils.resolveTextRecord(parsedRecords[0]).content).toEqual(data); 35 | }); 36 | 37 | it("Should create and parse a basic url",function() { 38 | var data = "https://www.test.com"; 39 | var record = Ndef.Utils.createUriRecord(data); 40 | var message = new Ndef.Message([record]); 41 | var rawBytes = message.toByteArray(); 42 | var parsedMessage = Ndef.Message.fromBytes(rawBytes); 43 | var parsedRecords = parsedMessage.getRecords(); 44 | expect(parsedRecords.length).toEqual(1); 45 | expect(Ndef.Utils.resolveUriRecordToString(parsedRecords[0])).toEqual(data); 46 | }); 47 | 48 | it("Should create and parse a ndef record longer than 255 bytes",function() { 49 | var data = ""; 50 | for(var i = 0; i < 400; i++) { 51 | data += "a"; 52 | } 53 | 54 | var record = Ndef.Utils.createTextRecord(data); 55 | var message = new Ndef.Message([record]); 56 | var rawBytes = message.toByteArray(); 57 | var parsedMessage = Ndef.Message.fromBytes(rawBytes); 58 | var parsedRecords = parsedMessage.getRecords(); 59 | expect(parsedRecords.length).toEqual(1); 60 | expect(Ndef.Utils.resolveTextRecord(parsedRecords[0]).content).toEqual(data); 61 | }); 62 | 63 | it("Should create and parse a multi-record ndef message", function() { 64 | var textdata1 = "TEXT1"; 65 | var record1 = Ndef.Utils.createTextRecord(textdata1,"en"); 66 | var uridata = "http://www.google.com"; 67 | var record2 = Ndef.Utils.createUriRecord(uridata); 68 | var textdata2 = "TEXT2"; 69 | var record3 = Ndef.Utils.createTextRecord(textdata2,"en"); 70 | 71 | var message = new Ndef.Message([record1,record2,record3]); 72 | var rawBytes = message.toByteArray(); 73 | var parsedMessage = Ndef.Message.fromBytes(rawBytes); 74 | var parsedRecords = parsedMessage.getRecords(); 75 | expect(parsedRecords.length).toEqual(3); 76 | expect(Ndef.Utils.resolveTextRecord(parsedRecords[0]).content).toEqual(textdata1); 77 | expect(Ndef.Utils.resolveUriRecordToString(parsedRecords[1])).toEqual(uridata); 78 | expect(Ndef.Utils.resolveTextRecord(parsedRecords[2]).content).toEqual(textdata2); 79 | 80 | }); 81 | 82 | it("Should create and parse an NDEF message with an external record with arbitrary contents", function() { 83 | uint8ArrayToHexString = function(data) { 84 | var hexString = ""; 85 | for(var x = 0; x < data.length; x++) { 86 | var hexValue = data[x].toString(16).toUpperCase(); 87 | if(data[x] <= 15) { 88 | // gives zero padding to hex values less than 16 89 | hexString = hexString.concat("0" + hexValue); 90 | } 91 | else { 92 | hexString = hexString.concat(hexValue); 93 | } 94 | } 95 | return hexString; 96 | }; 97 | 98 | var tnf = Ndef.Record.TNF_EXTERNAL; 99 | var type = Uint8Array.from([0x22,0x22,0x22]); 100 | var id = Uint8Array.from([0x32,0x05,0x10,0x24]); 101 | var payload = Uint8Array.from([0xBB,0xA0,0x45]); 102 | var chunked = false; 103 | 104 | var record = new Ndef.Record(chunked, tnf, type, id, payload); 105 | var message = new Ndef.Message([record]); 106 | var rawBytes = message.toByteArray(); 107 | var parsedMessage = Ndef.Message.fromBytes(rawBytes); 108 | var parsedRecords = parsedMessage.getRecords(); 109 | 110 | expect(parsedRecords.length).toEqual(1); 111 | 112 | expect(parsedRecords[0].getTnf()).toEqual(tnf); 113 | expect(parsedRecords[0].getType()).toEqual(type); 114 | expect(parsedRecords[0].getId()).toEqual(id); 115 | expect(parsedRecords[0].getPayload()).toEqual(payload); 116 | expect(parsedRecords[0].isChunked()).toEqual(chunked); 117 | }); 118 | }); 119 | --------------------------------------------------------------------------------