diff --git a/.gitignore b/.gitignore index 491fc35..6689d76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ node_modules lib +dist +.vscode +hue.json +package-lock.json diff --git a/Insomnia-collection.json b/Insomnia-collection.json new file mode 100644 index 0000000..541de91 --- /dev/null +++ b/Insomnia-collection.json @@ -0,0 +1 @@ +{"_type":"export","__export_format":4,"__export_date":"2021-04-14T20:30:22.423Z","__export_source":"insomnia.desktop.app:v2021.2.2","resources":[{"_id":"req_e583591e03c34937936c50ab2a1afaf8","parentId":"fld_9c27cdb6d26b4977b48500437245f8a6","modified":1617802927810,"created":1617780780384,"url":"{{host}}/api","name":"Create user","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"devicetype\": \"testuser\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_fcb68eafa3a844cb8e77bbe77148275a"}],"authentication":{},"metaSortKey":-1617780780384,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_9c27cdb6d26b4977b48500437245f8a6","parentId":"wrk_f4e45eadf6584a95991eb28e38af5375","modified":1608933738481,"created":1608933738481,"name":"Config","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1608933738481,"_type":"request_group"},{"_id":"wrk_f4e45eadf6584a95991eb28e38af5375","parentId":null,"modified":1607286293146,"created":1607285917895,"name":"Hue API","description":"","scope":"collection","_type":"workspace"},{"_id":"req_dc79184c6c7444709b0e6692f31590f8","parentId":"fld_9c27cdb6d26b4977b48500437245f8a6","modified":1611614372104,"created":1611508499534,"url":"{{ host }}/description.xml","name":"description.xml","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1611508499534,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_c3d2cad4798f43178b48494e5f345297","parentId":"fld_9c27cdb6d26b4977b48500437245f8a6","modified":1611518483149,"created":1608933749891,"url":"{{host}}/api/{{user}}/config","name":"Get configuration","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1608933749891,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f4307915e54a43f8b931946bb7f40bac","parentId":"fld_9c27cdb6d26b4977b48500437245f8a6","modified":1617180116945,"created":1617180113400,"url":"{{host}}/api/config","name":"Get configuration (unknown user)","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1608933749866,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f3dbea1e4c3b468caa77d9aa7c5ae5f7","parentId":"fld_9c27cdb6d26b4977b48500437245f8a6","modified":1611518561511,"created":1607286381012,"url":"{{host}}/api/{{user}}","name":"Get full state (datastore)","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1608933749841,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_3cb619d7c05c4ddfa1566d41eed17785","parentId":"fld_a9b215a5c0be4f26a41b5f64329b7169","modified":1618430925273,"created":1618430833459,"url":"{{host}}/api/{{user}}/scenes","name":"Create scene","description":"","method":"POST","body":{"mimeType":"application/json","text":"{ \n \"name\": \"Cozy dinner\", \n \"recycle\": false, \n \"group\": \"2\", \n \"type\": \"GroupScene\"\n} "},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_d29509bf41824535b017214511a3606a"}],"authentication":{},"metaSortKey":-1618430833459,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_a9b215a5c0be4f26a41b5f64329b7169","parentId":"wrk_f4e45eadf6584a95991eb28e38af5375","modified":1607286800345,"created":1607286800345,"name":"Scenes","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1607286800345,"_type":"request_group"},{"_id":"req_0564ef227c964bcf9fb8b6d728f3ba84","parentId":"fld_a9b215a5c0be4f26a41b5f64329b7169","modified":1618432135894,"created":1618428889816,"url":"{{host}}/api/{{user}}/scenes/8xqxsh0cWR3Vi12","name":"Get scene attributes and state","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1618428889816,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_3070708654e8477aa67a8a2524639723","parentId":"fld_a9b215a5c0be4f26a41b5f64329b7169","modified":1616160664863,"created":1607286808470,"url":"{{host}}/api/{{user}}/scenes","name":"Get all scenes","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1607286808470,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_77d0043bec734b95b3c7e048e5ff85fd","parentId":"fld_a9b215a5c0be4f26a41b5f64329b7169","modified":1618430843615,"created":1618430807500,"url":"{{host}}/api/{{user}}/scenes/1","name":"Delete scene","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1607286808420,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0f7a2514d0204742bd044ff3bbce8f5c","parentId":"fld_bcb438d586e245839562aa46bef24593","modified":1618430839786,"created":1618427039103,"url":"{{host}}/api/{{user}}/groups","name":"Create group","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"name\": \"FooRom\",\n\t\t\"type\": \"Room\",\n\t\t\"class\": \"Other\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_e47c5166bf034bc4a9810630e27b44a4"}],"authentication":{},"metaSortKey":-1618427091766,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_bcb438d586e245839562aa46bef24593","parentId":"wrk_f4e45eadf6584a95991eb28e38af5375","modified":1607286754492,"created":1607286754492,"name":"Groups","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1607286754492,"_type":"request_group"},{"_id":"req_2df977add89d4fd9af31216caa5f97c2","parentId":"fld_bcb438d586e245839562aa46bef24593","modified":1611518500455,"created":1607286771632,"url":"{{host}}/api/{{user}}/groups","name":"Get all groups","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1607286771632,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_33218c1b1f7341d6b92c578739d30f4c","parentId":"fld_bcb438d586e245839562aa46bef24593","modified":1618427169961,"created":1610144907315,"url":"{{host}}/api/{{user}}/groups/0","name":"Get group attributes and state","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1607286754686,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_ce727d78416f4206bf56abb22cb85f4d","parentId":"fld_bcb438d586e245839562aa46bef24593","modified":1618430845975,"created":1618427091716,"url":"{{host}}/api/{{user}}/groups/1","name":"Delete group","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1607286754636,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0860626b68a2468ea2a93c2901f752a9","parentId":"fld_6bbdb0bf5b884424935bb474898e39b2","modified":1617822489095,"created":1617822483252,"url":"{{host}}/api/{{user}}/lights","name":"Search new lights","description":"","method":"POST","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1617822483252,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_6bbdb0bf5b884424935bb474898e39b2","parentId":"wrk_f4e45eadf6584a95991eb28e38af5375","modified":1607286692923,"created":1607286692923,"name":"Lights","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1607286692923,"_type":"request_group"},{"_id":"req_aed80baf2c17446897e0ca1d1ec13d64","parentId":"fld_6bbdb0bf5b884424935bb474898e39b2","modified":1617826475221,"created":1617826454931,"url":"{{host}}/api/{{user}}/lights/new","name":"Get new lights","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1617801631818,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_2c0ba2e59f804dcfbb8a8e4452bc962f","parentId":"fld_6bbdb0bf5b884424935bb474898e39b2","modified":1611518523313,"created":1607286703848,"url":"{{host}}/api/{{user}}/lights","name":"Get all lights","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1607286905020,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_9ca5a68061c342298c29a953cbf23ad4","parentId":"fld_6bbdb0bf5b884424935bb474898e39b2","modified":1618083462196,"created":1607286904970,"url":"{{host}}/api/{{user}}/lights/0","name":"Get light attributes and state","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1607286904970,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_35aebfec1de24dc29a3be0010e23710d","parentId":"fld_6bbdb0bf5b884424935bb474898e39b2","modified":1618259698027,"created":1609327797786,"url":"{{host}}/api/{{user}}/lights/11/state","name":"Set light state","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n \"on\": true,\n \"bri\": 200\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_fbd75d3731854290a07a690d45afef96"}],"authentication":{},"metaSortKey":-1607286856720,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_3ec5d441f22740b09de2ff114b94a03b","parentId":"fld_6bbdb0bf5b884424935bb474898e39b2","modified":1618085232121,"created":1618083686719,"url":"{{host}}/api/{{user}}/lights/3","name":"Set light attribute (rename)","description":"","method":"PUT","body":{"mimeType":"application/json","text":""},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_fbd75d3731854290a07a690d45afef96"}],"authentication":{},"metaSortKey":-1607286832595,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_cc8cd8d7cb794332bb0d841247adb579","parentId":"fld_6bbdb0bf5b884424935bb474898e39b2","modified":1618430848756,"created":1618206806534,"url":"{{host}}/api/{{user}}/lights/3","name":"Delete light","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1607286832545,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_f0e6328cf4dcee429fa288c24a715b9ef79fa041","parentId":"wrk_f4e45eadf6584a95991eb28e38af5375","modified":1607286129322,"created":1607285917950,"name":"Base Environment","data":{"host":"192.168.2.108/api"},"dataPropertyOrder":{"&":["host"]},"color":null,"isPrivate":false,"metaSortKey":1607285917950,"_type":"environment"},{"_id":"jar_f0e6328cf4dcee429fa288c24a715b9ef79fa041","parentId":"wrk_f4e45eadf6584a95991eb28e38af5375","modified":1607285917952,"created":1607285917952,"name":"Default Jar","cookies":[],"_type":"cookie_jar"},{"_id":"spc_9c3bf55fa96e41559213e002e0a920ce","parentId":"wrk_f4e45eadf6584a95991eb28e38af5375","modified":1607285917900,"created":1607285917900,"fileName":"Hue","contents":"","contentType":"yaml","_type":"api_spec"},{"_id":"env_21e43374b0d4440d859f0756ef00f9f4","parentId":"env_f0e6328cf4dcee429fa288c24a715b9ef79fa041","modified":1618260598096,"created":1617783513399,"name":"Emulator","data":{"host":"http://127.0.0.1","user":"s7qf03cwlr0ipx63hei14t0i38him"},"dataPropertyOrder":{"&":["host","user"]},"color":"#e4cf62","isPrivate":false,"metaSortKey":1617783513400,"_type":"environment"}]} \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index 10c0e3d..874622b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,22 @@ # Hue bridge emulator -[![Build Status](https://travis-ci.org/tim-hellhake/hue-bridge-emulator.svg?branch=master)](https://travis-ci.org/tim-hellhake/hue-bridge-emulator) -[![dependencies](https://david-dm.org/tim-hellhake/hue-bridge-emulator.svg)](https://david-dm.org/tim-hellhake/hue-bridge-emulator) -[![devDependencies](https://david-dm.org/tim-hellhake/hue-bridge-emulator/dev-status.svg)](https://david-dm.org/tim-hellhake/hue-bridge-emulator?type=dev) -[![optionalDependencies](https://david-dm.org/tim-hellhake/hue-bridge-emulator/optional-status.svg)](https://david-dm.org/tim-hellhake/hue-bridge-emulator?type=optional) [![license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](LICENSE) -Emulate a hue bridge to interface with hue compatible devices like the Amazon Echo. +Node.js server to emulate a Philips Hue Bridge. -# How to use -See `./examples/color-bulb.js` for details. +This projects helps all developers for the Hue Bridge to simulate interactions with a Hue Bridge without even owning one. + +# How to compile +As the app is written in Typescript, the code needs to be compiled into the `dist` folder first. +The instructions below requires [`node`](https://nodejs.org/en/download/) being installed on your developer machine. +1. Fork/download the source code into the local folder of your choice +2. Open a Terminal and navigate to that root folder +3. Initiate the Node package environment with: `npm install` +4. The `package.json` has some script pre-configured to start right away with: `npm start dev` (development environment) + +# Implementation plan +- [x] Permanent storage (local JSON file to permanently store the bridge state) +- [x] Lights API +- [x] Groups API +- [x] Scenes API +- [ ] Error handler to provide proper feedback on wrong JSON body data diff --git a/examples/color-bulb.js b/examples/color-bulb.js deleted file mode 100644 index fa6afab..0000000 --- a/examples/color-bulb.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/.* - */ - -const { HueBridgeEmulator } = require('../lib/index.js'); - -// const hueBridgeEmulator = new HueBridgeEmulator({ port: 80, debug: true }); -const hueBridgeEmulator = new HueBridgeEmulator({ debug: true }); -hueBridgeEmulator.start(); -hueBridgeEmulator.addLight('foo', (key, value) => console.log(`foo.${key} => ${value}`)); -hueBridgeEmulator.addLight('bar'); diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index c58bb5b..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1541 +0,0 @@ -{ - "name": "hue-bridge-emulator", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "dev": true, - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", - "dev": true - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "@eslint/eslintrc": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", - "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - } - } - }, - "@types/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/connect": { - "version": "3.4.33", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", - "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.8", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", - "integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==", - "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "*", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.12.tgz", - "integrity": "sha512-EaEdY+Dty1jEU7U6J4CUWwxL+hyEGMkO5jan5gplfegUgCUsIUWqXxqw47uGjimeT4Qgkz/XUfwoau08+fgvKA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/mime": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", - "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", - "dev": true - }, - "@types/node": { - "version": "14.6.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", - "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==", - "dev": true - }, - "@types/qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", - "dev": true - }, - "@types/serve-static": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", - "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", - "dev": true, - "requires": { - "@types/express-serve-static-core": "*", - "@types/mime": "*" - } - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", - "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", - "dev": true - }, - "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", - "dev": true - }, - "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.8.1.tgz", - "integrity": "sha512-/2rX2pfhyUG0y+A123d0ccXtMm7DV7sH1m3lk9nk2DZ2LReq39FXHueR9xZwshE5MdfSf0xunSaMWRqyIA6M1w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.1.3", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.0", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^1.3.0", - "espree": "^7.3.0", - "esquery": "^1.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - }, - "espree": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", - "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "requires": { - "mime-db": "1.40.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "v8-compile-cache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", - "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - } - } -} diff --git a/package.json b/package.json index 056a952..196b94a 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,10 @@ "name": "hue-bridge-emulator", "version": "1.0.0", "description": "Interface with hue compatible devices", - "main": "lib/index.js", + "main": "dist/index.js", "scripts": { + "start": "tsc -p . && node dist/index.js", + "dev": "node_modules/ts-node-dev/lib/bin.js src/index.ts", "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc -p ." }, @@ -21,10 +23,12 @@ "@types/express": "^4.17.8", "babel-eslint": "^10.1.0", "eslint": "^7.8.1", + "ts-node-dev": "^1.1.6", "typescript": "^4.0.2" }, "dependencies": { - "express": "^4.17.1", - "body-parser": "^1.19.0" + "bluebird": "^3.7.2", + "body-parser": "^1.19.0", + "express": "^4.17.1" } } diff --git a/src/express-controller/hue-bridge-config.ts b/src/express-controller/hue-bridge-config.ts new file mode 100644 index 0000000..365ab24 --- /dev/null +++ b/src/express-controller/hue-bridge-config.ts @@ -0,0 +1,29 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Request, Response } from "express"; +import * as HueResponse from "./hue-response"; +import { Type as HueErrorType, Description as HueErrorDescr } from "./hue-response"; +import * as Randomizers from '../helpers/randomizers'; +import { HueStorageConfig } from "../storage/hue-storage-config"; + +/** + * Express controller to create a new user + * @param req Express request information. Expects `devicetype` in body property. + */ +export const createUser = async (req: Request, res: Response) => { + // devicetype parameter + if (req.body.devicetype) { + // create new username + const username = Randomizers.randomString(29, '0123456789abcdefghijklmnopqrstuvwxyz'); + await HueStorageConfig.shared.addUser(username); + // return result + return res.status(200).json(HueResponse.successStructure('username', username)); + } else { + // report missing body + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/', HueErrorDescr.MissingParameter)) + } +} \ No newline at end of file diff --git a/src/express-controller/hue-bridge-groups.ts b/src/express-controller/hue-bridge-groups.ts new file mode 100644 index 0000000..10b119d --- /dev/null +++ b/src/express-controller/hue-bridge-groups.ts @@ -0,0 +1,146 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Request, Response } from "express"; +import { HueStorageGroups } from "../storage/hue-storage-groups"; +import * as HueResponse from "./hue-response"; +import * as Models from '../storage/hue-storage-models'; +import { Type as HueErrorType, Description as HueErrorDescr } from "./hue-response"; + +/** + * Express controller to create a new group + * @param req Express request information. Expects `name`, or `lights` as body property. Optionally, `type` and `class` can also be provided as body property. + */ +export const newGroup = async (req: Request, res: Response) => { + // get requested group + let request = req.body as Models.Group; + // set attributes + let group = {} as Models.Group; + group.name = request.name ?? "Group"; + group.lights = request.lights ?? []; + group.type = request.type ?? "LightGroup"; + group.class = request.class ?? "Other"; + // default attributes + group.sensors = []; + group.state = { "all_on": false, "any_on": false } as Models.GroupState; + group.recycle = false; + group.action = { "on": false, "alert": "none" } as Models.GroupAction; + // new group + return HueStorageGroups.shared.addGroup(group) + .then(newKey => { + return res.status(200).json(HueResponse.successStructure('id', newKey)); + }) + .catch(error => { + console.log(error); + // return error + return res.status(200).json(HueResponse.error(HueErrorType.InternalError, "/groups/", HueErrorDescr.InternalError)); + }) +} + +/** + * Express controller to get list of groups + * @param req Express request information. No special requirements here. + */ +export const getGroups = async (req: Request, res: Response) => { + // get groups + let groups = HueStorageGroups.shared.getGroups(); + // return success + return res.status(200).json(groups); +} + +/** + * Express controller to get a group by id + * @param req Express request information. Expects `groupid` in params property. + */ +export const getGroup = async (req: Request, res: Response) => { + // get parameters + let groupid = req.params.groupid as string; + // get group from storage + let group = HueStorageGroups.shared.getGroup(groupid); + if (group) { + // return success + return res.status(200).json(group); + } else { + // return error + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/groups/'+groupid, HueErrorDescr.MissingParameter)) + } +} + +/** + * Express controller to set attributes of a group (e.g. name, lights, or class) + * @param req Express request information. Expects `groupid` in params property and `name`, or `lights`, or `class` in body property. + */ +export const setGroup = async (req: Request, res: Response) => { + // get group id + let groupid = req.params.groupid as string; + // get new body attributes + let name = req.body.name as string; + let lights = req.body.lights as Array; + let groupClass = req.body.class as string; + // check inputs + if (groupid == undefined) { + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/groups/'+groupid, HueErrorDescr.MissingParameter)); + } + // set group attributes + return HueStorageGroups.shared.setGroup(groupid, name, lights, groupClass) + .then(() => { + // TODO: Return successfully applied attributes + return res.status(200).json(HueResponse.successStructure("/groups/"+groupid, name)); + }) + .catch(error => { + console.log(error); + // return error + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/groups/'+groupid, HueErrorDescr.MissingParameter)); + }) +} + +/** + * Express controller to set group action + * @param req Express request information. Expects `groupid` in params property and the body property being conform to `Models.GroupState`. + */ +export const setGroupAction = async (req: Request, res: Response) => { + // get group id + let groupid = req.params.groupid as string; + // get requested group action + let action = req.body as Models.GroupAction; + if (groupid == undefined || action == undefined) { + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/groups/'+groupid, HueErrorDescr.MissingParameter)); + } + // set group action + return HueStorageGroups.shared.setGroupAction(groupid, action) + .then(() => { + // TODO: Return successfully applied actions + return res.status(200).json(); + }) + .catch(error => { + console.log(error); + // return error + return res.status(200).json(HueResponse.error(HueErrorType.InternalError, "/groups/"+groupid, HueErrorDescr.InternalError)); + }) +} + +/** + * Express controller to delete a group + * @param req Express request information. Expects `groupid` in params property. + */ +export const deleteGroup = async (req: Request, res: Response) => { + // get group id + let groupid = req.params.groupid as string; + if (groupid == undefined) { + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/groups/'+groupid, HueErrorDescr.MissingParameter)); + } + // delete group + return HueStorageGroups.shared.deleteGroup(groupid) + .then(() => { + // return success + return res.status(200).json(HueResponse.success("/groups/"+groupid+" deleted")); + }) + .catch(error => { + console.log(error); + // return error + return res.status(200).json(HueResponse.error(HueErrorType.InternalError, "/groups/"+groupid, HueErrorDescr.InternalError)); + }) +} \ No newline at end of file diff --git a/src/express-controller/hue-bridge-lights.ts b/src/express-controller/hue-bridge-lights.ts new file mode 100644 index 0000000..c54cc9f --- /dev/null +++ b/src/express-controller/hue-bridge-lights.ts @@ -0,0 +1,148 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Request, Response } from "express"; +import * as HueResponse from "./hue-response"; +import { Type as HueErrorType, Description as HueErrorDescr } from "./hue-response"; +import { HueStorageLights } from "../storage/hue-storage-lights"; +import * as Models from '../storage/hue-storage-models'; +import * as Timehelper from '../helpers/timehelper'; + +let lastScan: string = Timehelper.getIsoDateTime(); +let lastFoundLight: Models.Light; + +/** + * Express controller to search for new lights + * @param req Express request information. No special requirements here. + */ +export const searchNewLights = async (req: Request, res: Response) => { + // get current date + lastScan = Timehelper.getIsoDateTime(); + // get new random light + lastFoundLight = await HueStorageLights.shared.newRandomLight(); + // return success + return res.status(200).json(HueResponse.successStructure('/lights', 'Searching for new devices')); +} + +/** + * Express controller to add a new light that has been randomly created with `searchNewLights`. + * @param req Express request information. No special requirements here. + */ +export const getNewLights = async (req: Request, res: Response) => { + // add new light to storage + if (lastFoundLight) { + let key = await HueStorageLights.shared.addLight(lastFoundLight); + let jsonSuccess: {[key: string]: {name: string} | string } = {}; + jsonSuccess[key] = {name: lastFoundLight.name}; + jsonSuccess["lastscan"] = lastScan; + // return success + return res.status(200).json(jsonSuccess); + } + // return success, but without new lights + return res.status(200).json({ "lastscan": lastScan }); +} + +/** + * Express controller to get list of lights + * @param req Express request information. No special requirements here. + */ +export const getLights = async (req: Request, res: Response) => { + // get lights + let lights = HueStorageLights.shared.getLights(); + // return success + return res.status(200).json(lights); +} + +/** + * Express controller to get a light by id + * @param req Express request information. Expects `lightid` in params property. + */ +export const getLight = async (req: Request, res: Response) => { + // get parameters + let lightid = req.params.lightid as string; + // get light from storage + let light = HueStorageLights.shared.getLight(lightid); + if (light) { + // return success + return res.status(200).json(light); + } else { + // return error + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/lights/'+lightid, HueErrorDescr.MissingParameter)) + } +} + +/** + * Express controller to set attributes of a light (e.g. name) + * @param req Express request information. Expects `lightid` in params property and `name` in body property. + */ +export const setLight = async (req: Request, res: Response) => { + // get light id + let lightid = req.params.lightid as string; + // get new name + let name = req.body.name as string; + // check inputs + if (lightid == undefined || name == undefined) { + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/lights/'+lightid, HueErrorDescr.MissingParameter)); + } + // set light name + return HueStorageLights.shared.setLight(lightid, name) + .then(() => { + // return success + return res.status(200).json(HueResponse.successStructure("/lights/"+lightid+"/name", name)); + }) + .catch(error => { + // return error + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/lights/'+lightid, HueErrorDescr.MissingParameter)); + }) +} + +/** + * Express controller to set light state + * @param req Express request information. Expects `lightid` in params property and the body property being conform to `Models.LightState`. + */ +export const setLightState = async (req: Request, res: Response) => { + // get light id + let lightid = req.params.lightid as string; + // get requested light state + let lightstate = req.body as Models.LightState; + if (lightid == undefined || lightstate == undefined) { + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/lights/'+lightid, HueErrorDescr.MissingParameter)); + } + // set light state + return HueStorageLights.shared.setLightState(lightid, lightstate) + .then(() => { + // TODO: Return successfully applied states + return res.status(200).json(); + }) + .catch(error => { + console.log(error); + // return error + return res.status(200).json(HueResponse.error(HueErrorType.InternalError, "/lights/"+lightid, HueErrorDescr.InternalError)); + }) +} + +/** + * Express controller to delete a light + * @param req Express request information. Expects `lightid` in params property. + */ +export const deleteLight = async (req: Request, res: Response) => { + // get light id + let lightid = req.params.lightid as string; + if (lightid == undefined) { + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/lights/'+lightid, HueErrorDescr.MissingParameter)); + } + // delete light + return HueStorageLights.shared.deleteLight(lightid) + .then(() => { + // return success + return res.status(200).json(HueResponse.success("/lights/"+lightid+" deleted")); + }) + .catch(error => { + console.log(error); + // return error + return res.status(200).json(HueResponse.error(HueErrorType.InternalError, "/lights/"+lightid, HueErrorDescr.InternalError)); + }) +} \ No newline at end of file diff --git a/src/express-controller/hue-bridge-scenes.ts b/src/express-controller/hue-bridge-scenes.ts new file mode 100644 index 0000000..2ae9974 --- /dev/null +++ b/src/express-controller/hue-bridge-scenes.ts @@ -0,0 +1,110 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Request, Response } from "express"; +import { HueStorageScenes } from "../storage/hue-storage-scenes"; +import * as HueResponse from "./hue-response"; +import * as Models from '../storage/hue-storage-models'; +import { Type as HueErrorType, Description as HueErrorDescr } from "./hue-response"; + +/** + * Express controller to create a new scene + * @param req Express request information. Optionally receives `name`, `lights`, or `lightstates` as body property. Optionally, `type` and `class` can also be provided as body property. + */ +export const newScene = async (req: Request, res: Response) => { + // get requested group + let scene = req.body as Models.Scene; + // new scene + return HueStorageScenes.shared.addScene(scene) + .then(newKey => { + return res.status(200).json(HueResponse.successStructure('id', newKey)); + }) + .catch(error => { + console.log(error); + // return error + return res.status(200).json(HueResponse.error(HueErrorType.InternalError, "/scenes/", HueErrorDescr.InternalError)); + }) +} + +/** + * Express controller to get list of scenes + * @param req Express request information. No special requirements here. + */ +export const getScenes = async (req: Request, res: Response) => { + // get scenes + let scenes = HueStorageScenes.shared.getScenes(); + // return success + return res.status(200).json(scenes); +} + +/** + * Express controller to get a scene by id + * @param req Express request information. Expects `sceneid` in params property. + */ +export const getScene = async (req: Request, res: Response) => { + // get parameters + let sceneid = req.params.sceneid as string; + // get scene from storage + let scene = HueStorageScenes.shared.getScene(sceneid); + if (scene) { + // return success + return res.status(200).json(scene); + } else { + // return error + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/scenes/'+sceneid, HueErrorDescr.MissingParameter)) + } +} + +/** + * Express controller to set attributes of a scene (e.g. name, lights, or lightstates) + * @param req Express request information. Expects `sceneid` in params property and optionally `name`, or `lights`, or `lightstates` in body property. + */ +export const setScene = async (req: Request, res: Response) => { + // get scene id + let sceneid = req.params.sceneid as string; + // get new body attributes + let name = req.body.name as string; + let lights = req.body.lights as Array; + let lightstates = req.body.lightstates as { [key: string]: Models.LightState }; + // check inputs + if (sceneid == undefined) { + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/scenes/'+sceneid, HueErrorDescr.MissingParameter)); + } + // set group attributes + return HueStorageScenes.shared.setScene(sceneid, name, lights, lightstates) + .then(() => { + // TODO: Return successfully applied attributes + return res.status(200).json(HueResponse.successStructure("/scenes/"+sceneid, name)); + }) + .catch(error => { + console.log(error); + // return error + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/scenes/'+sceneid, HueErrorDescr.MissingParameter)); + }) +} + +/** + * Express controller to delete a scene + * @param req Express request information. Expects `sceneid` in params property. + */ +export const deleteScene = async (req: Request, res: Response) => { + // get scene id + let sceneid = req.params.sceneid as string; + if (sceneid == undefined) { + return res.status(200).json(HueResponse.error(HueErrorType.MissingParameter, '/scenes/'+sceneid, HueErrorDescr.MissingParameter)); + } + // delete scene + return HueStorageScenes.shared.deleteScene(sceneid) + .then(() => { + // return success + return res.status(200).json(HueResponse.success("/scenes/"+sceneid+" deleted")); + }) + .catch(error => { + console.log(error); + // return error + return res.status(200).json(HueResponse.error(HueErrorType.InternalError, "/scenes/"+sceneid, HueErrorDescr.InternalError)); + }) +} \ No newline at end of file diff --git a/src/express-controller/hue-response.ts b/src/express-controller/hue-response.ts new file mode 100644 index 0000000..e8399e3 --- /dev/null +++ b/src/express-controller/hue-response.ts @@ -0,0 +1,80 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +interface HueResponse { + error?: HueError; + success?: HueSuccess | string; +} + +interface HueError { + type: number; + address: string; + description: string; +} + +interface HueSuccess { + [key: string]: string; +} + +export enum Type { + UnauthorizedUser = 1, + MissingParameter = 5, + InternalError = 901 +} + +export enum Description { + UnauthorizedUser = 'unauthorized user', + MissingParameter = 'invalid/missing parameters in body', + InternalError = 'Internal error occurred' +} + +/** + * Message structure to response an Error in the following format: `{ + "error": { + "type": , + "address":
, + "description": + } + }` + * @param type Error type + * @param address Address of the operation + * @param description Error description + * @returns List containing one item per parameter failed + */ +export const error = (type: Type, address: string, description: Description): Array => { + return [{ + error: { + type: type, + address: address, + description: description + } as HueError + }] +} + +/** + * Message structure to response a Success in the following format: `[{"success":{
: }}]` + * @param address Indicating the address of the operation + * @param message Success message + * @returns List containing one item per parameter modified + */ +export const successStructure = (address: string, message: string): Array => { + let map: {[key: string]: string} = {}; + map[address] = message; + return [{ + success: map + }]; +} + +/** + * Message structure to response a Success in the following format: `[{"success": }]` + * @param message Success message + * @returns List containing one item per parameter modified + */ +export const success = (message: string): Array => { + return [{ + success: message + }]; +} \ No newline at end of file diff --git a/src/express-middleware/hue-bridge-auth.ts b/src/express-middleware/hue-bridge-auth.ts new file mode 100644 index 0000000..8abd032 --- /dev/null +++ b/src/express-middleware/hue-bridge-auth.ts @@ -0,0 +1,17 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Request, Response, NextFunction } from "express"; +import * as HueResponse from "../express-controller/hue-response"; +import { Type as HueErrorType, Description as HueErrorDescr } from "../express-controller/hue-response"; +import { HueStorageConfig } from "../storage/hue-storage-config"; + + export const authUser = async (req: Request, res: Response, next: NextFunction) => { + let user = req.params?.user ?? ""; + if (user && HueStorageConfig.shared.checkUser(user)) { return next(); } + // return error (unauthorized) + return res.status(200).json(HueResponse.error(HueErrorType.UnauthorizedUser, '/', HueErrorDescr.UnauthorizedUser)); + } \ No newline at end of file diff --git a/src/helpers/randomizers.ts b/src/helpers/randomizers.ts new file mode 100644 index 0000000..375341e --- /dev/null +++ b/src/helpers/randomizers.ts @@ -0,0 +1,11 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +export function randomString(length: number, chars: string): string { + var result: string = ''; + for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; + return result; +} \ No newline at end of file diff --git a/src/helpers/timehelper.ts b/src/helpers/timehelper.ts new file mode 100644 index 0000000..af3bd7b --- /dev/null +++ b/src/helpers/timehelper.ts @@ -0,0 +1,11 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +export function getIsoDateTime(): string { + var dateFormat = require('dateformat'); + var now = new Date(); + return dateFormat(now, "yyyy-mm-dd'T'hh:MM:ss"); +} \ No newline at end of file diff --git a/src/hue-bridge-emulator.ts b/src/hue-bridge.ts similarity index 55% rename from src/hue-bridge-emulator.ts rename to src/hue-bridge.ts index 8a41f47..da5b6ef 100644 --- a/src/hue-bridge-emulator.ts +++ b/src/hue-bridge.ts @@ -10,6 +10,11 @@ import { json, urlencoded } from 'body-parser'; import hueColorLamp from './hue-color-lamp.json'; import { createSocket } from 'dgram'; import { AddressInfo } from 'net'; +import { createUser } from './express-controller/hue-bridge-config'; +import { authUser } from './express-middleware/hue-bridge-auth'; +import { searchNewLights, getLights, getNewLights, getLight, setLight, setLightState, deleteLight } from './express-controller/hue-bridge-lights'; +import { deleteGroup, getGroup, getGroups, newGroup, setGroup, setGroupAction } from './express-controller/hue-bridge-groups'; +import { deleteScene, getScene, getScenes, newScene, setScene } from './express-controller/hue-bridge-scenes'; interface ConfigOptions { port?: number, @@ -26,7 +31,7 @@ const defaultConfig: Config = { debug: false } -export class HueBridgeEmulator { +export class HueBridge { private lights: { [key: string]: any } = {}; private callbacks: { [key: string]: (key: string, value: any) => void } = {}; private nextId: number = 0; @@ -60,64 +65,130 @@ export class HueBridgeEmulator { next(); }); + // Get description.xml for UPnP compatibility app.get(descriptionPath, (_, res) => { res.status(200).send(this.createDescription(ipAddress, this.config.port, serialNumber, uuid)); }); - app.post('/api', (_, res) => { - const result = [{ success: { username: 'foo' } }]; - res.status(200).contentType('application/json').send(JSON.stringify(result)); - }); - - app.get('/api/foo/lights', (_, res) => { - res.status(200).contentType('application/json').send(JSON.stringify(this.lights)); - }); - - app.get('/api/foo/lights/:id', (req, res) => { - const light = this.lights[req.params.id]; - - if (light) { - res.status(200) - .contentType('application/json') - .send(JSON.stringify(light)); - } else { - res.status(404).send(); - } - }); - - app.put('/api/foo/lights/:id/state', (req, res) => { - const id = req.params.id; - const light = this.lights[id]; - const callback = this.callbacks[id]; - const state = req.body; - this.debug(`Received state change ${JSON.stringify(state)}`); - - if (light) { - const result = []; - - for (let key in state) { - const value = state[key]; - - if (callback) { - try { - callback(key, value); - } catch (err) { - console.error(err); - } - } - - light.state[key] = value; - result.push({ success: { [`/lights/${id}/state/${key}`]: value } }); - } - - res.status(200) - .contentType('application/json') - .send(JSON.stringify(result)); - } else { - res.status(404).send(); - } - }); + /** + * Create new user + * @see https://developers.meethue.com/develop/hue-api/7-configuration-api/#create-user + */ + app.post('/api', createUser); + + + /** + * Initiate search for new lights (simulation only) + * @see https://developers.meethue.com/develop/hue-api/lights-api/#search-for-new-lights + */ + app.post('/api/:user/lights', authUser, searchNewLights); + + /** + * Receive list of new lights + * @see https://developers.meethue.com/develop/hue-api/lights-api/#get-new-lights + * */ + app.get('/api/:user/lights/new', authUser, getNewLights); + + /** + * Receive list of lights + * @see https://developers.meethue.com/develop/hue-api/lights-api/#get-all-lights + */ + app.get('/api/:user/lights', authUser, getLights); + + /** + * Receive a light from the light list + * @see https://developers.meethue.com/develop/hue-api/lights-api/#get-attr-and-state + */ + app.get('/api/:user/lights/:lightid', authUser, getLight); + + /** + * Set attributes of a light (e.g. name) + * @see https://developers.meethue.com/develop/hue-api/lights-api/#set-light-attr-rename + */ + app.put('/api/:user/lights/:lightid', authUser, setLight); + + /** + * Set state of a light + * @see https://developers.meethue.com/develop/hue-api/lights-api/#set-light-state + */ + app.put('/api/:user/lights/:lightid/state', authUser, setLightState); + + /** + * Delete a light + * @see https://developers.meethue.com/develop/hue-api/lights-api/#del-lights + */ + app.delete('/api/:user/lights/:lightid', authUser, deleteLight); + + + /** + * Create a group + * @see https://developers.meethue.com/develop/hue-api/groupds-api/#create-group + */ + app.post('/api/:user/groups', authUser, newGroup); + + /** + * Receive list of groups + * @see https://developers.meethue.com/develop/hue-api/groupds-api/#get-all-groups + */ + app.get('/api/:user/groups', authUser, getGroups); + + /** + * Receive a group from the group list + * @see https://developers.meethue.com/develop/hue-api/groupds-api/#get-group-attr + */ + app.get('/api/:user/groups/:groupid', authUser, getGroup); + + /** + * Set attributes of a group (e.g. name, lights, or class) + * @see https://developers.meethue.com/develop/hue-api/groupds-api/#set-group-attr + */ + app.put('/api/:user/groups/:groupid', authUser, setGroup); + + /** + * Set action of a group + * @see https://developers.meethue.com/develop/hue-api/groupds-api/#set-gr-state + */ + app.put('/api/:user/groups/:groupid/action', authUser, setGroupAction); + + /** + * Delete a group + * @see https://developers.meethue.com/develop/hue-api/groupds-api/#del-group + */ + app.delete('/api/:user/groups/:groupid', authUser, deleteGroup); + + + /** + * Create a scene + * @see https://developers.meethue.com/develop/hue-api/4-scenes/#create-scene + */ + app.post('/api/:user/scenes', authUser, newScene); + + /** + * Receive list of scenes + * @see https://developers.meethue.com/develop/hue-api/4-scenes/#get-all-scenes + */ + app.get('/api/:user/scenes', authUser, getScenes); + + /** + * Receive scene from the scenes list + * @see https://developers.meethue.com/develop/hue-api/4-scenes/#get-scene + */ + app.get('/api/:user/scenes/:sceneid', authUser, getScene); + + /** + * Set attributes of a scene (e.g. name, lights, or lightstates) + * @see https://developers.meethue.com/develop/hue-api/4-scenes/#43_modify_scene + */ + app.put('/api/:user/groups/:sceneid', authUser, setScene); + + /** + * Delete a scene + * @see https://developers.meethue.com/develop/hue-api/4-scenes/#delete-scene + */ + app.delete('/api/:user/scenes/:sceneid', authUser, deleteScene); + + const restServer = app.listen(this.config.port, () => { const info: AddressInfo | null = restServer?.address(); console.log(`Api is listening on ${info?.address}:${info?.port}`); @@ -184,7 +255,7 @@ export class HueBridgeEmulator { throw 'No ip address found'; } - addLight(name: string, onChange: (key: string, value: any) => void) { + addLight(name: string, onChange?: (key: string, value: any) => void) { const light = JSON.parse(JSON.stringify(hueColorLamp)); light.name = name; const id = this.nextId; diff --git a/src/index.ts b/src/index.ts index 1c7b167..8b2c5c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,4 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/.* */ -export * from './hue-bridge-emulator' +import { HueBridge } from './hue-bridge'; + +let bridge = new HueBridge({ debug: true }); +bridge.start(); \ No newline at end of file diff --git a/src/storage/hue-storage-config.ts b/src/storage/hue-storage-config.ts new file mode 100644 index 0000000..d270711 --- /dev/null +++ b/src/storage/hue-storage-config.ts @@ -0,0 +1,38 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + + import { HueStorage } from "./hue-storage"; + + export class HueStorageConfig extends HueStorage { + /** Shared instance of HueStorageConfig for global use */ + static shared: HueStorageConfig = new HueStorageConfig(); + + /** + * Add a new username to the Hue Storage + * @param name String of the username + * @returns Promise to confirm the update being written to file + * @throws If either object could not be parsed (`JSON.stringify`) or file could not be written (`fs.writeFile`) + */ + public async addUser(name: string): Promise { + // check if name already exists + if (this.storage.whitelist?.includes(name) ?? false) { return } + // add name + if (!this.storage.whitelist) { this.storage.whitelist = []; } + this.storage.whitelist?.push(name); + // write to file + return this.writeStorage(this.storage).catch(console.log); + } + + /** + * Check if username is whitelisted + * @param name String of the username + * @returns if whitelisted + */ + public checkUser(name: string): boolean { + if (this.storage.whitelist?.includes(name) ?? false) { return true; } + return false; + } + } \ No newline at end of file diff --git a/src/storage/hue-storage-errors.ts b/src/storage/hue-storage-errors.ts new file mode 100644 index 0000000..0a1ae04 --- /dev/null +++ b/src/storage/hue-storage-errors.ts @@ -0,0 +1,35 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +export module HueStorageErrors { + export class WriteStorage extends Error { + constructor(message: string) { + super(message); + this.name = "WriteStorageError"; + } + } + + export class LightNotFound extends Error { + constructor(message: string) { + super(message); + this.name = "LightNotFoundError"; + } + } + + export class GroupNotFound extends Error { + constructor(message: string) { + super(message); + this.name = "GroupNotFound"; + } + } + + export class SceneNotFound extends Error { + constructor(message: string) { + super(message); + this.name = "SceneNotFound"; + } + } +} diff --git a/src/storage/hue-storage-groups.ts b/src/storage/hue-storage-groups.ts new file mode 100644 index 0000000..671cb76 --- /dev/null +++ b/src/storage/hue-storage-groups.ts @@ -0,0 +1,126 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + + import { HueStorage } from "./hue-storage"; + import { HueStorageErrors } from "./hue-storage-errors"; + import * as Models from './hue-storage-models'; + + export class HueStorageGroups extends HueStorage { + static shared: HueStorageGroups = new HueStorageGroups(); + + /** + * Get all registered groups from the Hue Storage + * @returns Key-value pairs of all groups, whereas the key represents the group id + */ + public getGroups(): { [key: string]: Models.Group } { + return this.storage.groups ?? {}; + } + + /** + * Get a registered group from the Hue Storage + * @param id Group id of the group + * @returns Group object + */ + public getGroup(id: string): Models.Group | undefined { + return this.storage.groups?.[id]; + } + + /** + * Add a ne wgroup to the Hue Storage + * @param group Object of the group + * @returns Promise to confirm the update being written to file and to return the group key + */ + public async addGroup(group: Models.Group): Promise { + // get all existing group ids + let keys = Object.keys(this.storage.groups ?? []); + let numberKeys = keys.map(x => parseInt(x)); + // new key + let newKey = numberKeys.length > 0 ? String(Math.max(...numberKeys) + 1) : '0'; + // add group + if (!this.storage.groups) { this.storage.groups = {}; } + this.storage.groups[newKey] = group; + console.log('Added new group with key "' + newKey + '"'); + // write to file + return this.writeStorage(this.storage) + .then(() => { + return newKey; + }) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } + + /** + * Set group attributes + * @param id Id of the group + * @param name Optional: New name of the group + * @param lights Optional: New array of lights mapped to the group + * @param groupclass Optional: New group class + * @returns Promise to confirm the update being written to file + */ + public async setGroup(id: string, name?: string, lights?: Array, groupclass?: string): Promise { + // get group object + let group = this.storage.groups?.[id]; + if (group) { + // set group object + if (name) { group.name = name; } + if (lights) { group.lights = lights; } + if (groupclass) { group.class = groupclass; } + // write to file + return this.writeStorage(this.storage) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } else { + return Promise.reject(new HueStorageErrors.GroupNotFound('Group id ' + id + ' has not been found')); + } + } + + /** + * Set group actions + * @param id Id of the group + * @param action New action properties to be applied to the group object + * @returns Promise to confirm the update being written to file + */ + public async setGroupAction(id: string, action: Models.GroupAction): Promise { + // get group object + let group = this.storage.groups?.[id] as Models.Group; + if (group) { + if (!group.action) { group.action = {}; } + // set actions + group.action.on = action.on; + group.action.bri = action.bri; + group.action.hue = action.hue; + group.action.sat = action.sat; + group.action.xy = action.xy; + group.action.ct = action.ct; + // write to file + return this.writeStorage(this.storage) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } else { + return Promise.reject(new HueStorageErrors.GroupNotFound('Group id ' + id + ' has not been found')); + } + } + + /** + * Delete group element + * @param id Id of the group + * @returns Promise to confirm the update being written to file + */ + public async deleteGroup(id: string): Promise { + // get light object + if (!this.storage.groups) { this.storage.groups = {}; } + // delete element + delete this.storage.groups[id]; + // write to file + return this.writeStorage(this.storage) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } + } \ No newline at end of file diff --git a/src/storage/hue-storage-lights.ts b/src/storage/hue-storage-lights.ts new file mode 100644 index 0000000..4e64ec4 --- /dev/null +++ b/src/storage/hue-storage-lights.ts @@ -0,0 +1,150 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { HueStorage } from "./hue-storage"; +import { HueStorageErrors } from "./hue-storage-errors"; +import * as Models from './hue-storage-models'; +import * as Randomizers from '../helpers/randomizers'; + +export class HueStorageLights extends HueStorage { + /** Shared instance of HueStorageLights for global use */ + static shared: HueStorageLights = new HueStorageLights(); + + readonly templateFile = './src/storage/templates/lights.json'; + + /** + * Add a new light to the Hue Storage + * @param light Object of the light + * @returns Promise to confirm the update being written to file and to return the light key + */ + public async addLight(light: Models.Light): Promise { + // get all existing light ids + let keys = Object.keys(this.storage.lights ?? []); + let numberKeys = keys.map(x => parseInt(x)); + // new key + let newKey = numberKeys.length > 0 ? String(Math.max(...numberKeys) + 1) : '0'; + // add light + if (!this.storage.lights) { this.storage.lights = {}; } + this.storage.lights[newKey] = light; + console.log('Added new light with key "' + newKey + '"'); + // write to file + return this.writeStorage(this.storage) + .then(() => { + return newKey; + }) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } + + /** + * Get all registered lights from Hue Storage + * @returns Key-value pairs of all lights, whereas the key represents the light id + */ + public getLights(): { [key: string]: Models.Light } { + return this.storage.lights ?? {}; + } + + /** + * Get a registered light from Hue Storage + * @param id Light id of the light + * @returns Light object + */ + public getLight(id: string): Models.Light | undefined { + return this.storage.lights?.[id]; + } + + /** + * Set light attribute (name) + * @param id Light id of the light + * @param name New name of the light + * @returns Promise to confirm the update being written to file + */ + public async setLight(id: string, name: string): Promise { + // get light object + let light = this.storage.lights?.[id]; + if (light) { + // set light object + light.name = name; + // write to file + return this.writeStorage(this.storage) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } else { + return Promise.reject(new HueStorageErrors.LightNotFound('Light id ' + id + ' has not been found')); + } + } + + /** + * Set light state + * @param id Id of the light + * @param state New state properties to be applied to the light object + * @returns Promise to confirm the update being written to file + */ + public async setLightState(id: string, state: Models.LightState): Promise { + // get light object + let light = this.storage.lights?.[id] as Models.Light; + if (light) { + if (!light.state) { light.state = {}; } + // set states + light.state.on = state.on; + light.state.bri = state.bri; + light.state.hue = state.hue; + light.state.sat = state.sat; + light.state.xy = state.xy; + light.state.ct = state.ct; + // write to file + return this.writeStorage(this.storage) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } else { + return Promise.reject(new HueStorageErrors.LightNotFound('Light id ' + id + ' has not been found')); + } + } + + /** + * Delete light element + * @param id Id of the light + * @returns Promise to confirm the update being written to file + */ + public async deleteLight(id: string): Promise { + // get light object + if (!this.storage.lights) { this.storage.lights = {}; } + // delete element + delete this.storage.lights[id]; + // write to file + return this.writeStorage(this.storage) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } + + /** + * Generate a new light randomly from the template file + * @returns Promise of new light + */ + public async newRandomLight(): Promise { + // get template file + let templates = await this.getFilecontent<[Models.Light]>(this.templateFile); + // select a random element + let light = templates[Math.floor(Math.random() * templates.length)]; + // Randomize unique parameters + light.uniqueid = this.getRandomUniqueid(); + return light; + } + + /** Generate unique identifier in the format of "00:17:88:01:02:f0:5b:bc-0b" */ + private getRandomUniqueid(): string { + let uniques: string[] = []; + for (let i = 0; i < 8; i++) { + uniques.push(Randomizers.randomString(2, '0123456789abcdef')); + } + let uniqueid = uniques.join(':') + '-' + Randomizers.randomString(2, '0123456789abcdef'); + return uniqueid; + } +} \ No newline at end of file diff --git a/src/storage/hue-storage-models.ts b/src/storage/hue-storage-models.ts new file mode 100644 index 0000000..520419e --- /dev/null +++ b/src/storage/hue-storage-models.ts @@ -0,0 +1,94 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/** Interface for Hue storage file */ +export interface File { + /** Users being whitelisted */ + whitelist?: Array; + /** Registered lights */ + lights?: { [key: string]: Light }; + /** Registered groups */ + groups?: { [key: string]: Group }; + /** Registered scenes */ + scenes?: { [key: string]: Scene }; +} + + +export interface Light { + state: LightState; + type: string; + name: string; + modelid: string; + manufacturername: string; + productname: string; + uniqueid: string; + productid?: string; +} + +export interface LightState { + on?: boolean; + bri?: number; + ct?: number; + alert?: string; + colormode?: string; + mode?: string; + reachable?: boolean; + hue?: number; + sat?: number; + effect?: string; + xy?: number[]; +} + + +export interface Group { + name?: string; + lights?: string[]; + sensors?: any[]; + type?: string; + state?: GroupState; + recycle?: boolean; + class?: string; + action?: GroupAction; +} + +export interface GroupState { + all_on?: boolean; + any_on?: boolean; +} + +export interface GroupAction { + on?: boolean; + bri?: number; + hue?: number; + sat?: number; + effect?: string; + xy?: number[]; + ct?: number; + alert?: string; + colormode?: string; +} + + +export interface Scene { + name?: string; + type?: string; + group?: string; + lights?: string[]; + owner?: string; + recycle?: boolean; + locked?: boolean; + appdata?: SceneAppdata; + picture?: string; + image?: string; + lastupdated?: Date; + version?: number; + lightstates?: { [key: string]: LightState }; +} + +export interface SceneAppdata { + version?: number; + data?: string; +} \ No newline at end of file diff --git a/src/storage/hue-storage-scenes.ts b/src/storage/hue-storage-scenes.ts new file mode 100644 index 0000000..335c268 --- /dev/null +++ b/src/storage/hue-storage-scenes.ts @@ -0,0 +1,108 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import * as Randomizers from "../helpers/randomizers"; +import { HueStorage } from "./hue-storage"; +import { HueStorageErrors } from "./hue-storage-errors"; +import * as Models from './hue-storage-models'; + +export class HueStorageScenes extends HueStorage { + /** Shared instance of HueStorageScenes for global use */ + static shared: HueStorageScenes = new HueStorageScenes(); + + /** + * Add a new scene to the Hue Storage + * @param scene Object of the scene + * @returns Promise to confirm the update being written to file and to return the scene key + */ + public async addScene(scene: Models.Scene): Promise { + // new key + let newKey = this.getRandomUniqueid(); + // add scene + if (!this.storage.scenes) { this.storage.scenes = {}; } + this.storage.scenes[newKey] = scene; + console.log('Added new scene with key "' + newKey + '"'); + // write to file + return this.writeStorage(this.storage) + .then(() => { + return newKey; + }) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } + + /** + * Set scene attributes + * @param id Id of the Scene + * @param name Optional: New name of the scene + * @param lights Optional: New array of lights mapped to the scene + * @param lightstates Optional: New/updated elements of the light states + * @returns Promise to confirm the update being written to file + */ + public async setScene(id: string, name?: string, lights?: Array, lightstates?: { [key: string]: Models.LightState }): Promise { + // get scene object + let scene = this.storage.scenes?.[id]; + if (scene) { + // set scene object + if (name) { scene.name = name; } + if (lights) { scene.lights = lights; } + if (lightstates) { + if (!scene.lightstates) { scene.lightstates = {}; } + let scenelightstate = scene.lightstates; + Object.keys(lightstates).forEach(key => { + scenelightstate[key] = lightstates[key]; + }) + }; + // write to file + return this.writeStorage(this.storage) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } else { + return Promise.reject(new HueStorageErrors.SceneNotFound('Scene id ' + id + ' has not been found')); + } + } + + /** + * Get all registered scenes from Hue Storage + * @returns Key-value pairs of all scenes, whereas the key represents the scene id + */ + public getScenes(): { [key: string]: Models.Scene } { + return this.storage.scenes ?? {}; + } + + /** + * Get a registered scene from Hue Storage + * @param id Scene id of the Scene + * @returns Scene object + */ + public getScene(id: string): Models.Scene | undefined { + return this.storage.scenes?.[id]; + } + + /** + * Delete scene element + * @param id Id of the scene + * @returns Promise to confirm the update being written to file + */ + public async deleteScene(id: string): Promise { + // get scene object + if (!this.storage.scenes) { this.storage.scenes = {}; } + // delete element + delete this.storage.scenes[id]; + // write to file + return this.writeStorage(this.storage) + .catch(error => { + return Promise.reject(new HueStorageErrors.WriteStorage(error)); + }); + } + + /** Generate unique identifier in the format of "3T2SvsxvwteNNys" */ + private getRandomUniqueid(): string { + return Randomizers.randomString(15, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-'); + } +} \ No newline at end of file diff --git a/src/storage/hue-storage.ts b/src/storage/hue-storage.ts new file mode 100644 index 0000000..9f8ad10 --- /dev/null +++ b/src/storage/hue-storage.ts @@ -0,0 +1,91 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import fs from 'fs'; +import * as Storage from './hue-storage-models'; + +/** + * Handles storage feature for Hue emulator. + */ +export class HueStorage { + /** Shared instance of HueStorage for global use */ + static shared: HueStorage = new HueStorage(); + + /** Handler for asynchronuous file actions */ + readonly fsPromises = fs.promises; + /** Standard file for Hue storage */ + readonly file = './hue.json'; + + /** Storage object for caching */ + protected storage: Storage.File = {}; + + /** + * Init HueStorage: + * - Handle file storage initialization synchronuously + */ + constructor() { + try { + // read Hue storage file and load into cache + let jsonString = fs.readFileSync(this.file, 'utf8'); + let storage = JSON.parse(jsonString); + if (storage) { this.storage = storage; } + } catch(error) { + if (error.code == 'ENOENT') { + // create storage file if not existing yet + console.log("Create a Hue storage file '" + this.file + "'"); + fs.writeFileSync(this.file, JSON.stringify({} as Storage.File) as string); + console.log("Hue storage file '" + this.file + "' created."); + } else { + console.log(error); + } + } + } + + /** + * Receive full content of a file as T object + * @param file File to read + * @param encoding Encoding to be used for reading + * @returns Promise of T object + * @throws If either file could not be found/read (`fs.readFile`) or JSON string could not parsed (`JSON.parse`) + */ + protected async getFilecontent(file: string, encoding: BufferEncoding = 'utf8'): Promise { + // read file + const jsonString = await this.fsPromises.readFile(file, encoding); + return JSON.parse(jsonString) as T; + } + + /** + * Overwrites content T object into a file + * @param file File to write + * @param content T object + * @param encoding Encoding to be used for writing + * @returns Promise to verify content being written + * @throws If either object could not be parsed (`JSON.stringify`) or file could not be written (`fs.writeFile`) + */ + protected async writeFilecontent(file: string, content: T, encoding: BufferEncoding = 'utf8'): Promise { + const jsonString = JSON.stringify(content) as string; + return await this.fsPromises.writeFile(file, jsonString, { flag: 'w', encoding: encoding }); + } + + /** + * Receive full content of Hue Storage asynchronuously as Storage object + * @returns Promise of storage object + * @throws If either file could not be found/read (`fs.readFile`) or JSON string could not parsed (`JSON.parse`) + */ + protected async getStorage(): Promise { + return this.getFilecontent(this.file); + } + + /** + * Overwrites content Storage object into the Storage file + * @param storage Storage object to be overwritten to file + * @returns Promise to verify content being written + * @throws If either object could not be parsed (`JSON.stringify`) or file could not be written (`fs.writeFile`) + */ + protected async writeStorage(storage: Storage.File): Promise { + return this.writeFilecontent(this.file, storage); + } +} \ No newline at end of file diff --git a/src/storage/templates/lights.json b/src/storage/templates/lights.json new file mode 100644 index 0000000..f22e87a --- /dev/null +++ b/src/storage/templates/lights.json @@ -0,0 +1,912 @@ +[ + { + "state": { + "on": false, + "bri": 254, + "ct": 447, + "alert": "select", + "colormode": "ct", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-03-04T03:09:11" + }, + "type": "Color temperature light", + "name": "Spot Figur", + "modelid": "LTW013", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue ambiance spot", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 1000, + "maxlumen": 250, + "ct": { + "min": 153, + "max": 454 + } + }, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "spotbulb", + "function": "functional", + "direction": "downwards", + "startup": { + "mode": "powerfail", + "configured": true + } + }, + "uniqueid": "00:17:88:01:02:f0:5b:bc-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "FBD2BF7E", + "productid": "Philips-LTW013-1-GU10CTv1" + }, + { + "state": { + "on": false, + "bri": 254, + "ct": 447, + "alert": "select", + "colormode": "ct", + "mode": "homeautomation", + "reachable": false + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-03-04T03:09:15" + }, + "type": "Color temperature light", + "name": "Spot Schrankl", + "modelid": "LTW013", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue ambiance spot", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 1000, + "maxlumen": 250, + "ct": { + "min": 153, + "max": 454 + } + }, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "spotbulb", + "function": "functional", + "direction": "downwards", + "startup": { + "mode": "powerfail", + "configured": true + } + }, + "uniqueid": "00:17:88:01:04:7d:2b:f7-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "FBD2BF7E", + "productid": "Philips-LTW013-1-GU10CTv1" + }, + { + "state": { + "on": false, + "bri": 88, + "hue": 7760, + "sat": 254, + "effect": "none", + "xy": [ + 0.5334, + 0.427 + ], + "ct": 500, + "alert": "select", + "colormode": "xy", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-07-12T02:35:17" + }, + "type": "Extended color light", + "name": "Bett", + "modelid": "LST002", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue lightstrip plus", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 40, + "maxlumen": 1600, + "colorgamuttype": "C", + "colorgamut": [ + [ + 0.6915, + 0.3083 + ], + [ + 0.17, + 0.7 + ], + [ + 0.1532, + 0.0475 + ] + ], + "ct": { + "min": 153, + "max": 500 + } + }, + "streaming": { + "renderer": true, + "proxy": true + } + }, + "config": { + "archetype": "huelightstrip", + "function": "mixed", + "direction": "omnidirectional", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:03:f5:31:5e-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "59F2C3A3", + "productid": "Philips-LST002-1-LedStripsv3" + }, + { + "state": { + "on": true, + "bri": 254, + "ct": 366, + "alert": "select", + "colormode": "ct", + "mode": "homeautomation", + "reachable": false + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-03-14T03:39:17" + }, + "type": "Color temperature light", + "name": "Spot Bad 3", + "modelid": "LTW013", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue ambiance spot", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 1000, + "maxlumen": 250, + "ct": { + "min": 153, + "max": 454 + } + }, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "spotbulb", + "function": "functional", + "direction": "downwards", + "startup": { + "mode": "powerfail", + "configured": true + } + }, + "uniqueid": "00:17:88:01:04:96:31:f3-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "FBD2BF7E", + "productid": "Philips-LTW013-1-GU10CTv1" + }, + { + "state": { + "on": false, + "alert": "select", + "mode": "homeautomation", + "reachable": false + }, + "swupdate": { + "state": "notupdatable", + "lastinstall": "2019-11-23T21:52:41" + }, + "type": "On/Off plug-in unit", + "name": "Christbaum", + "modelid": "Plug 01", + "manufacturername": "OSRAM", + "productname": "On/Off plug", + "capabilities": { + "certified": false, + "control": {}, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "plug", + "function": "functional", + "direction": "omnidirectional" + }, + "uniqueid": "7c:b0:3e:aa:00:b2:67:ac-03", + "swversion": "V1.04.12" + }, + { + "state": { + "on": false, + "bri": 254, + "ct": 443, + "alert": "none", + "colormode": "ct", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-03-05T21:28:05" + }, + "type": "Color temperature light", + "name": "Spot Esstisch", + "modelid": "LTW013", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue ambiance spot", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 1000, + "maxlumen": 250, + "ct": { + "min": 153, + "max": 454 + } + }, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "spotbulb", + "function": "functional", + "direction": "downwards", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:04:a8:04:58-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "FBD2BF7E", + "productid": "Philips-LTW013-1-GU10CTv1" + }, + { + "state": { + "on": false, + "bri": 254, + "ct": 443, + "alert": "select", + "colormode": "ct", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-03-13T17:04:48" + }, + "type": "Color temperature light", + "name": "Spot Bar", + "modelid": "LTW013", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue ambiance spot", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 1000, + "maxlumen": 250, + "ct": { + "min": 153, + "max": 454 + } + }, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "spotbulb", + "function": "functional", + "direction": "downwards", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:02:f0:5e:b6-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "FBD2BF7E", + "productid": "Philips-LTW013-1-GU10CTv1" + }, + { + "state": { + "on": false, + "bri": 254, + "ct": 443, + "alert": "select", + "colormode": "ct", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-03-13T17:04:56" + }, + "type": "Color temperature light", + "name": "Spot Couch", + "modelid": "LTW013", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue ambiance spot", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 1000, + "maxlumen": 250, + "ct": { + "min": 153, + "max": 454 + } + }, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "spotbulb", + "function": "functional", + "direction": "downwards", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:03:dc:81:64-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "FBD2BF7E", + "productid": "Philips-LTW013-1-GU10CTv1" + }, + { + "state": { + "on": true, + "bri": 254, + "ct": 366, + "alert": "none", + "colormode": "ct", + "mode": "homeautomation", + "reachable": false + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-03-14T18:11:48" + }, + "type": "Color temperature light", + "name": "Spot Bad 1", + "modelid": "LTW013", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue ambiance spot", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 1000, + "maxlumen": 250, + "ct": { + "min": 153, + "max": 454 + } + }, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "spotbulb", + "function": "functional", + "direction": "downwards", + "startup": { + "mode": "powerfail", + "configured": true + } + }, + "uniqueid": "00:17:88:01:04:95:50:11-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "FBD2BF7E", + "productid": "Philips-LTW013-1-GU10CTv1" + }, + { + "state": { + "on": true, + "bri": 254, + "ct": 366, + "alert": "none", + "colormode": "ct", + "mode": "homeautomation", + "reachable": false + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-03-19T03:24:40" + }, + "type": "Color temperature light", + "name": "Spot Bad 2", + "modelid": "LTW013", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue ambiance spot", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 1000, + "maxlumen": 250, + "ct": { + "min": 153, + "max": 454 + } + }, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "spotbulb", + "function": "functional", + "direction": "downwards", + "startup": { + "mode": "powerfail", + "configured": true + } + }, + "uniqueid": "00:17:88:01:04:96:28:25-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "FBD2BF7E", + "productid": "Philips-LTW013-1-GU10CTv1" + }, + { + "state": { + "on": false, + "bri": 254, + "alert": "select", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "notupdatable", + "lastinstall": "2020-03-19T21:23:54" + }, + "type": "Dimmable light", + "name": "Pendelleuchte", + "modelid": "TRADFRI transformer 30W", + "manufacturername": "IKEA of Sweden", + "productname": "Dimmable light", + "capabilities": { + "certified": false, + "control": {}, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "classicbulb", + "function": "functional", + "direction": "omnidirectional" + }, + "uniqueid": "90:fd:9f:ff:fe:04:c0:fa-01", + "swversion": "1.2.245" + }, + { + "state": { + "on": false, + "bri": 254, + "hue": 12054, + "sat": 143, + "effect": "none", + "xy": [ + 0.502, + 0.4204 + ], + "alert": "select", + "colormode": "xy", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-08-21T11:44:16" + }, + "type": "Color light", + "name": "Hue iris 1", + "modelid": "LLC010", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue iris", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 10000, + "maxlumen": 210, + "colorgamuttype": "A", + "colorgamut": [ + [ + 0.704, + 0.296 + ], + [ + 0.2151, + 0.7106 + ], + [ + 0.138, + 0.08 + ] + ] + }, + "streaming": { + "renderer": true, + "proxy": false + } + }, + "config": { + "archetype": "hueiris", + "function": "decorative", + "direction": "upwards", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:03:32:e5:af-0b", + "swversion": "5.127.1.26581" + }, + { + "state": { + "on": false, + "bri": 254, + "hue": 7676, + "sat": 199, + "effect": "none", + "xy": [ + 0.5016, + 0.4151 + ], + "ct": 443, + "alert": "none", + "colormode": "xy", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-08-23T02:26:06" + }, + "type": "Extended color light", + "name": "Hue lightstrip plus 1", + "modelid": "LST002", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue lightstrip plus", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 40, + "maxlumen": 1600, + "colorgamuttype": "C", + "colorgamut": [ + [ + 0.6915, + 0.3083 + ], + [ + 0.17, + 0.7 + ], + [ + 0.1532, + 0.0475 + ] + ], + "ct": { + "min": 153, + "max": 500 + } + }, + "streaming": { + "renderer": true, + "proxy": true + } + }, + "config": { + "archetype": "huelightstrip", + "function": "mixed", + "direction": "omnidirectional", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:03:f5:6c:8d-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "59F2C3A3", + "productid": "Philips-LST002-1-LedStripsv3" + }, + { + "state": { + "on": false, + "bri": 254, + "hue": 7675, + "sat": 199, + "effect": "none", + "xy": [ + 0.5016, + 0.4151 + ], + "ct": 443, + "alert": "select", + "colormode": "xy", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2021-01-14T03:23:07" + }, + "type": "Extended color light", + "name": "Hue go 1", + "modelid": "LCT026", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue go", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 70, + "maxlumen": 400, + "colorgamuttype": "C", + "colorgamut": [ + [ + 0.6915, + 0.3083 + ], + [ + 0.17, + 0.7 + ], + [ + 0.1532, + 0.0475 + ] + ], + "ct": { + "min": 153, + "max": 500 + } + }, + "streaming": { + "renderer": true, + "proxy": true + } + }, + "config": { + "archetype": "huego", + "function": "decorative", + "direction": "omnidirectional", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:08:5a:8a:fc-0b", + "swversion": "1.76.10", + "swconfigid": "9C47B782", + "productid": "Philips-LCT026-1-HueGo_v2" + }, + { + "state": { + "on": false, + "bri": 254, + "hue": 6600, + "sat": 254, + "effect": "none", + "xy": [ + 0.5019, + 0.4152 + ], + "ct": 447, + "alert": "select", + "colormode": "xy", + "mode": "homeautomation", + "reachable": false + }, + "swupdate": { + "state": "transferring", + "lastinstall": "2020-09-24T02:09:56" + }, + "type": "Extended color light", + "name": "Hue go 2", + "modelid": "LCT026", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue go", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 70, + "maxlumen": 400, + "colorgamuttype": "C", + "colorgamut": [ + [ + 0.6915, + 0.3083 + ], + [ + 0.17, + 0.7 + ], + [ + 0.1532, + 0.0475 + ] + ], + "ct": { + "min": 153, + "max": 500 + } + }, + "streaming": { + "renderer": true, + "proxy": true + } + }, + "config": { + "archetype": "huego", + "function": "decorative", + "direction": "omnidirectional", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:08:5a:8d:b6-0b", + "swversion": "1.65.9_hB3217DF", + "swconfigid": "BF1EED9B", + "productid": "Philips-LCT026-1-HueGo_v2" + }, + { + "state": { + "on": false, + "bri": 254, + "hue": 7674, + "sat": 199, + "effect": "none", + "xy": [ + 0.5017, + 0.4151 + ], + "ct": 443, + "alert": "none", + "colormode": "xy", + "mode": "homeautomation", + "reachable": true + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-09-25T21:10:45" + }, + "type": "Extended color light", + "name": "Hue lightstrip plus 2", + "modelid": "LST002", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue lightstrip plus", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 40, + "maxlumen": 1600, + "colorgamuttype": "C", + "colorgamut": [ + [ + 0.6915, + 0.3083 + ], + [ + 0.17, + 0.7 + ], + [ + 0.1532, + 0.0475 + ] + ], + "ct": { + "min": 153, + "max": 500 + } + }, + "streaming": { + "renderer": true, + "proxy": true + } + }, + "config": { + "archetype": "huelightstrip", + "function": "mixed", + "direction": "omnidirectional", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:03:e1:46:92-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "59F2C3A3", + "productid": "Philips-LST002-1-LedStripsv3" + }, + { + "state": { + "on": false, + "bri": 254, + "ct": 447, + "alert": "select", + "colormode": "ct", + "mode": "homeautomation", + "reachable": false + }, + "swupdate": { + "state": "noupdates", + "lastinstall": "2020-09-25T21:12:04" + }, + "type": "Color temperature light", + "name": "Spot Eingang", + "modelid": "LTW013", + "manufacturername": "Signify Netherlands B.V.", + "productname": "Hue ambiance spot", + "capabilities": { + "certified": true, + "control": { + "mindimlevel": 1000, + "maxlumen": 250, + "ct": { + "min": 153, + "max": 454 + } + }, + "streaming": { + "renderer": false, + "proxy": false + } + }, + "config": { + "archetype": "spotbulb", + "function": "functional", + "direction": "downwards", + "startup": { + "mode": "safety", + "configured": true + } + }, + "uniqueid": "00:17:88:01:04:7d:2c:b0-0b", + "swversion": "1.50.2_r30933", + "swconfigid": "FBD2BF7E", + "productid": "Philips-LTW013-1-GU10CTv1" + } +] \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index ef72c51..465114e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es6", "module": "commonjs", "lib": [ "es6" @@ -8,7 +8,7 @@ "declaration": true, "declarationMap": true, "sourceMap": true, - "outDir": "lib", + "outDir": "dist", "rootDir": "src", "strict": true, "noImplicitAny": true, @@ -18,8 +18,8 @@ "strictPropertyInitialization": true, "noImplicitThis": true, "alwaysStrict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, + "noUnusedLocals": false, + "noUnusedParameters": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "esModuleInterop": true, diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..545264b --- /dev/null +++ b/tslint.json @@ -0,0 +1,14 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "no-unnecessary-type-assertion": true, + "array-type": [true, "array"], + "no-double-space": true, + "no-var-keyword": true + }, + "rulesDirectory": [] +} \ No newline at end of file