Compare commits

...
207 Commits
Author SHA1 Message Date
roemmelter63329 6a69bbcaf7 Global refactor 2019-07-24 12:36:43 +02:00
mandutzse68104 577f041928 mong.tags 2019-07-24 12:16:02 +02:00
mandutzse68104 285249254a Merge remote-tracking branch 'origin/developer' into developer
# Conflicts:
#	public/routes/msgCard.js
2019-07-23 21:42:14 +02:00
mandutzse68104 9f9d3c1480 bookmars 2019-07-23 21:41:27 +02:00
gruenzingerxe68435 9a16ed0480 Geht bei der Suche zurück zum Dashboard 2019-07-23 21:25:44 +02:00
gruenzingerxe68435 ce740b61ec Suche nach Links 2019-07-23 18:39:31 +02:00
gruenzingerxe68435 499f0764da fixed subscribe-btn 2019-07-23 17:44:03 +02:00
gruenzingerxe68435 bac8786ab4 Merge remote-tracking branch 'origin/developer' into developer
# Conflicts:
#	public/routes/home.js
#	public/style/style.css
#	public/style/style.less
2019-07-23 17:38:11 +02:00
gruenzingerxe68435 6529b1d937 verschiedenes 2019-07-23 17:34:57 +02:00
roemmelter63329 2d4850520d Vue Rerender function 2019-07-23 17:22:01 +02:00
mandutzse68104 b1d0a0f095 bookmark without color change 2019-07-23 16:07:14 +02:00
roemmelter63329 ae5360386a Session object deletion on logout; 2019-07-23 13:31:39 +02:00
roemmelter63329 85bca37a8c User creation on login (server-seitig) 2019-07-23 10:34:20 +02:00
mandutzse68104 a6dd34cf81 adding/ loading of bookmarks implemented (only for fixed User) 2019-07-22 23:01:01 +02:00
Edwina Barbalan 9eaf71ec3d Desktopansicht Profil und Home angepasst 2019-07-22 18:51:02 +02:00
gruenzingerxe68435 6eb741760f Button und Link für die Suche 2019-07-22 18:40:51 +02:00
roemmelter63329 3550f1558b Merge remote-tracking branch 'origin/developer' into developer 2019-07-22 16:56:33 +02:00
Edwina Barbalan b62927c28d Merge remote-tracking branch 'origin/developer' into developer
# Conflicts:
#	public/routes/createMessage.js
#	public/routes/files.js
2019-07-22 16:51:34 +02:00
Edwina Barbalan 0f06d8fcd7 Kleiner Fehler in buefy.css ausgebessert 2019-07-22 16:36:56 +02:00
Edwina Barbalan 613041d5cb Desktopansicht bei Suche/CreateMsg "verbessert" (Ohne doppelte Komponente" 2019-07-22 16:22:27 +02:00
roemmelter63329 783938821c Outsource global variables into 'initGlobals.js' 2019-07-22 14:15:20 +02:00
mandutzse68104 1f6261dba0 Merge remote-tracking branch 'origin/developer' into developer
# Conflicts:
#	public/routes/files.js
2019-07-21 15:27:04 +02:00
mandutzse68104 4aa5856fbc fixed createMessage/ files Tag autocomplete 2019-07-21 15:24:40 +02:00
Edwina Barbalan 57901853a9 Merge remote-tracking branch 'origin/developer' into developer 2019-07-21 15:20:42 +02:00
Edwina Barbalan 3eb0a99813 Desktopansicht auf allen Seiten angepasst 2019-07-21 15:20:32 +02:00
gruenzingerxe68435 74232689fb Farbe der Tags geändert 2019-07-21 14:39:33 +02:00
mandutzse68104 d003166e33 merge conflict 2019-07-21 14:22:37 +02:00
mandutzse68104 0097de5122 Merge remote-tracking branch 'origin/developer' into developer
# Conflicts:
#	public/index.html
#	public/routes/createMessage.js
2019-07-21 14:22:16 +02:00
mandutzse68104 5a4faca86f extended search 2019-07-21 14:12:16 +02:00
roemmelter63329 92220fc11c Update 'userField' from createMsg after Login; Add API for user get, post, put; Refactor API /tag/id 2019-07-20 23:16:42 +02:00
roemmelter63329 633dbc90f0 Refactored mong.js; Implemented majorList for ProfilCard; 2019-07-20 18:24:14 +02:00
roemmelter63329 5f0c8c1d2f Login + Logout implemented; ProfilCard v1 implemented; 2019-07-19 23:12:38 +02:00
Edwina Barbalan 1fe9f22109 Extra Logo für Desktop / Anpassung des Contents 2019-07-19 17:01:07 +02:00
gruenzingerxe68435 b7e3737a70 - 2019-07-19 16:27:37 +02:00
Edwina Barbalan 4f2316481e Merge remote-tracking branch 'origin/developer' into developer 2019-07-19 16:13:09 +02:00
gruenzingerxe68435 a49b0ee6a5 searchbar 2019-07-19 16:13:00 +02:00
Edwina Barbalan 6318b358d2 Merge remote-tracking branch 'origin/developer' into developer 2019-07-19 16:03:59 +02:00
Edwina Barbalan 35bdcd9684 Navigation Desktopansicht angepasst 2019-07-19 16:03:49 +02:00
gruenzingerxe68435 c1db18b323 fixid searchbar 2019-07-19 15:58:13 +02:00
gruenzingerxe68435 c8aee0c979 x bei suche geändert 2019-07-19 15:36:38 +02:00
gruenzingerxe68435 d8091ecafd x bei suche formatiert 2019-07-19 15:35:11 +02:00
gruenzingerxe68435 a56ab9cadd Merge remote-tracking branch 'origin/developer' into developer
# Conflicts:
#	public/index.html
#	public/routes/navRoutes.js
2019-07-19 15:34:10 +02:00
gruenzingerxe68435 a4da6243f5 few style changes 2019-07-19 15:28:26 +02:00
Edwina Barbalan 35b5748af9 Merge remote-tracking branch 'origin/developer' into developer
# Conflicts:
#	public/index.html
2019-07-19 15:24:46 +02:00
mandutzse68104 70befa7c4d extended search 2019-07-19 12:52:43 +02:00
Edwina Barbalan 3770d747ba Merge remote-tracking branch 'origin/developer' into developer 2019-07-19 12:29:25 +02:00
gruenzingerxe68435 2f07441fb7 Merge remote-tracking branch 'origin/developer' into developer 2019-07-19 12:10:26 +02:00
gruenzingerxe68435 2b73aaabb2 Frontend - erweiterte Suche 2019-07-19 12:10:22 +02:00
Edwina Barbalan 7eed075963 Index-Datei "aufgeräumt" (auskommentierte/alte sachen weg) 2019-07-19 11:59:03 +02:00
Edwina Barbalan d66be879cd Merge remote-tracking branch 'origin/developer' into developer 2019-07-19 11:54:26 +02:00
Edwina Barbalan 373d644134 Desktopansicht angepasst 2019-07-19 11:54:13 +02:00
mandutzse68104 0c8b9da3f6 Merge remote-tracking branch 'origin/developer' into developer 2019-07-09 12:38:56 +02:00
mandutzse68104 d1a5a0b4cf the x in search is working 2019-07-09 12:38:46 +02:00
gruenzingerxe68435 8ddf59666b link zu button geändert -> Tagsuche durch "link" 2019-07-09 12:37:21 +02:00
gruenzingerxe68435 10f349b948 Merge remote-tracking branch 'origin/developer' into developer 2019-07-09 11:37:07 +02:00
gruenzingerxe68435 f4ab8813f3 Clear-Funktion bei der Suche 2019-07-09 11:36:54 +02:00
mandutzse68104 45315d7c47 search not in mounted 2019-07-09 11:24:12 +02:00
mandutzse68104 a30bbe961c Merge remote-tracking branch 'origin/developer' into developer 2019-07-09 11:23:03 +02:00
mandutzse68104 b029846985 added tags 2019-07-09 11:22:56 +02:00
Edwina Barbalan 9d3d1b707e Merge remote-tracking branch 'origin/developer' into developer 2019-07-09 10:43:18 +02:00
Edwina Barbalan 6c9cb12480 Navbar is-active colorchange 2019-07-09 10:43:05 +02:00
roemmelter63329 9dab5e2dbc Update Node_modules 2019-07-02 16:05:15 +02:00
Edwina Barbalan 95a0ff3913 Auskommentiertes Array wieder hinzugefügt 2019-07-02 15:06:46 +02:00
Edwina Barbalan 83d1d4dea8 Merge remote-tracking branch 'origin/developer' into developer 2019-07-02 14:59:17 +02:00
Edwina Barbalan 9375316956 Router Componente erstellt / Navbar bearbeitet 2019-07-02 14:59:11 +02:00
mandutzse68104 a4dbafe3ef autocomplete fro tags working 2019-07-02 13:08:11 +02:00
gruenzingerxe68435 79475fe48d autocomplete bei der suche 2019-07-02 12:21:11 +02:00
gruenzingerxe68435 e15cf25745 Tag-Darstellung in der Msg-Card 2019-07-02 11:49:35 +02:00
mandutzse68104 3478901450 search repaired 2019-07-02 11:37:59 +02:00
mandutzse68104 d48a3bf5ed search for tags / normal text search not working 2019-06-27 12:08:08 +02:00
gruenzingerxe68435 ab358abe98 Merge remote-tracking branch 'origin/developer' into developer
# Conflicts:
#	public/routes/createMessage.js
2019-06-27 11:15:42 +02:00
gruenzingerxe68435 5280c47477 first Try to autocomplete the search 2019-06-27 11:14:37 +02:00
mandutzse68104 8c0d3dc669 # in front of tags and changed index of messages 2019-06-27 11:13:40 +02:00
gruenzingerxe68435 d45ab87a8b # bei der Tag-Ausgabe hinzugefügt 2019-06-27 10:56:34 +02:00
mandutzse68104 156ec36d1e easy search implemented 2019-06-27 10:28:48 +02:00
mandutzse68104 e93526c639 tags from database implemented 2019-06-25 14:43:33 +02:00
roemmelter63329 cad0230c47 Fix: tags were not visible on "home" page -> solved 2019-06-20 23:30:51 +02:00
roemmelter63329 82f8e4bbde outsource db routes, schemata and logic into dbs.js 2019-06-20 17:17:45 +02:00
roemmelter63329 5c26a11023 restructure server.js, adding ldap access, part 1/2 role authorization 2019-06-18 15:54:07 +02:00
roemmelter63329 4fe1bbd0e5 renaming library includes 2019-06-18 15:52:34 +02:00
roemmelter63329 eb89e2c3bf renaming library (adding version nr.) 2019-06-18 15:52:03 +02:00
roemmelter63329 37e7d757c3 moved db-model-schemata, adjust user-model 2019-06-18 15:47:42 +02:00
Edwina Barbalan d61def25b9 Merge remote-tracking branch 'origin/developer' into developer 2019-06-18 14:47:38 +02:00
gruenzingerxe68435 e108be1fb8 Erweiterung zum Log in 2019-06-18 14:25:09 +02:00
gruenzingerxe68435 773408dc12 Log-in Modal überarbeitet 2019-06-18 14:18:20 +02:00
Edwina Barbalan d71c50ef22 Profil änderungen 2019-06-18 09:37:48 +02:00
Edwina Barbalan c3073101b1 Merge remote-tracking branch 'origin/developer' into developer 2019-06-04 16:42:52 +02:00
Edwina Barbalan d77c8d69e3 Speicherfunktion / Buefy auf Profil angewendet 2019-06-04 16:42:45 +02:00
gruenzingerxe68435 ae4203ccf9 Merge remote-tracking branch 'origin/developer' into developer 2019-06-04 16:37:35 +02:00
gruenzingerxe68435 d9b249c828 #buefy-changes 2019-06-04 16:32:37 +02:00
mandutzse68104 aba5a9ce8e Mongodb Script added 2019-06-04 15:27:49 +02:00
mandutzse68104 8341ed24d0 user model added; message model changed; tag model added 2019-06-04 15:26:38 +02:00
mandutzse68104 d27216c8a8 Merge remote-tracking branch 'origin/developer' into developer 2019-06-04 14:47:40 +02:00
roemmelter63329 5d28bc36e1 node_modules updated, buefy add to lib folder 2019-06-04 14:29:48 +02:00
mandutzse68104 4603f7a7c1 User schema created 2019-05-28 14:40:14 +02:00
roemmelter63329 0a3f6ff240 add buefy + vuetify not minified css 2019-05-14 18:26:45 +02:00
roemmelter63329 0fcf2f7b97 add buefy + vuetify libs; added script tags to index.html 2019-05-14 18:20:33 +02:00
gruenzingerxe68435 9bf9f5faec Merge remote-tracking branch 'origin/developer' into developer 2019-04-17 15:58:39 +02:00
gruenzingerxe68435 56f4a3f940 vuetify eingebunden + erster Versuch Combobox 2019-04-17 15:58:15 +02:00
mandutzse68104 c7a835ad1c Merge remote-tracking branch 'origin/developer' into developer 2019-04-16 21:24:10 +02:00
mandutzse68104 2f90b14625 added array to tag type 2019-04-16 21:23:50 +02:00
gruenzingerxe68435 24642ca1a8 Log-in/Log-out prototyp 2019-04-16 21:21:35 +02:00
Edwina Barbalan 1523cc93c2 Modal height angepasst 2019-04-10 12:16:30 +02:00
Edwina Barbalan bee7427176 Modal bugfix + Änderungen in CSS/LESS 2019-04-09 14:25:29 +02:00
Edwina Barbalan e921e6a9e6 Profil Edit-Funktion 2019-04-09 14:03:55 +02:00
gruenzingerxe68435 e471a76377 Erster Code für die Login UI 2019-04-07 23:15:09 +02:00
roemmelter63329 091a1c87a2 Finale Abgabe: PrStA - Gesamtdokumentation (v.2) [Literaturverzeichnis-Einträge haben gefehlt.] 2019-03-06 11:50:48 +01:00
roemmelter63329 3f58fa194f Finale Abgabe: PrStA - Gesamtdokumentation 2019-03-05 23:35:35 +01:00
gruenzingerxe68435 7977b21a15 gerhört zu vorherigen commit 2019-03-05 17:38:22 +01:00
gruenzingerxe68435 1dbba098dd Quellen angepasst + Kap. Konzepterstellung überarbeitet 2019-03-05 17:37:54 +01:00
roemmelter63329 9ce2dcf199 Gruppen PrStA - v1 2019-03-05 00:50:05 +01:00
Edwina Barbalan 47d47b8b22 Präsentation beigefügt 2019-02-26 23:48:18 +01:00
roemmelter63329 9d6fcbc48f Add Examples .bib format 2019-02-17 15:10:14 +01:00
roemmelter63329 6f2fb1cb0b Added Titlepage form 2019-02-17 14:37:17 +01:00
gruenzingerxe68435 2e7ff6ebce Struktur 2019-02-17 14:28:49 +01:00
gruenzingerxe68435 0e257f891a Roadmap 2019-02-17 13:53:09 +01:00
roemmelter63329 fb36d85745 Update preamble.sty 2019-02-17 12:44:02 +01:00
phamvi65629 5691a5fb2c LaTex Projekt-Roadmap Tabelle eingefügt 2019-02-17 12:16:31 +01:00
roemmelter63329 8c80e9c6f2 Added preamble.sty 2019-02-17 11:55:46 +01:00
roemmelter63329 79bfe6ca10 Added Projekt-Roadmap.txt, meeting_doku.pdf 2019-02-13 20:42:44 +01:00
mandutzse68104 5fc6708490 refresh tag for demo purpose 2019-02-12 16:23:43 +01:00
gruenzingerxe68435 7a6d46c85b Präsentation-Styles aufkommentiert 2019-02-12 16:20:08 +01:00
mandutzse68104 9b2af2d2c8 refresh tag 2019-02-12 16:19:23 +01:00
gruenzingerxe68435 34a232afac Präsentations größe 2019-02-11 15:52:36 +01:00
gruenzingerxe68435 b99c8184d1 Size for Präsentation 2019-02-11 15:47:35 +01:00
roemmelter63329 80ce7c9d7d Set Android, Apple Icons 2019-02-11 15:44:29 +01:00
roemmelter63329 fd648dab29 Merge remote-tracking branch 'origin/developer' into developer 2019-02-11 15:40:59 +01:00
roemmelter63329 590bd9dc45 fix app icons 2019-02-11 15:40:19 +01:00
gruenzingerxe68435 342e4606e1 Schrift für Präsentation 2019-02-11 15:12:59 +01:00
gruenzingerxe68435 59781f4335 IOS - Icons hinzugefügt 2019-02-11 12:38:00 +01:00
roemmelter63329 2a5f627c38 ServiceWorker rewritten in Async/Await; SW caches only GET now 2019-02-11 00:28:54 +01:00
roemmelter63329 07a2480e19 Update Tab Icon; Organize ServiceWorker 2019-02-10 17:34:20 +01:00
roemmelter63329 63e1363dd2 Minor Fixes: Added {useNewUrlParser} fix deprecated warning, suppress some console.log output 2019-02-09 19:53:40 +01:00
roemmelter63329 f809f8ecea Updated manifest.json 2019-02-09 19:36:34 +01:00
roemmelter63329 82e18e0605 Properly renamed img-files 2019-02-09 19:35:48 +01:00
roemmelter63329 f67e5184b3 Changed file-folder structure 2019-02-09 19:35:01 +01:00
roemmelter63329 855dbf8ba7 Updated ServiceWorker 2019-02-09 19:33:29 +01:00
roemmelter63329 81da3b0057 Removed Http - library requests (instead included as lib-files) 2019-02-09 19:33:11 +01:00
gruenzingerxe68435 bedfbcb1b3 Android Icon hinzugefügt 2019-02-08 15:30:21 +01:00
mandutzse68104 e278cbf6ec changed tags from array to string 2019-02-08 15:23:35 +01:00
mandutzse68104 5a4bb82f4e om added 2019-02-08 15:08:33 +01:00
mandutzse68104 33f1c41df0 / erased 2019-02-08 15:06:15 +01:00
mandutzse68104 df40bcbfb1 reverse for; removed / from serviceworker 2019-02-08 14:59:19 +01:00
gruenzingerxe68435 464c8aca5f Redirect to home 2019-02-08 14:37:56 +01:00
Edwina Barbalan 258d9e816d css kleine änderung 2019-02-08 14:23:19 +01:00
mandutzse68104 c7905f5970 ignore updated 2019-02-08 14:02:45 +01:00
mandutzse68104 145f9b086b mongo.config moved 2019-02-08 14:01:42 +01:00
mandutzse68104 7dea38049b / added 2019-02-08 13:57:52 +01:00
mandutzse68104 e12bd98dbb /api --> api 2019-02-08 13:52:48 +01:00
mandutzse68104 1dbb310560 Merge remote-tracking branch 'origin/developer' into developer 2019-02-08 13:40:34 +01:00
mandutzse68104 31f1add35d everything i working 2019-02-08 13:40:21 +01:00
gruenzingerxe68435 c802056b2d Icon hinzugefügt + Responsivness angepasst 2019-02-07 22:13:11 +01:00
Xenia Grünzinger c609a9b8d6 Few Css-Changes 2019-02-07 13:24:37 +01:00
phamvi65629 4b11dd4f35 createMsg erstellt und message.model.js id Änderung 2019-02-07 12:16:40 +01:00
Edwina Barbalan 79455401e8 fehlerhaftes div geschlossen 2019-02-07 11:32:29 +01:00
mandutzse68104 5c7ad18abd added post request to server.js for Vivi 2019-02-07 11:24:45 +01:00
mandutzse68104 4d9f3d8648 erased the ajax test 2019-02-06 17:16:20 +01:00
mandutzse68104 b52de633b0 ajax listmessages and messages is working 2019-02-06 17:00:38 +01:00
roemmelter63329 f2a75643c8 Merge remote-tracking branch 'origin/developer' into developer 2019-02-03 01:00:49 +01:00
roemmelter63329 2bd4c80ef9 Update ServiceWorker 2019-02-03 00:59:23 +01:00
mandutzse68104 3069d8b99a I do not know why this was not commited with the other stuff 2019-02-01 14:07:43 +01:00
mandutzse68104 f3c33a35cd database and ajax test added 2019-02-01 14:06:44 +01:00
Xenia Grünzinger b5eb4c2b79 list_messages() hinzugefügt 2019-01-31 19:01:40 +01:00
Edwina Barbalan 56b238ad32 Bootstrap funktion verbessert 2019-01-16 17:38:57 +01:00
roemmelter63329 d1595c136d Add changes to .gitignore and add latex- .tex/.bib/.sty -file. 2019-01-16 15:12:50 +01:00
Edwina Barbalan eeaf7b428c home.js ausgebessert 2018-12-28 10:18:21 +01:00
Edwina Barbalan 013bdb4ce1 Funktion get_insert_message von index.html zu home.js / 2.json und 3.json verändert 2018-12-28 10:07:27 +01:00
Xenia Grünzinger 81d2148aac Merge remote-tracking branch 'origin/developer' into developer 2018-12-19 18:18:49 +01:00
Xenia Grünzinger ff0ac4aba2 Added Json-Files + included them 2018-12-19 18:18:42 +01:00
roemmelter63329 1f9d5d2e69 Add .gitignore rules: Exclude LaTeX output, brackets-latex config file 2018-12-19 16:57:29 +01:00
roemmelter63329 a7092ed173 Add Latex Template File 2018-12-19 16:44:27 +01:00
Xenia Grünzinger b2ecbb6e12 Bookmark-Icon auf Card gepackt + Keine Nav-Zeile beim Formular 2018-12-18 09:20:13 +01:00
Edwina Barbalan efd023b050 Kleine Verbesserung 2018-12-12 15:59:30 +01:00
Xenia Grünzinger 129496a13f Test-Methode 2018-12-12 15:56:09 +01:00
Xenia Grünzinger 645e111f51 Merged Profil-Komponente 2018-12-12 14:40:27 +01:00
Xenia Grünzinger b2df5884dd Files und NewMsg Componente hinzugefügt 2018-12-12 14:34:39 +01:00
Edwina Barbalan 985d4a62e2 Profil componente erstellt 2018-12-12 14:31:54 +01:00
Edwina Barbalan 2f9750eae4 refactoring home.js; bookmark.js hinzugefügt 2018-12-06 10:23:38 +01:00
roemmelter63329 9b8fe37cc6 Add manifest.json; add some meta for iOS to index.html; add iOS icons for testing 2018-12-05 21:30:53 +01:00
roemmelter63329 9391cf8aa9 Fixed: ServiceWorker, Favicon.ico must be at root 2018-12-05 19:45:41 +01:00
roemmelter63329 7e534ce00f move several files in folder (+ rename links); remove .eslintrc file; serviceWorker update 2018-12-05 19:32:36 +01:00
phamvi65629 0f07ea58f2 Merge remote-tracking branch 'origin/developer' into developer 2018-12-05 16:42:55 +01:00
Edwina Barbalan 4cf54df7e7 funktionierender Router hinzugefügt 2018-12-05 16:35:54 +01:00
phamvi65629 c6c8435684 Merge remote-tracking branch 'origin/developer' into developer 2018-12-05 14:43:13 +01:00
Edwina Barbalan a2776c77a5 home.js zurückgestellt 2018-12-05 14:42:02 +01:00
Edwina Barbalan 9600eaff36 index.html zurückgestellt 2018-12-05 14:41:24 +01:00
phamvi65629 47d20b8192 fix ssl try catch error 2018-12-03 23:32:07 +01:00
Edwina Barbalan d21d11b622 Router hinzugefügt - funktioniert noch nicht 2018-11-29 11:40:23 +01:00
Edwina Barbalan b41b7ab520 Created new Components 2018-11-29 10:07:12 +01:00
Xenia Grünzinger 0ece964326 refactoring 2018-11-29 09:46:47 +01:00
Xenia Grünzinger 03935ea5c3 Merge remote-tracking branch 'origin/master' 2018-11-28 14:33:43 +01:00
Xenia Grünzinger 2a5be0bd77 few changes at the msg-component 2018-11-28 14:33:31 +01:00
phamvi65629 97c82b5d92 SSL Key created and added to enable HTTPS 2018-11-27 23:33:26 +01:00
Edwina Barbalan 4eeb3d003b Vue Nachrichtenfeld-Komponente erstellt (korrigiert) 2018-11-26 13:03:08 +01:00
Edwina Barbalan eaaee61978 Vue komponenten erstellt - Nachrichtenfelder 2018-11-26 12:57:52 +01:00
roemmelter63329 1de994c4e0 Merge remote-tracking branch 'origin/master' 2018-11-22 16:42:32 +01:00
roemmelter63329 d20e8a9d10 Fix EsLint Brackets (error markup 'const'). 2018-11-22 16:41:46 +01:00
Xenia Grünzinger 5d7e5f2a4c Erste Schritte mit Vue.js 2018-11-22 15:00:09 +01:00
Xenia Grünzinger 75add7333d Kleine Design Anpassungen des Mockups 2018-11-22 11:48:01 +01:00
roemmelter63329 bdf4157b08 Clean up empty JS-Files and add CSS-link 2018-11-21 15:54:09 +01:00
Xenia Grünzinger 7043e5b2ed Design Anpassungen für Mockup 2018-11-21 14:42:59 +01:00
roemmelter63329 c3d8d2c49d Add some extra structure to index.html, main.js, serviceWorker.js:
Moved some script code from index.html to main.js + added serviceWorker registration.
Added several Comments to index.html with TAGs (supported by brackets plugin).
Fixed serviceWorker.js; ServiceWorker is now working and offers offline functionality.
2018-11-14 11:48:32 +01:00
roemmelter63329 b0971ed56a First Commit: serviceWorker.js (yet not running) 2018-11-12 00:17:21 +01:00
roemmelter63329 32b20cd1c1 Added comments in server.js 2018-11-11 12:42:03 +01:00
roemmelter63329 5d9dbe0b89 Removed index.html outside of 'public' folder. (All app files need to be inside.) 2018-11-10 23:11:52 +01:00
mandutzse68104 910e6f4dae Merge remote-tracking branch 'origin/master'
# Conflicts:
#	index.html
2018-11-01 09:44:56 +01:00
mandutzse68104 1295d32266 comment added to test pushing without pulling 2018-11-01 09:42:30 +01:00
7001 changed files with 810099 additions and 45393 deletions

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
# Config file of brackets-latex plugin with an folder path
.brackets.json
# brackets-latex compile output folders
latex-out/
out/
# latex compile output files. Could cause trouble.
latex-files/*.aux
latex-files/*.bbl
latex-files/*.bcf
latex-files/*.blg
latex-files/*.dvi
latex-files/*.log
latex-files/*.out
latex-files/*.pdf
latex-files/*.run.xml
latex-files/*.toc
# config
mongodb.config.js
# exclude Mac-File .DS_Store
*.DS_Store
+59
View File
@@ -0,0 +1,59 @@
<!doctype html>
<html lang="de">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Material Design for Bootstrap fonts and icons -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons">
<!-- Material Design for Bootstrap CSS -->
<link rel="stylesheet" href="https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css" integrity="sha384-wXznGJNEXNG1NFsbm0ugrLFMQPWswR3lds2VeinahP8N0zJw9VWSopbjv2x7WCvX" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="style/style.css">
<title>oma</title>
</head>
<body>
<div class="om-header-container ">
<div class="om-header">
<a href="index.html"><img src="img/th_nbg_ohmicon_amp.png" width=55px height=55px></a>
<form class="om-searchbar">
<input type="search" placeholder="Search...">
</form>
</div>
</div>
<div class="om-content">
</div>
<div>
<nav class="nav nav-tabs nav-justified om-nav ">
<router-link to="/home" class="nav-item nav-link"><i class="material-icons">home</i></router-link>
<router-link to="/files" class="nav-item nav-link"><i class="material-icons">folder</i></router-link>
<router-link to="/createMessage" class="nav-item nav-link outlined"><i class="material-icons">add_circle</i></router-link>
<router-link to="/bookmark" class="nav-item nav-link"><i class="material-icons">bookmark</i></router-link>
<router-link to="/profil" class="nav-item nav-link"><i class="material-icons">person</i></router-link>
</nav>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://unpkg.com/popper.js@1.12.6/dist/umd/popper.js" integrity="sha384-fA23ZRQ3G/J53mElWqVJEGJzU0sTs+SvzG8fXVWP+kJQ1lwFAOkcUOysnlKJC33U" crossorigin="anonymous"></script>
<script src="https://unpkg.com/bootstrap-material-design@4.1.1/dist/js/bootstrap-material-design.js" integrity="sha384-CauSuKpEqAFajSpkdjv3z9t8E7RlpJ1UP0lKM/+NdtSarroVKu069AlsRPKkFBz9" crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
$('body').bootstrapMaterialDesign();
});
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

@@ -0,0 +1,307 @@
@online{spa:1,
author = {t3n},
title = {Was ist eigentlich eine Single-Page-Webanwendung?},
month = {04},
year = {2018},
url = {https://t3n.de/news/single-page-webanwendung-1023623/},
note = {Zuletzt besucht am 26.02.2019}
}
@online{spa:2,
author = {Nebil Aboobacker},
title = {Was ist eine Single Page Application (SPA)?},
month = {05},
year = {2018},
url = {https://www.yuhiro.de/was-ist-eine-single-page-application-spa/},
note = {Zuletzt besucht am 26.02.2019}
}
@online{vue,
author = {Marc Teufel},
title = {Vue.js Tutorial: So entwickelt man Komponenten mit Vue.js},
month = {07},
year = {2018},
url = {https://entwickler.de/online/javascript/einfuehrung-vuejs-vue-579851816.html},
note = {Zuletzt besucht am 26.02.2019}
}
@online{vue:warum,
author = {Verena Sappelt},
title = {Vue.js – Vorteile: Das Beste aus zwei Welten},
month = {01},
year = {2019},
url = {https://www.ppw.de/blog/vue-js-vorteile-das-beste-aus-zwei-welten/},
note = {Zuletzt besucht am 26.02.2019}
}
@online{vue:router,
author = {Said Hayani},
title = {How to use routing in Vue.js to create a better user experience},
month = {06},
year = {2018},
url = {https://medium.freecodecamp.org/how-to-use-routing-in-vue-js-to-create-a-better-user-experience-98d225bbcdd9},
note = {Zuletzt besucht am 26.02.2019}
}
@online{vue:router2,
title = {Vue Router - Getting Started},
month = {01},
year = {2014},
url = {https://router.vuejs.org/guide/#javascript},
note = {Zuletzt besucht am 26.02.2019}
}
@online{vue:router3,
author = {Raymond Camden},
title = {Working with Routes in Vue.js},
month = {012},
year = {2017},
url = {https://www.raymondcamden.com/2017/11/12/working-with-routes-in-vue},
note = {Zuletzt besucht am 26.02.2019}
}
@online{api2,
author = {Björn Behrendt},
title = {Application-Programming-Interface (API)},
month = {06},
year = {2018},
url = {https://www.gruenderszene.de/lexikon/begriffe/application-programming-interface-api?interstitial},
note = {Zuletzt besucht am 26.02.2019}
}
@online{scrib,
title = {Scribble},
month = {08},
year = {2017},
url = {https://de.wikipedia.org/wiki/Scribble},
note = {Zuletzt besucht am 26.02.2019}
}
@online{md,
title = {Material Design},
month = {01},
year = {2019},
url = {https://de.wikipedia.org/wiki/Material_Design},
note = {Zuletzt besucht am 26.02.2019}
}
@online{md2,
author = {Google},
title = {Homepage Material Design},
url = {https://material.io},
note = {(o.D.), Zuletzt besucht am 26.02.2019}
}
@online{bootstrap,
author = {Federico Zivolo and Bootstrap contributors},
title = {Bootstrap Material Design},
month = {08},
year = {2018},
url = {https://fezvrasta.github.io/bootstrap-material-design/},
note = {Zuletzt besucht am 26.02.2019}
}
@online{less,
author = {Alexis Sellier },
title = {LESS - Die dynamische Stylesheet Sprache},
month = {01},
year = {2013},
url = {http://www.lesscss.de},
note = {Zuletzt besucht am 26.02.2019}
}
@online{ajax1,
author = {Jesse James Garret},
title = {Ajax: A New Approach to Web Applications},
month = {07},
year = {2008},
url = {https://web.archive.org/web/20080702075113/http://www.adaptivepath.com/ideas/essays/archives/000385.php},
note = {Zuletzt besucht am 26.02.2019}
}
@online{ajax2,
author = {Matthias Hertel},
title = {The AJAX Engine},
month = {02},
year = {2007},
url = {http://www.mathertel.de/AJAXEngine/#view=Home},
note = {Zuletzt besucht am 26.02.2019}
}
@book{ajax3,
author = {David Sawyer McFarland},
title = {JavaScript \& jQuery - the missing manual},
year = {2012},
publisher = {O'Reilly Media, inc.},
address = {1005 Gravenstein Highway North, Sebastopol, CA 95472},
}
@online{api,
title = {Programmierschnittstelle},
month = {02},
year = {2019},
url = {https://de.wikipedia.org/wiki/Programmierschnittstelle},
note = {Zuletzt besucht am 26.02.2019}
}
@online{rest,
author = {Thomas Bayer},
title = {REST Web Services},
month = {11},
year = {2002},
url = {https://www.oio.de/public/xml/rest-webservices.htm},
note = {Zuletzt besucht am 26.02.2019}
}
@online{mongo,
title = {What is MongoDB?},
year = {2019},
url = {https://www.mongodb.com/what-is-mongodb},
note = {Zuletzt besucht am 26.02.2019}
}
@online{mongoose,
author = {Nick Karnik},
title = {Introduction to Mongoose for MongoDB},
month = {02},
year = {2018},
url = {https://medium.freecodecamp.org/introduction-to-mongoose-for-mongodb-d2a7aa593c57},
note = {Zuletzt besucht am 26.02.2019}
}
@online{Bear,
author = {Sevilleja, Chris},
month = {02},
year = {2019},
title = {Build a RESTful API Using Node and Express 4},
url = {https://scotch.io/tutorials/build-a-restful-api-using-node-and-express-4},
note = {Zuletzt besucht am 01.03.219}
}
@online{gitefi,
author = {Matthias Hopf},
month = {02},
year = {2019},
title = {Git efi},
url = {https://git.efi.th-nuernberg.de/gitea/hopfma/om/wiki/_pages},
note = {Zuletzt besucht am 25.02.2019}
}
@online{Powtoon,
title = {Powtoon - Brings Awesomeness to your presentations},
month = {02},
year = {2019},
url = {https://www.powtoon.com/my-powtoons/#/},
note = {Zuletzt besucht am 01.03.219}
}
@online{Risingstack,
author = {Gergely Nemeth},
year = {2017},
title = {10 Best Practices for Writing Node.js REST APIs | @RisingStack},
url = {https://blog.risingstack.com/10-best-practices-for-writing-node-js-rest-apis/},
note = {Zuletzt besucht am 01.03.2019}
}
@online{trello:uri,
author = {ATLASSIAN},
title = {Trello},
year = {2019},
url = {https://trello.com/home},
note = {(Zuletzt besucht am 25.02.2019)}
}
@online{trello:price,
author = {ATLASSIAN},
title = {Trello-Preisliste},
year = {2019},
url = {https://trello.com/pricing},
note = {(Zuletzt besucht am 25.02.2019)}
}
@online{bitrix:uri,
author = {Bitrix, Inc.},
title = {Führende Kostenlose Plattform Für Kommunikation Und Zusammenarbeit Mit Aufgaben, Projekten, CRM, Dokumenten, Chat u.v.a.m.},
year = {2019},
url = {https://www.bitrix24.de/},
note = {(Zuletzt besucht am 25.02.2019)}
}
@online{bitrix:feature,
author = {Bitrix, Inc.},
title = {Bitrix24: Führende kostenlose Software zum Projektmanagement},
year = {2019},
url = {https://www.bitrix24.de/tools/tasks_and_projects/},
note = {(Zuletzt besucht am 25.02.2019)}
}
@online{sw:staleWhileRevalidate,
author = {Jake Archibald},
title = {The offline cookbook - JakeArchibald.com},
month = {12},
year = {2014},
url = {https://jakearchibald.com/2014/offline-cookbook/#stale-while-revalidate},
note = {(Zuletzt besucht am 26.02.2019)}
}
@online{sw:cacheBackToNetwork,
author = {Jake Archibald},
title = {The offline cookbook - JakeArchibald.com},
month = {12},
year = {2014},
url = {https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network},
note = {(Zuletzt besucht am 26.02.2019)}
}
@online{sw:networkBackToCache,
author = {Jake Archibald},
title = {The offline cookbook - JakeArchibald.com},
month = {12},
year = {2014},
url = {https://jakearchibald.com/2014/offline-cookbook/#network-falling-back-to-cache},
note = {(Zuletzt besucht am 26.02.2019)}
}
@online{sw:cacheThenNetwork,
author = {Jake Archibald},
title = {The offline cookbook - JakeArchibald.com},
month = {12},
year = {2014},
url = {https://jakearchibald.com/2014/offline-cookbook/#cache-then-network},
note = {(Zuletzt besucht am 26.02.2019)}
}
@online{api:doku,
author = {Erik Römmelt},
title = {Build, Collaborate \& Integrate APIs | SwaggerHub - OHM News API},
month = {02},
year = {2019},
url = {https://app.swaggerhub.com/apis-docs/roemmelter/om_api/1.0},
note = {(Zuletzt besucht am 26.02.2019)}
}
@online{dev:homescreen,
author = {Pete LePage},
title = {Your First Progressive Web App | Web Fundamentals | Google Developers},
month = {01},
year = {2019},
url = {https://developers.google.com/web/fundamentals/codelabs/your-first-pwapp/#support_native_integration},
note = {(Zuletzt besucht am 26.02.2019)}
}
@online{dev:codeSource,
author = {Pete LePage},
title = {Your First Progressive Web App | Web Fundamentals | Google Developers},
month = {01},
year = {2019},
url = {https://developers.google.com/web/fundamentals/codelabs/your-first-pwapp/},
note = {(Zuletzt besucht am 26.02.2019)}
}
@@ -0,0 +1,832 @@
% !TeX spellcheck = de_DE
% ---
% Preamble
% ---
\documentclass[12pt]{report}
\usepackage{defaultPreamble}
\bibfilename{Gruppenbericht.bib}
%
% ---
% Document Body
% ---
\begin{document}
\pagenumbering{arabic}
%
% Deckblatt
\setcounter{page}{0} % setze seitenzähler auf 0 -> alle Seitenzahlen <1 werden nicht angezeigt
{\centering
\clear % Select ClearSans for the title page
\vspace*{2\baselineskip}
%
{\large Technische Hochschule}\\
\vspace{0.3ex}
{\large Georg-Simon Ohm Nürnberg}\\
\vspace{0.3ex}
{\large Fakultät Elektrotechnik Feinwerktechnik Informationstechnik}\\
\vspace{2.5ex}
%
{\large Projekt OHM News}\\
\vspace{3.5ex}
%
{\Large\b{Projektstudienarbeit}}\\
\vspace{0.3ex}
{\Large\b{B-ME 5}}\\
\vspace{4.5ex}
{\Large\b{OHM News - See only what matters}}\\
\vspace{3.5ex}
%
{\large Wintersemester 2018/2019}\\
\vspace{10\baselineskip}
%
\begin{table}[h]
\centering \large
\begin{tabular}{rl}
\rule{0pt}{14pt}\b{Senta Mandutz} & \b{Edwina Barbalan} \\
Matrikel-Nr.: 3022812 & Matrikel-Nr.: 3064088 \\
\rule{0pt}{14pt}\b{Vivianne Pham} & \b{Xenia Gr\"unzinger} \\
Matrikel-Nr.: 2928261 & Matrikel-Nr.: 3046080 \\
\rule{0pt}{14pt}\b{Erik R\"ommelt} & \\
Matrikel-Nr.: 2843345 & \\
\end{tabular}
\end{table}
} % Need 1 empty line above for centering
%
\newpage % Seitenumbruch nach Titelblatt
\paragraph{\Large Projekt-Roadmap}$~~$\\
%
\begin{table}[h]
\centering%\small
\begin{tabularx}{\textwidth}{|X|X|X|}
\hline
\rule{0pt}{14pt}\b{Tätigkeit} & \b{Dokument} & \b{Beteiligung} \\
\hline
\rule{0pt}{14pt}Konzeption & Projektidee & Alle \\
Organisation und Pflege & Trello & Erik \\
Organisation und Pflege & Bitrix24 & Erik \\
Protokollierung der Treffen & Handschriftlich & Erik, Vivianne \\
Transkription der Protokolle & LaTeX Protokolle & Erik \\
Organisation und Pflege & Wiki & Vivianne \\
Erstformulierung & Pflichtenheft & Alle \\
Erste Entwürfe des App UI & Scribbles, Skizzen & Alle \\
Namensüberlegungen & Notizen zur Namensfindung & Alle \\
Corporate Design & Styleguide & Xenia \\
Entwicklung & Erstellung der Mockups & Xenia, Edwina \\
Entwicklung & Frontenddesign mit Bootstrap & Xenia, Edwina \\
Entwicklung & Single-Page mit Vue.js & Xenia, Edwina \\
Entwicklung & ServiceWorker & Erik \\
Entwicklung & Manifest mit Json & Erik \\
Entwicklung & Meta-Tags mit HTML & Erik \\
Entwicklung & Erstellung der API-Schnittstelle & Senta, Edwina, Vivianne, \mbox{Xenia} \\
Dokumentation RESTful API & Digital mit Swagger.io & Erik \\
Entwicklung & Server mit Node.js/Express.js & Vivianne \\
Entwicklung & Datenbank mit MongoDB & Senta \\
Entwicklung & DB-Anbindung mit Mongoose & Senta \\
Designentwürfe Plakat & Plakatgestaltung & Vivianne \\
\rule{0pt}{14pt}Design und Ausformulierung & Projektpräsentation & Edwina, Vivianne \\
\hline
\end{tabularx}
\caption{Projekt-Roadmap Tabelle}
\end{table}
%
% Inhaltsverzeichnis
\newpage
\tableofcontents
%
% Bericht
\newpage % Seitenumbruch nach Titelblatt
\section*{1. Einleitung}
\phantomsection\addcontentsline{toc}{section}{1. Einleitung}
In der heutigten Zeit werden die meisten Menschen tagtäglich mit einer Vielzahl an Reizen und Informationen überflutet. Dabei ist es nicht immer einfach den Blick auf das Wesentliche zu halten. Häufig werden wir durch Werbung, irrelevante Mails, Social Media Beiträge und weiteres schnell abgelenkt. So geht es auch vielen Studierenden und Angehörigen der Technischen Hochschule Nürnberg.\\
Für uns war das Anlass genug. Es sollte doch eine Möglichkeit zur qualitativen Verbesserung der Informationsflut im Studienalltag geben. Auf der einen Seite müssen die Hochschulangehörigen entlastet werden, damit die Konzentration für die relevanten Themen gesteigert werden kann und auf der anderen Seite sollte genügend Freiraum für die individuellen Interessen bestehen.\\
Im Folgenden wird die Entwicklung einer Idee zur Lösung dieser Problematik, über deren Ausarbeitung in Form eines Konzeptes bis hin zur praktischen Entwicklung eines lauffähigen Prototypen detailliert beschrieben.
%
\section*{2. Vorwort zur Projektidee}
\phantomsection\addcontentsline{toc}{section}{2. Vorwort zur Projektidee}
Die erste Konzeptversion zu der Projektidee von dem Projekt ''Ohm Management App'', dass im Verlauf der ersten Projektphase in ''OHM-News - See only what matters'' umbenannt wurde, wurde von Erik Römmelt erstellt. Nach gemeinsamer Überarbeitung durch Prof.Dr. Matthias Hopf, dem zukünftig betreuenden Professor und Erik Römmelt wurde die zu Projektbeginn gegebene Version des Projektkonzeptes ausformuliert.
%
\section*{3. Projektidee}
\phantomsection\addcontentsline{toc}{section}{3. Projektidee}
Die Projektidee basiert darauf, dass eine plattformunabhängige App für Studierende an der TH Nürnberg entwickelt werden soll, die Kurznachrichten filtert und in einem Nachrichtenfeed anzeigt. Das Filtern funktioniert durch Tags. Es können beim Nachrichten erstellen mehrere, jedoch mindestens eine eingegeben werden. Diese werden bei einer Überschneidung bei der Suche durch eine logische UND-Verknüpfung eingeschränkt. Angezeigt werden die Nachrichten abhängig von ihrer Priorität und Aktualität. \\
Dazu können in einer Dateiablage von Professoren und Befugten wichtige Links oder andere Dateien gesammelt werden oder als Referenz darauf verwiesen werden. Damit es den Status ''Befugte'' gibt, muss es zudem eine Rollenverteilung geben, wem welche Rechte zu stehen oder beispielsweise wer nur Nachrichten empfangen kann.
Zudem ist eine Speicherung einzelner Nachrichten aus dem Feed möglich, welche im Nachhinein in den Bookmarks oder auch Lesezeichen einsehbar sind.\\
Des Weiteren können Push-Benachrichtigungen angezeigt werden, sodass das Risiko, wichtige Nachrichten zu übersehen, minimiert wird.
Zuletzt gibt es noch ein Nutzerprofil. Hier können Daten zur Person eingetragen und angezeigt werden wie zum Beispiel das Profilbild, deren Status an der technischen Hochschule oder die Matrikelnummer.
Für den Optimalfall einer Übernahme der App von der Hochschule soll modulorientiert und wartungsaufwandsarm gearbeitet werden, um die spätere Verwaltung zu erleichtern. Auch wird Wert gelegt auf eine nutzerfreundliche und möglichst intuitive Bedienoberfläche.
%
\subsection*{3.1. Technische Umsetzung}
\phantomsection\addcontentsline{toc}{subsection}{3.1. Technische Umsetzung}
Zur Umsetzung des Projektes fiel die Zielsetzung auf die Entwicklung einer progressiven Web-App. Aufgrund ihrer Plattformunabhängigkeit und Kompatibilität mit den TH-Servern (VPN und Eduroam) und laut Erfahrungen von Professor Dr. Hopf stellt dies eine gute Lösung und sichere Wahl dar. Ebenfalls, was die Datenschutzvorkehrungen der Hochschule betrifft. \\
Es werden die Codesprachen HTML, CSS und JavaScript verwendet und als Frameworks Vue.js, Bootstrap und node.js. \\
Als Datenbank dient MongoDB und das zugehörige Framework mongoose.
%
\section*{4. Projektorganisation}
\phantomsection\addcontentsline{toc}{section}{4. Projektorganisation}
Wöchentlich fand ein Treffen, zum Austausch und zur Besprechung zurückliegender und anstehender Aufgaben, statt. An diesem Treffen nahmen in der Regel alle Mitglieder dieser Projektgruppe und der Projektbetreuer Prof.Dr. Matthias Hopf teil. Konnten Teammitglieder, gleich welcher Gründe, an einem Treffen nicht teilnehmen, so haben alle sich unverzüglich über Absprachen und den neu anstehenden Aufgaben selbstständig erkundigt.
%
\subsection*{4.1. Trello}
\phantomsection\addcontentsline{toc}{subsection}{4.1. Trello}
Zu Beginn des Projekts wurde entschieden, dass zur Verbesserung des Arbeitsablaufs ein Organisationstool hilfreich wäre. So fiel ist Trello in Gespräch gekommen.\\
Trello ist ein graphisch sehr gut aufbereitetes Projektmanagement-Tool in Form von Kanban-Boards. In der kostenfreien Version, ist es möglich ''unbegrenzt [viele] Boards, Listen, Karten, Mitglieder, Checklisten und Anhänge'' hinzuzufügen und zu nutzen\cite{trello:price}. Zur typografischen Gestaltung der Karten, um zum Beispiel eine Liste einzufügen ist die Nutzung von einigen Markdown-Markup Elementen möglich.\\
Dem Projekt zugute kommend ist die sehr einfache Bedienbarkeit, Plattform unabhängige Verfügbarkeit, sowie die gute Übersichtlichkeit der Boards. Nachteilig ist, dass es nur sehr eingeschränkt die Zeitkomponente abbilden kann. Dies ist nur über die sogenannten ''Plugins'' möglich, wobei in der kostenfreien Version nur ein Plugin je Board zugleich genutzt werden kann. So kann einem Board entweder die Funktion ''Gantt-Diagramme nutzen'' oder ''Storypoints den Karten hinzufügen'' verwendet werden.\\
Diese Einschränkung führte zur der Entscheidung gegen die Nutzung von Trello im Team.
%
\subsection*{4.2. Bitrix24}
\phantomsection\addcontentsline{toc}{subsection}{4.2. Bitrix24}
Nach dem Ausschluss von Trello begann die Umsicht nach einem anderen Tool, dass kostenfrei mit einer Gruppe von fünf Personen genutzt werden kann. Und es sollte die Funktionen Gantt-Diagramme erstellen, Aufgaben an Mitglieder verteilen und den Gewichtungs-Punkte oder ''Storypoints'' an Aufgaben zuweisen erfüllen.\\
Bitrix24 hat diese Anforderungen erfüllt, war jedoch bereits auf den ersten Blick deutlich unübersichtlicher und komplexer in der Handhabung. Da es sich bei Bitrix24 in erster Linie um ein umfangreiches CRM-System handelt, mit vielen weiteren Funktionen wie ''Aufgabenverwaltung für Gruppen'', ''Projektplanung und -management'' und ''Team-Chat''\cite{bitrix:feature}.\\
Auch nach der Deaktivierung und Ausblendung sämtlicher Funktionen, die die Nutzung von Bitrix24 komplizierter machten, blieb die Benutzeroberfläche noch unübersichtlich. Was die Umsetzung der Aufgaben als Kanban-Karten mit Gewichtungs-Punkten und der zeitlichen Darstellung der Aufgaben in einem Gantt-Diagramm betrifft, so war dies sehr angenehm und vollends zufriedenstellend möglich.\\
So wurden zahlreiche organisatorische Fristen, sowie Aufgaben organisatorischer und entwicklungs-technischer Natur in Bitrix24 erfasst und anschließend in einem wöchentlichen Treffen vorgestellt. Im Weiteren wurde die Nutzung und Pflege des Tools an alle Mitglieder übertragen.\\
Es stellte sich am Ende des ersten Projektabschnittes heraus, dass das Tool Bitrix24 aufgrund der komplexen und unübersichtliche Benutzeroberfläche kaum genutzt wurde.
%
\section*{5. Entwicklerblog}
\phantomsection\addcontentsline{toc}{section}{5. Entwicklerblog}
Die Wiki Blogeinträge in dem hochschulinternen github Repository dienen dazu, die Projektfortschritte transparent zu gestalten und eine kurze prägnante Zusammenfassung für Professoren und Studierende zu gewährleisten. Einzusehen sind die Beiträge auf der git efi Seite \cite{gitefi}.
%
\subsection*{5.1. Protokollierung}
\phantomsection\addcontentsline{toc}{subsection}{5.1. Protokollierung}
Für die Protokollierung wurden in regelmäßigen Intervallen Dokumentationen über die Tätigkeiten aller Teammitglieder geführt und diese folglich in Schrift festgehalten. Enthalten sind die Aufgabenaufteilung und die bisherige Umsetzung aller Projektteilnehmer in einer bestimmten Zeit, welche anschließend gebündelt zusammengefasst wurden.
%
\subsection*{5.2. Blogentwurf}
\phantomsection\addcontentsline{toc}{subsection}{5.2. Blogentwurf}
Da das git efi nicht nur für Professoren sondern auch Studierenden der Fakultät efi zugänglich ist, werden die Vorgänge des Projekts in kurzen Sätzen und möglichst verständlich wiedergegeben. So können sich auch Studierende außerhalb des Fachgebietes ein ungefähres Bild von dem Fortschritt der OHM News-App machen. Nach dem Umformulieren und Reduzieren auf die Kernaussagen wurden Einträge in kontinuierlichen Abständen auf dem Hochschul github\cite{gitefi} zum Abrufen hochgeladen. Diese berufen sich auf etwa ein Monats-Abstände oder kürzer. Bei neuen Einträgen wird Professor Dr. Hopf informiert, sodass er stets den Verlauf des Projektes nachvollziehen kann. In zeitlich chronologischer Reihenfolge aufgelistet, vereinfacht dies den Überblick. Insgesamt wurden im Laufe des OHM News-App Projektes sechs Wiki Blogeinträge erstellt.
%
\section*{6. Pflichtenheft}
\phantomsection\addcontentsline{toc}{section}{6. Pflichtenheft}
Das Pflichtenheft hat die Gruppe gemeinsam zusammengestellt. Dabei sollte der Datenzugriff per MongoDB erzeugt werden, alternativ könnte auch eine Schnittstelle zur Verfügung gestellt werden. Die Grafische UI der PWA sollte einen Login, ein Menü, eine Suche, ein Profil, einen Filter nach Hashtags, ein Dashboard, Einstellungen, das Nachrichtfeld und ein Formular zum Verfassen von Nachrichten umfassen. Ein Abhängigkeitsdiagramm soll erstellt werden. Die Anwendungslogik sollte im Bereich des Routings, der Darstellung und Speicherung von Nachrichten und dem LDAP-Login umgesetzt werden. Codetests sollten durchgeführt werden und das Rollenkonzept der Nutzer sollte festgelegt werden.\\
Des Weiteren sollte ein Anwendungsname gefunden und ein Logo erstellt werden. Ein UX Konzept sollte aufgestellt werden. Wenn es zeitlich noch möglich wäre, hätte man die Funktionen umsetzen sollen um die Datenbankabrufe zu filtern und Kanälen zu folgen. Ebenso hätte man die Anzeigekriterien festlegen können.
%
\section*{7. Konzepterstellung}
\phantomsection\addcontentsline{toc}{section}{7. Konzepterstellung}
Der erste Schritt zur Definition eines Layoutentwurfs war die Erstellung von Scribbles. Unter einem Scribble versteht man einen groben ersten Entwurf, der zur Ideenfindung beitragen soll. Nach Festlegung der grundsätzlichen Projektidee, zeichnete jedes unserer Teammitglieder Scribbles für verschiedene Seiten der App, die wir im Anschluss zusammen diskutierten. Wichtig war uns hierbei, dass jedes Teammitglied erst eigene Ideen entwickelte, um so viele verschiedene Aspekte zu sammeln.\cite{scrib} \\
Wir versuchten alle Ideen zu vereinen und einigten uns auf einen Layoutentwurf, welcher im Anschluss in einem detaillierten Screendesign umgesetzt wurde. In Abbildung \ref{layout} ist das Screendesign für die Startseite zu sehen. Wir legten vor allem den Fokus auf die Startseite, da diese der erste Baustein der App werden sollte. Die Navigation auf die anderen Seiten erfolgt über eine Leiste am unteren Displayrand. Die weiteren Seiten legten wir zu Beginn fest, arbeiteten diese allerdings erst mit der Zeit detaillierter aus. Ein genauer Aufbau der App wird unter 9.1. beschrieben. \\
\begin{figure}[H]
\centering
\includegraphics[width=4.5cm]{Layout}
\caption{Layoutentwurf der Startseite}
\label{layout}
\end{figure}
Bevorstehende Implementierungen von Funktionalitäten sollten, wenn bereits im Vorfeld möglich, ebenfalls in den Layoutentwürfen berücksichtigt werden. Änderungen sind natürlich auch zu einem späteren Zeitpunkt möglich, erhöhen allerdings den Aufwand. Deswegen wurde versucht, möglichst viele Funktionalitäten bereits in die Layoutentwürfe miteinzubeziehen.\\\\
Wir einigten uns im Projektteam darauf die App gemäß den Material Design Richtlinien von Google zu gestalten. Material Design ist eine von Google entwickelte Designvorgabe, die auf materienartigen Flächen basiert und dadurch zu einem sehr minimalistischen \mbox{Design} führt. Ziel dieser Designsprache ist unter anderem eine intuitive Benutzeroberfläche. Die Richtlinien wurden bei der Erstellung des Screendesigns berücksichtigt. \cite{md,md2}
%
\section*{8. Namensfindung}
\phantomsection\addcontentsline{toc}{section}{8. Namensfindung}
Zu Anfang haben wir uns für den Titel OHM-Management entschieden, mit dem Wissen, dass es höchstwahrscheinlich nicht dabei bleibt, da das Wort Management mit unserer angestrebten Funktionalität der App nur einschränkend passte.
Die Verwendung des Wortstammes Kommunikation war für unsere Benennung auch nicht zutreffend, insofern der Austausch von Information nur in eine Richtung erfolgt.
Zusätzlich gestaltete sich die Namensfindung etwas schwierig, da wir sozusagen eine App für die TH-Nürnberg entwickelten, die man auch in Verbindung mit dem OHM-Zeichen kennt. Diesbezüglich wollten wir uns bei der Benennung auch an dieses anlehnen. Um dies tun zu können, mussten wir uns erst einmal informieren, ob es ähnliche Namen bereits gibt die man aus diesem Grund dementsprechend nicht verwenden kann.
%
\subsection*{8.1. Recherche}
\phantomsection\addcontentsline{toc}{subsection}{8.1. Recherche}
Bevor also der erste Schritt in Richtung Namensfindung gemacht werden konnte, recherchierten wir über bereits vorhandene Namen, die von der TH-Nürnberg genutzt werden. Neben Namen wie InfoTHek, OHMdoc und OHM-Shop, welche für unser Thema eher irrelevant wirkten, fanden wir auch den Titel OHM-News. Dies war ein Online Journal, welches jedoch, wie sich später rausgestellt hatte, eingestellt wurde.
%
\subsection*{8.2. Brainstorming}
\phantomsection\addcontentsline{toc}{subsection}{8.2. Brainstorming}
In der Zwischenzeit setzten wir jedoch die Ideensammlung weiter fort und präsentierten in der Gruppe einzelne Namensvorschläge. Hieraus entstand eine Liste an kreativen Gedanken. Unsere Favoriten :
\begin{itemize}
\item \b{ON AIR}
\begin{itemize}
\item \b{O}HM \b{N}ews \b{A}pp for \b{I}nformation and \b{R}ediscovery/\b{R}ecommendations
\item \b{O}HM \b{N}ews Stream for \b{A}ctivities, \b{I}nformation and \b{R}ecommendations
\end{itemize}
%
\item \b{ON STREAM}
%
\begin{itemize}
\item \b{O}HM \b{N}ews \b{Stream}
\end{itemize}
%
\item \b{OHM News}
%
\item \b{TH i s}
%
\item \b{INFOhm}
\end{itemize}
Alle Vorschläge waren sehr durchdacht und kreativ. Jedoch hatten einige nicht ganz die Wirkung die wir uns erhofften, denn wir waren der Meinung, dass man das Wort On Air eher mit einem Radiosender verbindet. Ebenfalls der Nachteil bei der Idee On Stream war es, dass man stark an Streaming Dienste wie zum Beispiel Netflix erinnert wird.
%
\subsection*{8.3. Ergebnis}
\phantomsection\addcontentsline{toc}{subsection}{8.3. Ergebnis}
Nach langem Überlegen kamen wir auf den Namen OHM News zurück und dieser überzeugte uns im Endeffekt alle. Dieser Titel war recht kurz, er ist leicht zu verstehen ohne viel nachzudenken, man verbindet ihn auf Anhieb mit der TH-Nürnberg auf Grund des Wortes OHM und er verweist auf die Funktionalität die unsere App bereitstellt, die da wären Neuigkeiten, sprich News, für die Studenten der Hochschule bereitzustellen.
%
\subsection*{8.4. Claim}
\phantomsection\addcontentsline{toc}{subsection}{8.4. Claim}
Parallel zur Namensfindung, befassten wir uns auch mit dem Claim unseres Produktes. Da ein Claim die Funktion unserer App auf den Punkt bringen soll, und dabei kurz, leicht zu merken und aussagekräftig sein sollte, entschieden wir uns für eine Formulierung der Art ''nur relevante Themen werden angezeigt''. Nach einer kurzen Denkphase kamen wir auf den Claim ''See only what matters'', beziehungsweise ''Only see what matters'' vor. Dies kam bei uns allen sehr gut an, jedoch wussten wir nicht, welche von den beiden Optionen die erhoffte Bedeutung am besten hervorbringt. Sie haben zwar die selbe Bedeutung, trotz alle dem empfanden wir, dass die Betonung des Wortes „Only“ auf zwei verschiedene Faktoren des Satzes liegt. Nach reichlicher Überlegung entschieden wir uns also für die Formulierung ''See only what matters'' als Claim für unsere App.
%
\section*{9. Corporate Design}
\phantomsection\addcontentsline{toc}{section}{9. Corporate Design}
Um das ganze Konzept stimmig zu gestalten, wurde ein Corporate Design erstellt.
Wichtigste Grundlage hierfür ist das Farbkonzept. Da die App im Hochschulrahmen verwendet werden soll, wurde für die Grundfarbe der Blauton des Hochschullogos gewählt, umso eine Zugehörigkeit zu symbolisieren. Ergänzend zu diesem Blauton werden noch zwei dezente Grautöne verwendet, wie in Abbildung \ref{farbschema} zu sehen ist. \\
%
\begin{figure}[H]
\centering
\includegraphics[width=12cm]{Farbschema_CD}
\caption{Farbkonzept}
\label{farbschema}
\end{figure}
\noindent Für die Schrift wird in der gesamten App die von Google Material Design vorgegebene Schriftart Roboto verwendet. Diese ist eine serifenlosen Schriftart und deshalb durchweg gut lesbar.
Auch ein Logo ist wichtig, um einen gewissen wiedererkennungswert für die App zu schaffen. Da wir uns in der Gruppe für den App-Namen ''Ohm-News'' entschieden haben, war der erster Gedanke eine Zeitung für das Logo herzunehmen (siehe Abbildung \ref{logo1}). Die Grafik war allerdings schwierig in das Produkt einzubinden, weil sie sehr detailreich ist. \\
%
\begin{figure}[H]
\centering
\includegraphics[width=4cm]{Logo1_CD}
\caption{Erster Logoentwurf}
\label{logo1}
\end{figure}
\noindent Ein zweiter etwas minimalistischer Entwurf ist in Abbildung \ref{logo2} zu sehen, für welchen sich das Projektteam schlussendlich auch entschied. Es zeigt ein Globussymbol mit dem Hochschullogo im Zentrum, um auch hier wieder die Verbindung zur Hochschule schaffen. \\
%
\begin{figure}[H]
\centering
\includegraphics[width=4cm]{Logo2_CD}
\caption{Finales Logo}
\label{logo2}
\end{figure}
%
\noindent Das Logo wurde dann auch zu einem App Icon erweitert. In Abbildung \ref{icons} ist das Icon für Android sowie IOS Geräte zu sehen. Das Android Icon wurde analog den Material-Design Richtlinien gestaltet und besitzt deshalb einen leichten Schatten. Bei dem IOS Icon wurde das Logo lediglich auf einen einfarbigen Hintergrund gesetzt. \\
%
\begin{figure}[H]
\centering
\includegraphics[width=10cm]{Icons_CD}
\caption{App Icons}
\label{icons}
\end{figure}
%
\section*{10. Erstellung der Mockups}
\phantomsection\addcontentsline{toc}{section}{10. Erstellung der Mockups}
Auf Basis des Screendesigns wurden erste Mockups erstellt, welche mittels HTML und CSS umgesetzt wurden. Um die Elemente entsprechend den Material-Design Richtlinien zu gestalten, wurde das CSS-Framework Bootstrap mit eingebunden, das eine Auswahl an vordefinierten Gestaltungselementen und Stilen in Form von CSS-Stylesheets bereitstellt. Dazu gehören Elemente wie Buttons, Formulare, Navigationszeilen und viele mehr. Wir verwendeten Bootstrap Dateien, die auf Material Design abgestimmt sind. So wird ein einheitliches Design gewährleistet und die Styles müssen nicht selbst erstellt werden. Ein weitere Vorteil von Bootstrap, ist, dass es ein responsives Webdesign unterstützt und man deshalb plattformunabhängig entwickeln kann. Dafür sorgt ein Grid-Layout, welches den Benutzerbildschirm in 12 Spalten teilt und die Elemente, je nach verfügbarer Auflösung, anzeigt. Im ersten Projektabschnitt legten wir den Fokus besonders auf eine mobile Ansicht. Eine Desktopansicht wird ebenfalls unterstüzt, kann allerdings noch stark optimiert werden. \cite{bootstrap} \\
Durch die Ergänzung von eigenen CSS-Dateien konnten Änderungen an den vordefinierten Stilen vorgenommen werden. Sowohl unsere als auch die von Bootstrap bereitgestellten Dateien, wurden auf Grundlage von Less-Stylesheets erstellt. Dabei wird auf effiziente Weise CSS generiert. Variablen sowie Funktionen sind möglich und Vererbung wird durch \mbox{verschachtelte} Selektoren dargestellt. \cite{less} \\
Wir habe für jeden Menüpunkt eine HTML-Seite erstellt, welche im Unterpunkt genauer beschrieben werden.
%
\subsection*{10.1. Aufbau der App}
\phantomsection\addcontentsline{toc}{subsection}{10.1. Aufbau der App}
%
\begin{figure}[H]
\begin{minipage}[t]{5cm}
\vspace{0pt}
\centering
\includegraphics[width=4cm]{Homescreen_MU}
\caption{Startseite}
\label{abbildung_homescreen}
\end{minipage}
\hfill
\begin{minipage}[t]{11cm}
\vspace{0pt}
Auf der Startseite der App gelangt der Nutzer als erstes auf einen Newsfeed. Der Newsfeed besteht aus einzelnen Nachrichten in Form von Karten und soll für jeden Nutzer nur \mbox{relevante} Nachrichten anzeigen. Jede Nachricht besitzt einen oder \mbox{mehrere} Tags. Eine Nachricht wird für einen Nutzer als relevant eingestuft, wenn dieser mindestens einen Tag aus der Nachricht abonniert hat. Somit erhält jeder Nutzer eine individuelle Startseite. Zusätzlich befindet sich auf der Nachrichtenkarte oben rechts ein Icon, welches die Nachricht zu einer Fakultät zuordnet. Unten rechts kann das Lesezeichen-Icon geklickt werden, um eine Nachricht zu speichern. Auf diese Weise können die Studierenden wichtige Informationen schnell wiederfinden.\\\\
Im Header ist die Suche zu finden, in der Tags explizit gefiltert werden können. Der \mbox{Header} ist von jeder Seite aus zugängig. Im Footer befindet sich die Navigationszeile, von wo aus der Nutzer auf die weiteren Seiten gelangt.
\end{minipage}
\end{figure}
%
\begin{figure}[H]
\begin{minipage}[t]{5cm}
\vspace{0pt}
\centering
\includegraphics[width=5cm]{Links_MU}
\caption{Infoseite}
\label{abbildung_infoseite}
\end{minipage}
\hfill
\begin{minipage}[t]{11cm}
\vspace{0pt}
Der zweiten Menüpunkt, der durch ein Globus-Icon dargestellt wird, soll eine Informationsseite beinhalten. Das Konzept hierfür ist allerdings noch nicht konkret ausgearbeitet, da wir uns zuerst auf die Mitteilungsfunktionen konzentrieren wollten. Aktuell wird ein Empty State angezeigt.
\end{minipage}
\end{figure}
%
\medskip
\begin{figure}[H]
\begin{minipage}[t]{5cm}
\vspace{0pt}
\centering
\includegraphics[width=5cm]{CreateMessage_MU}
\caption{Formular}
\label{abbildung_createmsg}
\end{minipage}
\hfill
\begin{minipage}[t]{10cm}
\vspace{0pt}
Über das Plus-Icon, in der Mitte der Navigationszeile, gelangt man zu einem Formular. Hier können neue Nachrichten erstellt werden. Nachrichten sollen nur von ausgewählten Nutzergruppen, wie Fakultäten, Studienbüro und ähnlichen gesendet werden können.
Für eine Nachricht wird ein Betreff, mindestens ein Tag und der Nachrichteninhalt benötigt. Es soll eine Auswahl an vordefinierten Tags geben, damit am Ende nicht jeder Verfasser wahllos Tags setzten kann und \mbox{Duplikate} vermieden werden. Dieser Menüpunkt ist der einzige in dem keine Navigationszeile angezeigt wird. Nach Absenden einer Nachricht, gelangt man zurück auf die Startseite. Über den Button 'Abbrechen' wird man auf den vorherigen Menüpunkt zurückgeleitet.
\end{minipage}
\end{figure}
%
\begin{figure}[H]
\begin{minipage}[t]{5cm}
\vspace{0pt}
\centering
\includegraphics[width=5cm]{Gespeicherte_MU}
\caption{Gespeicherte}
\label{abbildung_gespeichert}
\end{minipage}
\hfill
\begin{minipage}[t]{11cm}
\vspace{0pt}
Der nächste Menüpunkt wird über das Lesezeichen-Icon erreicht und zeigt die gespeicherten Nachrichten an. Diese Seite ist ähnlich aufgebaut wie der Newsfeed der Startseite, beinhaltet allerdings nur die vom Nutzer gespeicherten Nachrichten.
\end{minipage}
\end{figure}
%
\medskip
\begin{figure}[H]
\begin{minipage}[t]{5cm}
\vspace{0pt}
\centering
\includegraphics[width=5cm]{Profil_MU}
\caption{Profil}
\label{abbildung_profil}
\end{minipage}
\hfill
\begin{minipage}[t]{11cm}
\vspace{0pt}
Der letzte Menüpunkt ist das Profil. \\
Oben sieht man zuerst das Profilbild, hier verwendeten wir die runde Form anstatt der üblichen Quadratischen. Darunter sieht man dann die im Vorfeld angegebene Information des Benutzers. Am Ende der sogenannten Karte befinden sich zwei Knöpfe, die Anzeigen wie vielen Kanälen man folgt und wie viele Beiträge man bis jetzt gespeichert hat. Auf der Karte rechts oben im Eck findet man den Bearbeitungs-Knopf, in Form eines kleinen Stiftes, dies ermöglicht dem Benutzer, die Seite gemäß seinen Wünschen anzupassen.
\end{minipage}
\end{figure}
%
\section*{11. Single-Page Entwicklung}
\phantomsection\addcontentsline{toc}{section}{11. Single-Page Entwicklung}
Neben klassischen Webanwendungen gibt es ebenfalls Single-Page Webanwendungen. Bei klassischen Webapplikationen gibt es mehrere untereinander verlinkte HTML-Dokumente, die dann gemeinsam eine komplette Anwendung ausmachen. Der Nachteil hierbei ist, dass die Seite beim Wechsel durch die erneute Kommunikation mit dem Server neu heruntergeladen werden muss. SPA’s jedoch bestehen nur aus einer HTML-Datei. Dies bewirkt, dass neue oder veränderte Daten dynamisch geladen werden und somit die Kommunikation zwischen Server und Client verringert wird. Es ermöglicht dem Benutzer, nach dem einmaligen laden der Seite, diese auch weiterhin im offline Modus zu steuern. Sollte also die Interaktion mit dem Server unterbrochen werden, werden die zwischengespeicherten Daten einfach aus dem Webbrowser verwendet.\cite{spa:1}\cite{spa:2}
%
\subsection*{11.1. Warum Vue.js?}
\phantomsection\addcontentsline{toc}{subsection}{11.1. Warum Vue.js?}
Neben dem Vue.js Framework gibt es noch weitere wie zum Beispiel react.js, angular.js, meteor.js und viele mehr. Warum wir uns jedoch für das Vue.js Framework entschieden haben, werden wir Ihnen nun erläutern. Vue.js das jüngste von den drei größten Frameworks, es hat jedoch den Vorteil, dass es aus den Fehlern der Vorgänger lernen konnte und diese schon im Vorfeld überdenken und ausmerzen konnte. Da es auf Sprachen wie HTML, CSS und JavaScript aufbaut, lässt es sich problemlos in schon bestehende Projekte einbauen.
Die Syntax ist hierbei, im Vergleich zu anderen, besser lesbar. Hierfür werden nämlich sogenannte ''Single File Components'' verwendet.\cite{vue:warum} Sogenannte Komponenten sind wiederverwendbare Vue-Instanzen mit einem Namen, die als benutzerdefiniertes Element in einer Vue-Stamminstanz verwendet werden. Wie so eine erstellte Komponente aussieht, werden wir Ihnen im folgenden erörtern.
%
\subsection*{11.2. Vue.js in vorhandene Mockups einbinden}
\phantomsection\addcontentsline{toc}{subsection}{11.2. Vue.js in vorhandene Mockups einbinden}
Nach reichlicher Recherche und dem einlesen in das uns vorher unbekannte Framework, war es an der Zeit unsere HTML-Seiten in Vue-Komponenten umzuwandern. Wir setzten uns erstmal zusammen und erstellten die Startseite.
Im Folgenden wird der Aufbau der Home-Komponente näher erläutert. In Listing 1 ist der dazugehörige Quellcode zu sehen. Nicht enthalten sind zur Komponente zugehörige Methoden. Die Home-Komponente wird in unserem Projekt als HomeRouter betitelt.
%
\begin{center}
\begin{lstlisting}[caption={HomeRouter-Komponente},captionpos=b]
const HomeRouter = {
template: `
<div id="om-msg-cards">
<MsgCard
v-for="id in messagelist.slice().reverse()"
:key="id"
:msg="messages[id] || {}"
></MsgCard>
</div>`,
//Restliche Komponente
...
\end{lstlisting}
\end{center}
%
Im Newsfeed, welcher der Hauptbestand der Startseite ist, wird eine Liste von Nachrichten dynamisch generiert, sodass jeder Nutzer später einmal eine individuelle Ansicht erhält, abhängig von den Tags, die dieser abonniert. \\
Jede Komponente besitzt ein Template in Form eines Strings, welcher HTML-Code \mbox{beinhaltet}. Die Komponente HomeRouter ist verschachtelt und enthält die Komponente MsgCard. Der Quellcode für diese Komponete ist in Listing 2 zu sehen.
%
\begin{center}
\begin{lstlisting}[caption={MsgCard-Komponente},captionpos=b]
Vue.component('MsgCard', {
template: `<div class="om-card card">
<h6 class="msg-head">
<b>{{ msg.subject }}</b>
<img src="favicon.ico" width=20px height=20px>
</h6>
{{ msg.message }}<br><br>
<a href="#">{{ msg.tag }}</a></p>
<div class="om-card-footer"> <div class="om-user-line">
<i class="material-icons">account_circle</i>
Erstellt von {{ msg.user }}</div>
<i class="material-icons">bookmark_border</i>
</div></div>`,
props: ['msg']
});
\end{lstlisting}
\end{center}
%
Eine MsgCard stellt eine einzelne Nachricht in Form einer Karte dar. In der HomeRouter-Komponete geht eine For-Schleife alle vorhandenen Nachrichten-Ids durch. Dabei werden der MsgCard-Komponente alle relevanten Informationen übergeben, die für eine Darstellung benötig wird. Am Ende stellt die App eine fortlaufende Liste mit allen Nachrichten dar.\\\\
Da wir nun ein bisschen geübter im Umgang mit Vue waren, teilten wir uns die restlichen Seiten auf. Dabei gingen wir ähnlich vor, wie bei der Home-Komponente.
%
\subsection*{11.3. Routing}
\phantomsection\addcontentsline{toc}{subsection}{11.3. Routing}
Vue.js bietet eine Reihe von Funktionen, mit denen Sie wiederverwendbare Webkomponenten erstellen können. Routing ist eine dieser Methoden. Der Benutzer kann somit zwischen den Seiten wechseln, ohne diese erneut zu aktualisieren. Dies ermöglicht dem Benutzer eine einfache und dynamische Navigation durch die Webanwendung. Wir werden Ihnen nun, anhand unseres Codes erklären, wie ein Vue.js-Router funktioniert.\cite{vue:router} \\
Zunächst richten wir hierfür die Navigation über die Navigationsbar ein, die wir mittels einem Router-Link-Element erstellt haben. Dieser gibt an, auf welche der angegebenen JS-Datei zugegriffen wird sobald man auf das ausgewählte Item klickt.
%
\begin{center}
\begin{lstlisting}[caption={Navbar},captionpos=b]
<nav class="nav nav-tabs nav-justified om-nav" v-if="$route.path !=='/createMessage' ">
<router-link to="/home" class="nav-item nav-link">
<i class="material-icons">home</i></router-link>
<router-link to="/files" class="nav-item nav-link">
<i class="material-icons">language</i></router-link>
<router-link to="/createMessage" class="nav-item nav-link outlined">
<i class="material-icons">add_circle</i></router-link>
<router-link to="/bookmark" class="nav-item nav-link">
<i class="material-icons">bookmark</i></router-link>
<router-link to="/profil" class="nav-item nav-link">
<i class="material-icons">person</i></router-link>
</nav>
\end{lstlisting}
\end{center}
%
Im Folgenden werden Komponenten definiert und dann in ein neues Array von Objekten, so genannte Routen, eingefügt. Zu beachten ist die Zuordnung eines URL-Pfads zu der dazugehörigen Komponente. Daraufhin wird ein Vue-Router definiert, der an ein neues Vue-Objekt übergeben wird. Wenn nun beispielsweise ''/home'' geladen ist, wird die Home-Komponente in der Router-Ansicht gerendert und aufgerufen. Die erste Zeile mit dem ''/'' steht für die Startseite, auf die man gelangt, sobald man die App öffnet.\cite{vue:router3} \\
%
\begin{center}
\begin{lstlisting}[caption={Routing},captionpos=b]
const routes = [
{ path: "/", component: HomeRouter },
{ path: "/home", component: HomeRouter },
{ path: "/files", component: FileRouter },
{ path: "/createMessage", component: CreateMsgRouter },
{ path: "/bookmark", component: BookmarkRouter },
{ path: "/profil", component: ProfilRouter },
];
const router = new VueRouter({
routes
});
var app = new Vue({
router,
el: '#api',
methods: {
...
},
});
\end{lstlisting}
\end{center}
%
\section*{12. Service Worker}
\phantomsection\addcontentsline{toc}{section}{12. Service Worker}
Der ServiceWorker ist eine JavaScript Datei, die üblicherweise im root-Verzeichnis der Webanwendung liegt. Für die Funktionsweise ist die Namensgebung der Datei nicht maßgebend. Damit der ServiceWorker überhaupt funktionieren kann, wird eine SSL-Verbindung benötigt. Ist eine sicher Verbindung gegeben, kann der ServiceWorker (im Weiteren auch mit ''SW'' abgekürzt) nach erfolgreichem Laden der Webseite im Browser des Clients registriert werden. Nachdem der SW zeitlich unabhängig, also asynchron im Browser läuft, muss bei der Entwicklung und Umsetzung des ServiceWorker-Codes darauf geachtet werden, dass in Promises geschrieben wird. Da Promises ein wenig schwieriger nachzuvollziehen sind und insbesondere das Problem der sogenannten Callback-Hell nicht lösen, bietet es dich an den SW in Async/Await Struktur zu schreiben. Diese basiert auf dem asynchronen Prinzip der Promises und lässt sich dennoch lesen wie synchrone Funktionen.\\
Zunächst wird der ServiceWorker, nach dem vollständigen Laden der Webanwendung, versuchen sich im Browser des Clients zu registrieren. Schlägt die Registrierung fehl, ist dies ein Anzeichen dafür, dass der genutzte Browser keine ServiceWorker unterstützt. Es folgen zwei Code Umsetzungen, beide dienen der Registrierung:\\
%
\begin{center}
\begin{lstlisting}[caption={Register, Promise/Then-Codestruktur},captionpos=b]
/* Promise / Then */
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/serviceWorker.js').then(function(registration) {
// Registration was successful
}, function(err) {
// registration failed
});
});
}
\end{lstlisting}
\end{center}
%
\begin{center}
\begin{lstlisting}[caption={Register, Async/Await-Codestruktur},captionpos=b]
/* Async / Await */
const registerServiceWorker = async () => {
try {
const registration = await navigator.serviceWorker.register('/serviceWorker.js', {scope: '/'});
// Registration successful
} catch (err) {
// Registration failed
}
return;
}
if ('serviceWorker' in navigator) {
window.addEventListener('load', registerServiceWorker());
}
\end{lstlisting}
\end{center}
%
Einmal in der Promise.then Schreibweise und einmal in der Async/Await Schreibweise.\\
Nach erfolgreicher Registrierung, folgt das ServiceWorker Event Installation. Dabei wird eine neue ServiceWorker Version im Browser angelegt und ein Abbild der festgelegten Dateien, hier als Beispiel nur die ''index.html'' (siehe filesToCache), im sogenannten ''Local Storage'' mittels Cache-Managements (auch Cache API genannt) gespeichert. Diese beiden Code Blöcke zeigen den Install-Vorgang:
%
\begin{center}
\begin{lstlisting}[caption={Install, Promise/Then-Codestruktur},captionpos=b]
/* Promise / Then */
const cacheName = 'appname-v1-0';
const filesToCache = [ '/index.html' ];
self.addEventListener('install', function(event) {
console.log('[ServiceWorker] Install');
event.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('[ServiceWorker] Caching app shell');
return cache.addAll(filesToCache);
})
);
});
\end{lstlisting}
\end{center}
%
\begin{center}
\begin{lstlisting}[caption={Install, Async/Await-Codestruktur},captionpos=b]
/* Async / Await */
const installNewCache = async (event) => {
const cacheName = 'appname-v1-0';
const filesToCache = [ '/index.html' ];
const cacheStatic = await caches.open(cacheName);
cacheStatic.addAll(filesToCache);
console.log('[ServiceWorker] Cache static files.');
return;
}
self.addEventListener('install', event => {
// don't wait
self.skipWaiting();
// cache static files
event.waitUntil(installNewCache());
console.log('[ServiceWorker] Install');
});
\end{lstlisting}
\end{center}
%
Anschließend folgt die Aktivierung. Hier wird aufgeräumt und zwar die alten ServiceWorker Versionen. Es sollte immer nur eine ServiceWorker Version je Webanwendung im Browser registriert und installiert sein.
%
\begin{center}
\begin{lstlisting}[caption={Activate, Promise/Then-Codestruktur},captionpos=b]
/* Promise / Then */
self.addEventListener('activate', function(event) {
// ServiceWorker activated
event.waitUntil(
caches.keys().then(function(cacheKeyList) {
return Promise.all(
cacheKeyList.map(function(cacheKeyList) {
if (cacheWhitelist.indexOf(cacheKeyList) === -1) {
// Old Cache removed
return caches.delete(cacheKeyList);
}
})
);
})
);
return self.clients.claim();
});
\end{lstlisting}
\end{center}
%
\begin{center}
\begin{lstlisting}[caption={Activate, Async/Await-Codestruktur},captionpos=b]
/* Async / Await */
const cacheCleanUp = async () => {
const cacheKeyList = await caches.keys();
const deletions = cacheKeyList
.filter(key => key.startsWith(appPrefix) && !cacheKeyList.includes(key))
.map(key => {
caches.delete(key)
// Old Cache removed
});
for (const success of deletions) {
await success;
}
return;
}
self.addEventListener('activate', event => {
event.waitUntil(cacheCleanUp());
clients.claim();
// ServiceWorker activated
});
\end{lstlisting}
\end{center}
%
Als letzter Schritt bleibt nur noch der 'fetch'. Das ist jenes Event in dem der ServiceWorker neue, aktuelle Dateien aus dem Netzwerk bezieht und die Dateiversionen im Cache, lokalen Speicher aktualisiert. Hier gibt es verschiedene Vorgehensweisen. Wir haben uns entschieden, das der ServiceWorker nur jene API GET-Anfragen speichern soll. Dies dient der Vermeidung doppelter und korrupter Datensätze. Im Weiteren soll überprüft werden, ob die angefragten Dateien mit den gespeicherten Dateien übereinstimmt. Falls nicht, gibt es keine gespeicherten Daten und eine Netzwerkanfrage wird zwingend benötigt. Andernfalls zeige die gespeicherten Daten an und tätige anschließend eine Anfrage ans Netzwerk. Wenn aktuellere Daten aus dem Netzwerk empfangen werden, so aktualisiere die gespeicherten Dateien und zeige die aktuelleren Daten an. Zusätzlich wird noch ein wenig der Ursprung der Anfrage überprüft.
%
\begin{center}
\begin{lstlisting}[caption={Fetch, Promise/Then-Codestruktur},captionpos=b]
/* Promise / Then */
self.addEventListener('fetch', function(event) {
/* We should only cache GET requests */
if (event.request.method !== 'GET') {
/* If we don't block the event as shown below,
then the request will go to the network as usual. */
console.log('[ServiceWorker] Fetch event ignored.',
event.request.method, event.request.url);
return;
}
event.respondWith(
caches.match(event.request)
.then(function(response) {
return fetch(event.request)
.then(function(response) {
// Check if response is valid, status is 200, response type is basic
// (indicates request is from origin, means that requests to third party
// assets aren't cached as well.
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
caches.open(CACHE_NAME)
.then(function(cache) {
// We have to clone the response here because request bodies can only
// be read once. Placing a response in the cache counts as a read.
cache.put(event.request, response.clone());
});
// Cache hit - return response
if (response) return response;
return response;
});
})
.catch(function(err) {
// Report a lack of connectivity to the user.
});
});
\end{lstlisting}
\end{center}
%
\begin{center}
\begin{lstlisting}[caption={Fetch, Async/Await-Codestruktur},captionpos=b]
/* Async / Await */
self.addEventListener('fetch', event => {
/* We should only cache GET requests */
if (event.request.method !== 'GET') {
/* If we don't block the event as shown below,
then the request will go to the network as usual. */
console.log('[ServiceWorker] Fetch event ignored.',
event.request.method, event.request.url);
return;
}
event.respondWith(async function update() {
try {
var requestURL = new URL(event.request.url);
// Start the network request as soon as possible.
const networkPromise = fetch(requestURL);
const cachedResponse = await caches.match(event.request);
const networkResponse = await networkPromise;
// Check if response is valid, status is 200, response type is basic
// (indicates request is from origin, means that requests to third party
// assets aren't cached as well.
if (!networkResponse || networkResponse.status !== 200
|| networkResponse.type !== 'basic') return networkResponse;
const cache = await caches.open(staticCacheKey);
// We have to clone the response here because request bodies can only
// be read once. Placing a response in the cache counts as a read.
cache.put(event.request, networkResponse.clone());
if (cachedResponse) return cachedResponse;
// ServiceWorker fetch
return networkResponse;
} catch (err) {
// Report a lack of connectivity to the user.
}
}());
});
\end{lstlisting}
\end{center}
%
Die Bildausschnitte mit dem Code wurden teils von mir erstellt, teils stammen diese von dieser Internetseite\cite{dev:codeSource}\cite{sw:cacheThenNetwork}.
%
\pagebreak
\section*{13. ''Add-To-Homescreen''-Funktion}
\phantomsection\addcontentsline{toc}{section}{13. ''Add-To-Homescreen''-Funktion}
Für die Implementierung der ''Add-to-Homescreen''-Funktionalität sind für Android, iOS und die diversen Browser unterschiedliche Schritte nötig.\\
Bei Android genügt es eine manifest.json Datei im root-Verzeichnis der App abzulegen. Diese Manifest-Datei enthält Meta-Informationen von App-Icon, über App-Name, über die Start-URL bis hin zu der Theme-Farbe für den Hintergrund des App-Icons oder dem Navigationsmenü im Browser. Wenn ein Nutzer im Abstand von 5 Minuten (Standard-Einstellung) die Webseite zweimalig aufruft, so wird dieser gefragt, ob er die App zum Homescreen hinzufügen möchte. Ab da kann er diese über das App-Icon als auch über den Browser starten.\\
Für iOS müssen die Meta-Informationen im Bereich des Head-Elements, der index.html eingefügt werden. So wird dort das Aussehen von mehreren App-Icon Versionen definiert. Dies wird benötigt, da die meisten Apple-Geräte unterschiedliche Density (Pixel per Inch) haben. Des Weiteren werden Konfigurationseinstellungen aktiviert oder deaktiviert, als auch das Aussehen des Ladescreens für iOS Geräte festgelegt.\\
Auch die Windows-Mobilgeräte muss in der index.html Datei im Head-Bereich mittels Meta-Informationen mitgeteilt werden, wie der Ladebildschirm oder das Appicon aussehen soll (vgl.\cite{dev:homescreen}).
%
\section*{14. Erstellung der API-Schnittstelle}
\phantomsection\addcontentsline{toc}{section}{14. Erstellung der API-Schnittstelle}
Nachdem Server und die Clientseite der PWA funktionsfähig waren, war es noch relevant, dass zwischen diesen beiden kommuniziert werden kann. Dafür verwendet man die API.\cite{api} API steht für ''Application Programming Interface'' und diese Programmierschnittstelle kann wie in diesem Projekt zum Zugriff auf die Datenbank verwendet werden. Dabei handelt es sich in diesem Fall um einen RESTful Webservice.\cite{rest} \\
%
\begin{figure}[H]
\centering
\includegraphics[width=0.3\textwidth]{ajax.png}
\caption{Ajax Modell einer Webanwendung. Quelle: nach Haischt, Daniel; https://de.wikipedia.org/wiki/Ajax\_(Programmierung)\#/media/File:Ajax-vergleich.svg}
\label{ajax}
\end{figure}
%
\noindent Das asynchrone Datenübertragungskonzept AJAX wurde zur Kommunikation zwischen Browser und Server verwendet.\cite{ajax1} \cite{ajax2} \cite{ajax3} Dabei werden wie in Abbildung \ref{ajax} zu sehen ist HTTP Requests von dem Browser an den Server geschickt. Diese werden daraufhin von dem Server verarbeitet und es werden entweder Daten zurückgegeben oder ein HTTP Response mit einer Rückmeldung. Mit der Verwendung von AJAX hat man einige Vorteile, zum Beispiel, dass man die Seite nicht neu laden muss, damit die neuen Inhalte erscheinen und dass der Browser bereits weiter Requests schicken kann, ohne auf die Responses des Servers warten zu müssen. Da die gesamte PWA des Projekts in JavaScript geschrieben ist, ist es zudem vorteilhaft, dass die AJAX-Engine in JavaScript geschrieben wird. Die Daten aus der Datenbank werden im JSON-Format transportiert.\\
\\
Um die Funktionsweise einer derartigen Schnittstelle zu verstehen, wurde der Code einer PWA die Professor Matthias Hopf geschrieben und der Gruppe zur Verfügung gestellt hat, analysiert. Dieser war jedoch etwas zu komplex für einen Einsteiger in das Thema. Deswegen wurde auf simplere Beispiele in Tutorials zurückgegriffen. Diese Beispiele konnten nun auf das eigene Projekt übertragen und getestet werden. Nach einer ausführlichen Erläuterung durch Professor Hopf anhand eines Beispiels im Projektzusammenhang, war ein grundsätzliches Verständnis über die Schnittstelle vorhanden. Nun konnte die konkrete Umsetzung der API beginnen.\\
Die erste Methode die umgesetzt wurde war ''list\_messages''. Diese zeigt nun alle Nachrichten der Datenbank auf der Startseite der PWA an. Dazu musste zuerst vom Server die IDs aller Nachrichten abgefragt werden. Man schreibt eine API Funktion, die ein GET Request an den Server schickt. Dieser sucht nach den IDs per find Anfrage in der Datenbank und antwortet mit einem HTTP Response, der das Array mit den IDs enthält. Auf der Client Seite wird nun für jede ID per GET Request alle Informationen der Nachricht angefragt. Der Server sucht die gefragten Information und schickt diese zurück mit dem dazugehörigen Nachrichtentext an den Browser.\\
%
\begin{center}
\begin{lstlisting}[caption={''list\_messages''-Methode},captionpos=b]
list_messages: function () {
$.ajax({url: "api/ids", method: "GET"})
.done(jd => {
_messagelist.splice(0, _messagelist.length);
_messagelist.push.apply(_messagelist, jd);
console.log("jd: "+jd);
for (var e in jd) {
if (!_messages[jd[e]]) {
get_insert_message(jd[e]);
}
}
}).fail(function (e, f, g) {
console.log("err: " + e + f + g);
});
}
\end{lstlisting}
\end{center}
%
Was für Vorgänge beim Senden einer Nachricht in einer API getätigt werden, ist im Folgenden aufgeführt. Hierfür muss die Mongodb heruntergeladen und angebunden sein.\\
In der Server.js Datei wird durch die ''App.post''-Methode eine Nachricht erstellt. Dabei wird in ''createMsg'' die Funktion aufgerufen, in einen String umgewandelt und die Nachricht anschließend in der Datenbank gespeichert. Mit der POST-Methode können Nachrichten in die Datenbank gespeichert werden und anhand von einer GET-Methode angezeigt werden.\\
Nun müssen in der createMsg.js alle Werte einsetzbar gestaltet werden. Wichtig dabei ist, sich an das vorgegebene Mongo Schema zu halten. Durch die ajax-Methode ''post'' können große Datenmengen an den Server geschickt werden. Zuletzt soll die Konsole auch einen Error anzeigen, falls der Abruf der Funktion erfolglos war. \\
In Abbildung \ref{sys} kann man diese Vorgänge nachverfolgen. Diese beiden Funktionen haben am Ende funktioniert und können zusammen verwendet werden. \\
\begin{figure}[H]
\centering
\includegraphics[width=0.7\textwidth]{system.png}
\caption{Erstellen einer Nachricht und Anzeigen aller Nachrichten}
\label{sys}
\end{figure}
%
\section*{15. Aufsetzung des Servers}
\phantomsection\addcontentsline{toc}{section}{15. Aufsetzung des Servers}
Zunächst muss ein http port eingerichtet werden. Über einen lokalen Server über node.js ist die Ohm News-App abrufbar. Mit der createServer Methode
%
\begin{center}
\begin{lstlisting}[caption={''list\_messages''-Methode},captionpos=b]
http.createServer(app).listen(http_port, function () {
console.log("Express http server listening on port " + http_port);
});
\end{lstlisting}
\end{center}
%
lässt sich ein Server aufsetzen. Hier wird auch der Listener für den Port impliziert und auf der Konsole ausgegeben, wenn die Verbindung erfolgreich war. \\
Um die wichtigsten Fehlermeldungen und Bugs auszugeben, wird für unsere node.js App der Logger ''Morgan'' verwendet. Zusätzlich wird eine try-catch Funktion eingeordnet, da Zertifikat und Schlüssel noch nicht zertifiziert sind (siehe SSL und Keys) und somit vorerst ein Fehler angezeigt wird.
%
\subsection*{15.1. SSL und Keys}
\phantomsection\addcontentsline{toc}{subsection}{15.1. SSL und Keys}
Um die Applikation für die Nutzer sicher zu gestalten und eine Verschlüsselung sensibler Daten zu gewährleisten, sorgt ein SSL(Secure Socket Layer). Dadurch wird ein sicherer Kanal zwischen Web-Server und Client bereitgestellt und aus der http- wird eine https-Verbindung.
\setlength{\parindent}{0cm}
Übergangsweise werden ein selbst verifizierter Schlüssel und Zertifikat erstellt, welche später durch echte ausgetauscht werden. In einem neu erstellten Ordner ''keys'' werden das Zertifikat und der Schlüssel platziert.
%
\section*{16. Datenbankanbindung}
\phantomsection\addcontentsline{toc}{section}{16. Datenbankanbindung}
Für diese Anwendung wird eine Datenbank benötigt, weil die geschriebenen Nachrichten gespeichert werden müssen. Später sollen auch User mit ihren Userinformationen gespeichert werden. Im Falle dieses Projekts wurde sich für eine MongoDB entschieden.\\
MongoDB ist eine dokumentenorientierte NoSQL Datenbank. Diese ist für diese Anwendung sinnvoll, da sie im Vergleich zu SQL Datenbanken flexibler handhabbar ist und somit auch viele Einstellungen noch im Nachhinein geändert werden können.\cite{mongo} \\
Um den Umgang mit MongoDB zu vereinfachen wurde Mongoose, eine Object Data Modeling Bibliothek, verwendet.\cite{mongoose} \\
Da man zu Beginn des Projekts noch nicht mit NoSQL Datenbanken gearbeitet hatte, mussten zuerst Tutorials und Erklärungen gelesen werden. Anhand von Beispieltests mit einer lokalen Datenbank konnte die grundsätzliche Funktionsweise bald verstanden werden. Daraufhin wurde das grobe Schema für eine Nachricht aufgebaut. Eine Nachricht sollte einen Betreff, eine Nachricht, den schreibenden User und die Tags beinhalten wie man in Listing \ref{lst:json} an einer beispielhaften JSON-Datei sehen kann. Die Datenbank musste konfiguriert und gestartet werden.
%
\begin{center}
\lstset{language=xml}
\begin{lstlisting}[caption={Nachricht im JSON-Format},captionpos=b,label=lst:json]
{
"messages": [{
"subject": "Test"
"message": "Hallo Welt!"
"tag": "#OHM_News"
"user": "User1"
}]
}
\end{lstlisting}
\lstsetdefault
\end{center}
%
Die serverseitigen Codeteile zur API und der Code für die Datenbank sind momentan zur Vereinfachung in der server.js Datei. Später sollen diese zur Übersichtbarkeit und Wartbarkeit ausgelagert werden. Das Schema für die Nachricht und die Konfiguration der Datenbank sind zu diesem Zweck bereits in eigenen js-Dateien zu finden.\\
Sobald all das lokal funktioniert hat, wurde die Portierung auf einen Server der Hochschule vorgenommen.
%
\section*{17. Plakatgestaltung}
\phantomsection\addcontentsline{toc}{section}{17. Plakatgestaltung}
Zu dem Plakat für die OHM News App wurde eine Ideensammlung in Form von Skizzen angefertigt.
\begin{figure}[H]
\centering
\includegraphics[width=6.5cm, angle=90, scale=1.409]{IMG_Skizze1.jpg}\quad
\includegraphics[width=6.5cm]{IMG_Skizze2.jpg}
\includegraphics[width=6.5cm, angle=270, scale=1.409]{IMG_Skizze3.jpg}\quad
\includegraphics[width=6.5cm, angle=270, scale=1.409]{IMG_Skizze4.jpg}
\end{figure}
%
Die Umsetzung derer folgte in Form von ersten Entwürfen in Illustrator, welche der gesamten Gruppe vorgestellt wurden. \\
Zu den hier unten aufgeführten Plakatentwürfen wurden jeweils zwei Versionen erstellt. Diese zwei kamen jedoch in die engere Auswahl und sollten noch weiter ausgearbeitet werden.
\begin{figure}[H]
\centering
\includegraphics[width=6.5cm]{PDF_Plakat1.pdf}\quad
\includegraphics[width=6.5cm]{PDF_Plakat2.pdf}
\end{figure}
%
Bei der nächsten Vorauswahl an fünf OHM News Plakat-Versionen wurde daraufhin auf zwei reduziert, welche hier zu sehen sind.
\begin{figure}[H]
\centering
\includegraphics[width=6.5cm]{PDF_Plakat3.pdf}\quad
\includegraphics[width=6.5cm]{PDF_Plakat4.pdf}
\end{figure}
%
Diese wurden weiter bearbeitet und Änderungen in Absprache mit der Projektgruppe vor\-genommen. Auch die Meinung von Professor Dr. Hopf beeinflusste die Plakatgestaltung.\\
Einige Ansätze später und nach etlichem Experimentieren stand die Endauswahl bei diesen nächsten zwei Exemplaren. Besonderen Zeitaufwand nahm die Suche nach einer geeigneten Schrift für die zweite Version in Anspruch. Es gestaltete sich als äußerst schwierig, den künstlerischen leicht verspielten Ausdruck des Plakatlayouts mit der Seriösität des Hochschulcharakters in einer Schrift zu vereinen.
\begin{figure}[H]
\centering
\includegraphics[width=6.5cm]{PDF_Plakat5.pdf}\quad
\includegraphics[width=6.5cm]{PDF_Plakat6.pdf}
\end{figure}
%
Zuletzt fiel die Wahl auf das linke mit blauem Verlauf versehene Plakat. Denn es vermittelt genau das, was der Slogan des Projektes aussagt. Der Fokus liegt auf der Nachrichtenbox, also das was wichtig ist, und der Rest verschwimmt im Hintergrund. \\
Bei den wöchentlichen Treffen wurde im Allgemeinen der Fortschritt des Plakatdesigns analysiert und mit konstruktiver Kritik versehen. Dieses Feedback wurde im Rahmen des Möglichen umgesetzt und so kristallisierte sich die finale Version heraus. Nach gründlichem Finetuning und einer Umformatierung von DIN A3 auf DIN A1 wurde die Endfassung per Mail eingeschickt und schließlich ausgedruckt. Im Anhang ist sie in voller Auflösung aufzufinden.
%
\section*{18. PowerPoint Präsentation}
\phantomsection\addcontentsline{toc}{section}{18. PowerPoint Präsentation}
Als sich das Projekt für dieses Semester zum Ende hinneigte, war es an der Zeit sich für die Projektpräsentation vorzubereiten. Hierfür erstellte ich für die Gruppe eine PowerPoint Präsentation. Zunächst besprachen wir Gemeinschaftlich, wie diese ungefähr aussehen sollte. Wir waren alle einstimmig der Meinung, dass wir die Präsentation sehr einfach gestalten wollen, ohne zu lange Textpassagen mit einzubinden. Wir wollten es nämlich verhindern, dass das Publikum von zu vielen Faktoren in der Darstellung abgelenkt werden, zum Beispiel durch lesen von langem Fließtext. Durch den Gebrauch von bildlicher Veranschaulichung, versuchten wir es den Zuschauern zu ermöglichen, das Verständnis zu erleichtern.
Auch hatten wir die Idee, eine fiktive Persona zu erstellen, zu dem der Betrachter einen Bezug finden kann und diese durch das vorgetragene Thema begleitet.
%
\subsection*{18.1. Gestaltungselemente}
\phantomsection\addcontentsline{toc}{subsection}{18.1. Gestaltungselemente}
Da man jedoch, aus Urheberrechtlichen Gründen, nicht einfach wahllos Charaktere aus dem Internet benutzen kann, suchte ich nach einer Seite, auf der man solche Animationen erstellen kann. Ich bin auch direkt fündig geworden, trotz alle dem war es erstmal schwer etwas zu finden das man auch Gratis verwenden konnte.
Dann bin ich auf die Seite www.Powtoon.com gestoßen. Dort fing ich als nächstes an, unseren sogenannten Klaus in all den Stimmungen, die wir für unsere Präsentation brauchten, zu gestalten. \\
Wir entschieden im Vorfeld als Gruppe, dass sich unser Männchen so wenig wie möglich bewegen soll, aus dem gleichen Grund wie auch oben schon erwähnt und zwar um den Fokus nicht zu sehr darauf zu richten und das Publikum nicht zu sehr abzulenken. Nachdem das erledigt war, war es an der Zeit, die eben erstellten Videos von unserer Persona einzeln so zu kürzen und zu beschneiden, dass man sie gut auf die gewünschte Position einsetzen konnte. Als dieses erledigt wurde, besprach ich weitere Vorgehen einzeln mit den anderen Gruppenmitgliedern. Ich Informierte mich darüber, über welches Thema jeder einzelne vorträgt und wie sie sich ihren Präsentationsteil vorstellen. Diese Wünsche und Vorstellung setzte ich dann dementsprechend in unserer Präsentation um.
Falls sie an dem Ergebnis interessiert sind, haben sie die Möglichkeit sich dies im Git-Repository im Ordner ''presentation'' anzusehen.
%
\section*{19. Weiterverwendung für Infoscreens}
\phantomsection\addcontentsline{toc}{section}{19. Weiterverwendung für Infoscreens}
Am Mittwoch, den 24.10.2018 fand das Info-Treffen mit Frau Fabi statt, welches von Prof.Dr. \mbox{Matthias} Hopf organisiert wurde. Dieses Treffen diente der Besprechung und Erörterung zukünftiger Nutzungsmöglichkeiten der Info-Screens, das sind geplante und teils bereits montierte Bildschirme zur Information-Bereitstellung für die Hochschulangehörigen auf dem Campusgelände. Frau Fabi ist Angehörige der Fakultät efi und in ihrer Funktion für die Dokumentenlenkung und Webredaktion an dem ''Tag''-basiertem Nachrichten-System unserer Webanwendung interessiert, welches zukünftig auch auf den Bildschirmen dargestellt werden könnten. Sie ließ auch das Interesse von Herr Pöhlau, dem Dekan der Fakultät efi bekunden.\\
Außerdem informierte Frau Fabi uns, die Projektmitglieder und unseren Projektbetreuer, dass die Fakultät efi sicher die restlichen Info-Screens montieren werde und derzeit Besprechungen hinsichtlich weiterer Sicherheitsaspekte zu den Montagebereichen am Laufen sind. Ergänzend würden sie sich eine Softwarelösung wünschen, für die Ansteuerung und ''Befüllung'' der Monitore mit den Daten unserer App. Diese wird jedoch nicht mehr im Rahmen unseres Projektes umgesetzt werden. Die Umsetzung einer Softwarelösung wäre als Idee für ein zukünftiges Projekt denkbar, nach Aussage von Prof.Dr. Matthias Hopf.
%
\section*{20. Soll/Ist Vergleich auf Basis des Projektplanes}
\phantomsection\addcontentsline{toc}{section}{20. Soll/Ist Vergleich auf Basis des Projektplanes}
Zu Beginn des Semesters hat die Gruppe zusammen ein Pflichtenheft für den ersten Abschnitt des Projekts erstellt. Ziel war es den Datenzugriff zu ermöglichen und eine Schnittstelle zur Verfügung zu stellen. Es besteht eine Datenbankanbindung über den Server der Technischen Hochschule Nürnberg. Darüber hinaus sollten die folgenden Ziele im Bereich der grafischen Benutzerschnittstelle erreicht werden.\\
Die Oberfläche der PWA sollte über eine Login Möglichkeit, eine Menüleiste, eine Suchoption, ein Profil, eine Filterfunktion, ein Dashboard als Hauptseite, ein Einstellungsmenü, Nachrichtenfelder und ein Formular zum Verfassen einer Nachricht verfügen. Die Login Funktion ist noch nicht implementiert und die Filterfunktion soll im nächsten Semester in das Suchfeld und die Profilanzeige eingebaut werden. Die restlichen Funktionen sind in der PWA grafisch umgesetzt worden. Es sollte zusätzlich ein Abhängigkeitsdiagramm erstellt werden. Allerdings war dies aufgrund fehlender komplexer Zusammenhänge noch nicht notwendig. Für die API-Schnittstelle wurde eine Dokumentation erstellt, um den Überblick zu bewahren. Im Bereich der Anwendungslogik wurde das Routing, die Darstellung der Nachrichten, die Speicherung der Nachrichten in der Datenbank und der LDAP-Login besonders behandelt. Das Routing sowie die Erstellung, Speicherung und Darstellung der Nachrichten funktioniert. Die Login Funktion wurde noch nicht näher betrachtet. Codetests wurden bisher noch keine durchgeführt. Das Rollenkonzept der Nutzer wurde bereits besprochen, allerdings noch nicht umgesetzt.\\
Darüber hinaus gibt noch einige nachrangige Punkte. Dazu gehört die Festlegung des Anwendungsnamens, die Erstellung eines Logos, das UX-Konzept, das Filtern der Datenbankabrufe, das Festlegen der Anzeigekriterien und die Funktion einem Kanal zu folgen. Wie oben bereits erwähnt wird das Filtern der Abrufe im nächsten Semester umgesetzt. Auch die Anzeigekriterien und die Möglichkeit einem Kanal zu folgen wird erst zu einem späteren Zeitpunkt möglich sein.
%
\section*{21. Fazit}
\phantomsection\addcontentsline{toc}{section}{21. Fazit}
Im ersten Projektabschnitt ist viel Zeit ist in der Konzeptionsphase vergangen, da es ein sehr iterativer Prozess ist und deshalb mehrere Durchläufe bis zum Endentwurf nötig waren. Verschiedene Meinungen mussten mit einbezogen werden. Doch ein gutes Konzept erspart am Ende viel Änderungsarbeit. \\
Für die weitere Erarbeitung der App haben sich die Projektmitglieder auf die Bereiche Front\-end, Datenbank, Sever und Service-Worker aufgeteilt. Diese Aufgaben\-verteilung hat gut funktioniert. Trotzdem ist unser Team sehr oft ins Stocken geraten, da sich die Aufgaben\-bereiche gerade am Anfang oft überschnitten haben. Hinzukommt, dass wir insgesamt \mbox{wenig} Erfahrung in diesen Bereichen hatten. Wir haben uns deshalb zu Beginn intensiv in die Themen eingearbeitet, was länger dauerte als erwartet. Zusätzlich mussten wir auf \mbox{fachliche} Unterstützung zurückgreifen.\\\\
Alles in einem kann man trotzdem von einem erfolgreichen Projektabschnitt sprechen. Ein erster funktionierender Prototyp ist fertig und kann im nächsten Zug erweitert werden. Parallele individuelle Arbeiten sind einfacher möglich, da das Grundgerüst steht. Das Team hat sich in die Materie eingearbeitet und kann im zweiten Projektabschnitt schneller durchstarten. Die Idee und das Konzept für die App sind soweit ausgereift, sodass diese nur noch umgesetzt werden müssen. Abläufe innerhalb der Gruppe sind klarer geworden, allerdings gibt es noch einiges strukturelle Abläufe, die im zweiten Projektabschnitt weiter optimiert werden können.
%
% Literaturverzeichnis
\newpage
\section*{Quellen- und Literaturverzeichnis}
\phantomsection\addcontentsline{toc}{section}{Quellen- und Literaturverzeichnis}
\medskip
%\nocite{*} % Alle Inhalte zur Ausgabe markieren, nicht nur jene die zitiert wurden.
{\setstretch{1.0}%
\printbibliography[heading=none]}
%
\end{document}
@@ -0,0 +1,171 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%
%% defaultPreamble.sty %%
%% %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%
%% Setup a preamble for document class: article %%
%% 1.Pagesettings: %%
%% - DIN A4 Paper %%
%% - Margin: Top = 20mm %%
%% Right = 20mm %%
%% Bottom = 30mm %%
%% Left = 20mm %%
%% - Header Separation = 10mm %%
%% - Footer Separation = 10mm %%
%% - Paragraph Spacing = 1.5 * line height %%
%% %%
%% 2.Redefine Pagelayout %%
%% - Remove printed page numbers on page 1. %%
%% - Page 1+2 counts for the table of content. %%
%% - Print the page numbering like "Seite" a "von" lastpage %%
%% to the right bottom corner. %%
%% %%
%% 3.Contain Packages %%
%% - geometry = Set page settings. %%
%% - setspace = Set linespacing, prevent math formula %%
%% streching. %%
%% - pageslts = Get last page number. %%
%% - fancyhr = Set header, footer and page layout. %%
%% - babel = Set hyphenation rules to [ngerman]. %%
%% - inputenc = Set input encoding to UTF-8 (Umlaute etc.). %%
%% - ClearSans = Provide fontfamily ClearSans. %%
%% - helvet = Set fontfamily to Helvetica (sans serif). %%
%% - fontenc = Set font encoding to 8bit (support <,=,>,"). %%
%% - xcolor = Provides support of font colors. %%
%% - upquote = Provides support quotes in verbatim and listings. %%
%% - listings = Provides support of indention and code colering. %%
%% - float = Provides support of fiqures and environment float.%%
%% - hyperref = Provides support of links (pdf anchor), URL. %%
%% - graphicx = Provides support to add images. %%
%% - tabularx = Provides advanced tabular support. %%
%% - pdfpages = Provides support to include pdf pages. %%
%% - amsmath = Provides advanced math typesetting support. %%
%% - amsfonts = Provides advanced font typesetting support. %%
%% - amssymb = Provides advanced symbol typesetting support. %%
%% - blindtext = Provides support to use Lorem Ipsum text. %%
%% - enumitem = Provides support change enum type in options. %%
%% - biblatex = Set biblatex for citation and bibliography %%
%% with biber as backend and the %%
%% bibliography style 'ieee'. %%
%% %%
%% 4.Defines New Commands %%
%% - \includePdf{filename} = Include a PDF file. %%
%% - \bibfilename{file.bib} = Set bib file. %%
%% - \printbib = Print bibliography. %%
%% - \b{text} = Set text to bold format. %%
%% - \mono{text} = Set text to monospace. %%
%% - \minsep = Set item spacing to minimum. %%
%% - \clear = Set fontfamily to ClearSans. %%
%% Default fontfamily is Helvetica. %%
%% %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
\NeedsTeXFormat{LaTeX2e}%
\ProvidesPackage{defaultPreamble}[2019/03/04 v1.4 DefaultPreamble Package from Erik Roemmelt]%
\ProcessOptions\relax%
%%% Packages %%%
% ___Document___
\RequirePackage[a4paper, left=20mm, right=20mm, top=20mm, bottom=30mm, headsep=10mm, footskip=10mm]{geometry} % Set page settings
\RequirePackage[nodisplayskipstretch]{setspace} % Set linespacing and prevent math formula streching
\setstretch{1.5} % Change typeset line spacing by multiplicator 1.0
\RequirePackage[pagecontinue=false,alphMult=0,AlphMulti=0,fnsymbolmult=false,romanMult=false,RomanMulti=false]{pageslts} % Get last page number with \lastpageref{LastPage}
\RequirePackage{fancyhdr} % Set header, footer and page layout.
\pagestyle{fancy} % Set the pagestyle to fancyhdr settings
%%% Fancyhdr Settings %%%
\fancyhf{} % Clear all header and footer fields
\fancyfoot[R]{\ifnum\value{page}<1\relax\else{Seite \thepage \hspace{1pt} von \lastpageref{VeryLastPage}}\fi} % Set page numbering "n / last" in the right field of footer
\renewcommand{\headrulewidth}{0pt} % Set header ruler hidden
\renewcommand{\footrulewidth}{0pt} % Set footer ruler hidden
\fancypagestyle{plain}{% Redefine plain style
\fancyhf{} % Clear all header and footer fields
\fancyfoot[R]{\ifnum\value{page}<2\relax\else{Seite \thepage \hspace{1pt} von \lastpageref{VeryLastPage}}\fi} % Set page numbering "n / last" in the right field of footer, skip page numbering on page 1+2
\renewcommand{\headrulewidth}{0pt} % Set header ruler hidden
\renewcommand{\footrulewidth}{0pt} % Set footer ruler hidden
}%
% ___Font___
\RequirePackage[ngerman]{babel} % Changes hyphenation rules
\RequirePackage[utf8]{inputenc} % Input encoding: Unicode support (Umlaute etc.)
\RequirePackage{ClearSans} % Provide fontfamily Clear Sans (sans serif) for title page
\RequirePackage[scaled]{helvet} % Scales fontfamily Helvetica to .95 (sans serif)
\renewcommand{\familydefault}{\sfdefault} % Changes default font to sans serif Helvetica
%\RequirePackage[default]{sourcesanspro}
%\RequirePackage[sfdefault]{FiraSans} % option 'sfdefault' activates Fira Sans as the default text font
%\renewcommand*\oldstylenums[1]{{\firaoldstyle #1}}
%\RequirePackage[sfdefault,light]{roboto} % Option 'sfdefault' only if the base font of the document is to be sans serif
\RequirePackage[T1]{fontenc} % Font encoding: (8bit) Accents support (<,=,>,",...)
\RequirePackage[dvipsnames]{xcolor} % Provides font coloring
\definecolor{editorGray}{rgb}{0.95, 0.95, 0.95} % Defines a new Color: gray
\definecolor{editorOcher}{rgb}{0.8, 0.4, 0} % #CC6600 -> rgb(204, 102, 0) % New Color Ocher
\definecolor{editorGreen}{rgb}{0, 0.5, 0} % #007C00 -> rgb(0, 124, 0) % New Color Green
\RequirePackage{upquote} % Allow quotes in verbatim and lstlisting
\RequirePackage{listings} % Allow indention and code colering
\lstdefinelanguage{JavaScript}{% Defines Javascript for lstlisting
morekeywords={typeof,new,true,false,catch,function,return,null,catch,switch,var,if,in,while,do,else,case,break},morecomment=[s]{/*}{*/},morecomment=[l]//,morestring=[b]",morestring=[b]'}%
\lstdefinelanguage{HTML5}{% Redefines HTML for lstlisting
language=html,sensitive=true,alsoletter={<>=-},otherkeywords={% HTML tags
<html>,<head>,<title>,</title>,<meta,/>,</head>,<body>,<canvas,\/canvas>,<script>,</script>,</body>,</html>,<!,html>,<style>,</style>,><},%
ndkeywords={%
% General
=,%
% HTML attributes
charset=,id=,width=,height=,%
% CSS properties
border:,transform:,-moz-transform:,transition-duration:,transition-property:,transition-timing-function:},%
morecomment=[s]{<!--}{-->},tag=[s]}%
\newcommand{\lstsetdefault}{%
\lstset{%
% Basic design
backgroundcolor=\color{editorGray},basicstyle={\small\ttfamily},frame=single,columns=fullflexible,%
% Line numbers
xleftmargin={0.75cm},numbers=left,stepnumber=1,firstnumber=1,numberfirstline=true,%
% Code design
keywordstyle=\color{blue}\bfseries,commentstyle=\color{darkgray}\ttfamily,ndkeywordstyle=\color{editorGreen}\bfseries,stringstyle=\color{editorOcher},%
% Code
language=HTML5,alsolanguage=JavaScript,alsodigit={.:;},tabsize=4,showtabs=false,showspaces=false,showstringspaces=false,extendedchars=true,breaklines=true,%
% Support for German umlauts
literate=%
{Ö}{{\"O}}1%
{Ä}{{\"A}}1%
{Ü}{{\"U}}1%
{ß}{{\ss}}1%
{ü}{{\"u}}1%
{ä}{{\"a}}1%
{ö}{{\"o}}1%
}}%
\lstsetdefault%
% ___Other___
\RequirePackage{float} % Allow fiqures and enviroments to float
\RequirePackage[hidelinks,pdfpagelabels=true]{hyperref} % Add a link to your document
\RequirePackage{graphicx} % Add pictures to your document
\RequirePackage{tabularx} % Advanced tabular support
\RequirePackage{pdfpages} % Allows to include pdf pages
\RequirePackage{amsmath} % Advanced math typesetting
\RequirePackage{amsfonts} % Advanced font typesetting
\RequirePackage{amssymb} % Advanced symoblic typesetting
\RequirePackage{blindtext} % Blindtext, Lorem Ipsum text
\RequirePackage[shortlabels]{enumitem} % Allows to change enumerate type easily
% __Bibliography___
\RequirePackage[backend=biber,style=ieee,sorting=nyt]{biblatex} % Use biblatex package
%%% New Commands %%%
% New command for closer spacing in list
\newcommand{\minsep}{\topsep=0pt\itemsep=0pt\parsep=0pt\partopsep=0pt}%
% Add new command to include a pdf file
\newcommand*{\@includePdf}{%
\PackageWarning{defaultPreamble}{PDF-File does not exist}%
NoN%
}%
\newcommand{\includePdf}[1]{\includepdf[pages=-,fitpaper=true,frame=false,pagecommand=\thispagestyle{plain}]{#1}}% Insert the name of the pdf file
% Add new command to set file name for *.bib file
\newcommand*{\@bibfilename}{%
\PackageWarning{defaultPreamble}{Bib-File-Name undefined}%
NoN%
}%
\newcommand{\bibfilename}[1]{\addbibresource{#1}}% Insert the name of the BibLatex file
% Add new command to print the bibliography
\newcommand{\printbib}{\printbibliography[heading=bibintoc]}% Insert the name of the BibLatex file
% Add new commant to shorten text format command: \textbf
\renewcommand{\b}{\textbf}%
% Add new commant to shorten text format command: \texttt
\newcommand{\mono}{\texttt}%
\endinput % Das ist das Ende von dieser .sty Datei
+21
View File
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDgjCCAmoCCQCFn/Kp0FpNazANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMC
REUxDzANBgNVBAgMBkJheWVybjESMBAGA1UEBwwJTnVlcm5iZXJnMQwwCgYDVQQK
DANvaG0xETAPBgNVBAsMCG1lZGlhZW5nMQ4wDAYDVQQDDAVWZWVwczEdMBsGCSqG
SIb3DQEJARYOZmFrZWFjY0BnbXguZGUwHhcNMTgxMTI3MjE1NDIyWhcNMTgxMjI3
MjE1NDIyWjCBgjELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJheWVybjESMBAGA1UE
BwwJTnVlcm5iZXJnMQwwCgYDVQQKDANvaG0xETAPBgNVBAsMCG1lZGlhZW5nMQ4w
DAYDVQQDDAVWZWVwczEdMBsGCSqGSIb3DQEJARYOZmFrZWFjY0BnbXguZGUwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxsNcIe742o+8vTF/zj+qoXhnU
gDYbaNyc/NP94SzxOArYIwetvsWz6SeIAk5iT/kf9v9CKNJquG2GcKYGnTolIzZB
h5r+1LA1gCe2q6Gj9RtDHL+s4w9QEEtXovigcy15KP21GdeL/HDEWVDZuFC6Mcn8
Zedka3tmQ2VWVhrapCCCHGbCdzSeWQYKIL0EWJOzOCjvlknq2LnzFoTCKIC20qv5
MTRaTmzPf2HGFtbpHJm3PdOhd6LtDKGjVCBIwQ+Rks2zUNoUdw83Bk5+XS11bKw6
A7wqlwH1molhMwnA8LYat3geSc3ZH964xOjrXFoBSz3dDCoCP3n8P1lh7eHTAgMB
AAEwDQYJKoZIhvcNAQELBQADggEBAFj6WU9mqSfkvUuALjeVjaCEj+uBCCyB/my8
Pahs8m/sjl7h6XbUfPKvqFOb5X93vC6h/dy5cVrkSBDOKOapO6b+s4sGPXvNW79+
LIus21ekrBKK3NB8XRFbJrGJnIbvGVHrvDD1HiXIo19PfUTDuFCIt1Uf9FtWP8E7
/xusEAQyX8hcGtRiYC3kxj+FtSAq1sIEFtpprD1PHOBCumbMmqj9BaU9i2IXQL6v
RDxU65XU1dlk3/Y+8BYdtSZ4SiHAjApmp45mNs7myE+mau/OTYTz9NlmdfLkFobd
lh2hQvSKasHXr4NfpzMoGP4WLO5qzergwzPntcII8CT6nBMKCik=
-----END CERTIFICATE-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA8bDXCHu+NqPvL0xf84/qqF4Z1IA2G2jcnPzT/eEs8TgK2CMH
rb7Fs+kniAJOYk/5H/b/QijSarhthnCmBp06JSM2QYea/tSwNYAntquho/UbQxy/
rOMPUBBLV6L4oHMteSj9tRnXi/xwxFlQ2bhQujHJ/GXnZGt7ZkNlVlYa2qQgghxm
wnc0nlkGCiC9BFiTszgo75ZJ6ti58xaEwiiAttKr+TE0Wk5sz39hxhbW6RyZtz3T
oXei7Qyho1QgSMEPkZLNs1DaFHcPNwZOfl0tdWysOgO8KpcB9ZqJYTMJwPC2Grd4
HknN2R/euMTo61xaAUs93QwqAj95/D9ZYe3h0wIDAQABAoIBAQCeswiviFlhFYnj
29mRW6mwvoFleH6Oxu89ogItTWMuTSdJvs629nJo7SrI8wK3KV4i8BOyRI3OB6Xb
RjKuWyUn55QjuUA8HjpKFwl8t/weZb5t9f2iL0IJx3lBZUCuUpLEYPceqIZRZARD
2YXuQEm5ySGhOuSSmsTfZFexNj7R2vbJBB3ejbY7d5W5B4HxwK/3ZzvzrZk2k1Hs
//cEA2aTWhNIdY9lOEVRY0fB5HvsM3s3T3y8n9kx8rtcqZml1Edy3WI3jZJHby++
oRZkO0ssxeAZLZLQRCwRaa11it7Tpxvm7dU6rsqgc9mU/TFMY7i8uGY54oUBXXrR
T4LOVj+JAoGBAP1reTCh0T+wA8YN9LhKrMpUwjMDLVNFR0V0u6fo18ORQZcQACKJ
lsIQZ8PC3q/wjUaoAcd8fOyqmYiVhXRMRahjy2TV0PGuJ/FK4brBaiqsyv8P+ev1
tYy98FdPOi3XYhKTLTj/onb2OCOCMc51Hmjvvqm87n1g6ZniaXtyZAiPAoGBAPQm
y5+GzLonkZUxmpLKOqVM1TsDyTe6GEExsRqoiCAyofR66tx8CVkzXciu5FU/3gCe
3fuYCLLt6/pnGdKdssMWvWEXIlICxlyqtKp0gtdtdUM1JyK2j7Su2l7n6s4z8WG0
UzlaJ5ipgdT9ppWZ40X8nrkgRahfGOXZTK8f4Qx9AoGAP3pMQQJHh8xvJY0ZX5BU
99mDS4PgNpuD3sH+v3Bzm2knJM+9OOl9+d5iH4cQgMGgobIzv+RO5nlVPAE4DImO
aT+WEwv76o3VM3uuTzljfo0JzBoUYCaL81Ugfr45tIHe7wguq7hc2a+TIFHcZ/qu
Q1rKR3orwvwjh4hPI7zjkcECgYEA0FfOyRnp2Czt4aeLpRmnQ+m1x/9qFQDbs75o
yDvqtbcfVxYRFwdNnu3jaN49gLXYhyUKfqj1O2MmTO7wLTdfFXVxM8nfAqbViLwv
IYn9IUKJn0lbVRb+uIvBUt2LFF1k7SBJKJbTVUXIwe2V2vGP7OgutgFhD9Xe1D1D
X+nPTK0CgYEAzSEQsuhLRxvnTlW6GYuBStR/tinsfCMkh9Umz5POzyTRGvtHVR5x
PZrcaLQXGd4/TV66F/O9CCvu1X5fDhf2RGK9W0EIkkBZfHKw81b5aTyX00dozO8Z
4r78/uu4o/YTxptH4WxIKEiPm4ODOpNJUMLd6QAz8y3tNqh2fSa9kyA=
-----END RSA PRIVATE KEY-----
+37
View File
@@ -0,0 +1,37 @@
// Terminal call: node server/ldap_test.js - needs VPN or eduroam
const inquirer = require('inquirer'),
ldap = require('./server/ldap_ohm.js');
inquirer.prompt([
{
name: 'username',
type: 'input',
message: 'Enter your VirtuOhm username:',
validate: function( value ) {
if (value.length) {
return true;
} else {
return 'Please enter your username.';
}
}
},
{
name: 'password',
type: 'password',
message: 'Enter your password:',
validate: function(value) {
if (value.length) {
return true;
} else {
return 'Please enter your password.';
}
}
}])
.then(answers => {
ldap.init(null);
ldap.authorize(answers.username,answers.password,function(user) {
console.log(JSON.stringify(user));
process.exit();
});
});
+22
View File
@@ -0,0 +1,22 @@
var db = db.getSiblingDB('omdb');
// Delete all collections + all records
db.messages.deleteMany({});
db.tags.deleteMany({});
db.users.deleteMany({});
db.sessions.deleteMany({});
// Insert data in collections
load('mong_msg.js');
load('mong_tag.js');
load('mong_usr.js');
// Print all collections + records count
var collections = db.getCollectionNames();
print('Collections inside the db:');
for(var i = 0; i < collections.length; i++){
var name = collections[i];
if(name.substr(0, 6) != 'system')
print(name + ' - ' + db[name].count() + ' records');
}
+9
View File
@@ -0,0 +1,9 @@
db.messages.insert([
{"subject":"DOLORE LABORIS AUTE","message":"Ex sunt elit elit in ex ad labore minim. Elit amet irure sit aliquip excepteur sit qui et aute qui mollit cillum.","user":"Jaime Potter","tag":["sit","in","dolore","ullamco"]},
{"subject":"NON IPSUM MOLLIT","message":"Ut laborum proident magna magna qui non in voluptate reprehenderit labore. Qui consectetur dolor nisi culpa cillum quis.","user":"Goff Durham","tag":["aliqua","sunt","labore","minim"]},
{"subject": "ESSE IPSUM INCIDIDUNT","message":"Esse enim amet laboris proident do do ut cupidatat laborum ex. Sit esse id laborum aliqua id.","user":"Helena Mercado","tag":["fugiat","nisi","incididunt","ullamco"]},
{"subject":"PARIATUR DOLOR SUNT","message":"Exercitation officia ad consequat est voluptate est incididunt ut culpa consectetur. Consectetur irure veniam ipsum sint sit anim minim aute exercitation ullamco nostrud reprehenderit consequat.","user":"Christi Walker","tag":["consectetur","sint","qui","ipsum"]},
{"subject":"TEMPOR ID VOLUPTATE","message":"Anim ex commodo officia consequat in magna esse sunt mollit sunt commodo. Ex sit aute reprehenderit elit magna sunt irure pariatur cupidatat labore.","user":"Kent Schroeder","tag":["adipisicing","nulla","tempor","veniam"]},
{"subject":"LOREM EST AUTE","message":"Irure excepteur laborum quis tempor officia cillum officia est ea adipisicing duis enim aliqua. Est dolor laborum officia elit ullamco.","user":"Dominguez Stevens","tag":["mollit","amet","consectetur","dolore"]},
{"subject":"ESSE MOLLIT CILLUM","message":"Eu culpa laborum consequat sunt aliqua excepteur. Voluptate pariatur deserunt consequat id nostrud do magna occaecat sint nulla non nostrud.","user":"Terrell Padilla","tag":["Lorem","consequat","fugiat","aliqua"]}
])
+3
View File
@@ -0,0 +1,3 @@
db.tags.insert([
{"name":"efi"},{"name":"wichtig"},{"name":"Wintersemester19/20"},{"name":"Rückmeldung"},{"name":"bme6"},{"name":"Mkz"},{"name":"Freizeit"},{"name":"Prüfungsanmeldung"},{"name":"THNürnberg"},{"name":"Prüfung"},{"name":"Stundenplan"}
])
+11
View File
@@ -0,0 +1,11 @@
db.users.insert([
// User: author, Pwd: author
{"_id":"author","name":"Author","type":"MA@EFI;","roles":'{"user":true,"author":true}',"hash":"sha256","salt":"SIzKNsNKsCRVr8a9U90q6A==","pwd":"HZly68TSAKHioz6Kz0QCbXVTHpT6hMmabUbFYVlCMeE=","abos":["nulla","tempor","veniam","ipsum"],"bookmarks":[],},
// User: admin, Pwd: SwenMho
{"_id":"admin","name":"Test Admin","type":"MA@AMP;","roles":'{"user":true,"author":true,"admin":true}',"hash":"sha256","salt":"z3PNXGmQaWvaT7m2ZlT+0w==","pwd":"nfUfNv032J745xj3Hzya3Mkk43Dz/H0BmNTZhtx8UM0=","abos":["veniam","ipsum"],"bookmarks":[],},
])
//var c = require('./server/crypto.js'),
// e = {"_id":"writer","hash":"sha256"};
//c.fillLocalAuth(e,"pwd");
//console.info(JSON.stringify(e));
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../bunyan/bin/bunyan
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../ldapjs/bin/ldapjs-add
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../ldapjs/bin/ldapjs-compare
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../ldapjs/bin/ldapjs-delete
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../ldapjs/bin/ldapjs-modify
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../ldapjs/bin/ldapjs-search
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../mkdirp/bin/cmd.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../ncp/bin/ncp
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../semver/bin/semver
+12
View File
@@ -1,3 +1,15 @@
1.3.7 / 2019-04-29
==================
* deps: negotiator@0.6.2
- Fix sorting charset, encoding, and language with extra parameters
1.3.6 / 2019-04-28
==================
* deps: mime-types@~2.1.24
- deps: mime-db@~1.40.0
1.3.5 / 2018-02-28 1.3.5 / 2018-02-28
================== ==================
+9 -10
View File
@@ -1,7 +1,7 @@
# accepts # accepts
[![NPM Version][npm-image]][npm-url] [![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url] [![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-version-image]][node-version-url] [![Node.js Version][node-version-image]][node-version-url]
[![Build Status][travis-image]][travis-url] [![Build Status][travis-image]][travis-url]
[![Test Coverage][coveralls-image]][coveralls-url] [![Test Coverage][coveralls-image]][coveralls-url]
@@ -131,13 +131,12 @@ curl -I -H'Accept: text/html' http://localhost:3000/
[MIT](LICENSE) [MIT](LICENSE)
[npm-image]: https://img.shields.io/npm/v/accepts.svg [coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/accepts/master
[coveralls-url]: https://coveralls.io/r/jshttp/accepts?branch=master
[node-version-image]: https://badgen.net/npm/node/accepts
[node-version-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/accepts
[npm-url]: https://npmjs.org/package/accepts [npm-url]: https://npmjs.org/package/accepts
[node-version-image]: https://img.shields.io/node/v/accepts.svg [npm-version-image]: https://badgen.net/npm/v/accepts
[node-version-url]: https://nodejs.org/en/download/ [travis-image]: https://badgen.net/travis/jshttp/accepts/master
[travis-image]: https://img.shields.io/travis/jshttp/accepts/master.svg
[travis-url]: https://travis-ci.org/jshttp/accepts [travis-url]: https://travis-ci.org/jshttp/accepts
[coveralls-image]: https://img.shields.io/coveralls/jshttp/accepts/master.svg
[coveralls-url]: https://coveralls.io/r/jshttp/accepts
[downloads-image]: https://img.shields.io/npm/dm/accepts.svg
[downloads-url]: https://npmjs.org/package/accepts
+25 -24
View File
@@ -1,27 +1,27 @@
{ {
"_from": "accepts@~1.3.5", "_from": "accepts@~1.3.7",
"_id": "accepts@1.3.5", "_id": "accepts@1.3.7",
"_inBundle": false, "_inBundle": false,
"_integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "_integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"_location": "/accepts", "_location": "/accepts",
"_phantomChildren": {}, "_phantomChildren": {},
"_requested": { "_requested": {
"type": "range", "type": "range",
"registry": true, "registry": true,
"raw": "accepts@~1.3.5", "raw": "accepts@~1.3.7",
"name": "accepts", "name": "accepts",
"escapedName": "accepts", "escapedName": "accepts",
"rawSpec": "~1.3.5", "rawSpec": "~1.3.7",
"saveSpec": null, "saveSpec": null,
"fetchSpec": "~1.3.5" "fetchSpec": "~1.3.7"
}, },
"_requiredBy": [ "_requiredBy": [
"/express" "/express"
], ],
"_resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", "_resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"_shasum": "eb777df6011723a3b14e8a72c0805c8e86746bd2", "_shasum": "531bc726517a3b2b41f850021c6cc15eaab507cd",
"_spec": "accepts@~1.3.5", "_spec": "accepts@~1.3.7",
"_where": "/home/erik/Documents/workspace_brackets/BME_Project_Ohm/om/node_modules/express", "_where": "/home/erik/Documents/workspace_brackets/a1_BME_Project_Ohm/om/node_modules/express",
"bugs": { "bugs": {
"url": "https://github.com/jshttp/accepts/issues" "url": "https://github.com/jshttp/accepts/issues"
}, },
@@ -38,21 +38,22 @@
} }
], ],
"dependencies": { "dependencies": {
"mime-types": "~2.1.18", "mime-types": "~2.1.24",
"negotiator": "0.6.1" "negotiator": "0.6.2"
}, },
"deprecated": false, "deprecated": false,
"description": "Higher-level content negotiation", "description": "Higher-level content negotiation",
"devDependencies": { "devDependencies": {
"eslint": "4.18.1", "deep-equal": "1.0.1",
"eslint-config-standard": "11.0.0", "eslint": "5.16.0",
"eslint-plugin-import": "2.9.0", "eslint-config-standard": "12.0.0",
"eslint-plugin-markdown": "1.0.0-beta.6", "eslint-plugin-import": "2.17.2",
"eslint-plugin-node": "6.0.1", "eslint-plugin-markdown": "1.0.0",
"eslint-plugin-promise": "3.6.0", "eslint-plugin-node": "8.0.1",
"eslint-plugin-standard": "3.0.1", "eslint-plugin-promise": "4.1.1",
"istanbul": "0.4.5", "eslint-plugin-standard": "4.0.0",
"mocha": "~1.21.5" "mocha": "6.1.4",
"nyc": "14.0.0"
}, },
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -78,8 +79,8 @@
"scripts": { "scripts": {
"lint": "eslint --plugin markdown --ext js,md .", "lint": "eslint --plugin markdown --ext js,md .",
"test": "mocha --reporter spec --check-leaks --bail test/", "test": "mocha --reporter spec --check-leaks --bail test/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", "test-cov": "nyc --reporter=html --reporter=text npm test",
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" "test-travis": "nyc --reporter=text npm test"
}, },
"version": "1.3.5" "version": "1.3.7"
} }
+131
View File
@@ -0,0 +1,131 @@
'use strict';
const x = module.exports;
const ESC = '\u001B[';
const OSC = '\u001B]';
const BEL = '\u0007';
const SEP = ';';
const isTerminalApp = process.env.TERM_PROGRAM === 'Apple_Terminal';
x.cursorTo = (x, y) => {
if (typeof x !== 'number') {
throw new TypeError('The `x` argument is required');
}
if (typeof y !== 'number') {
return ESC + (x + 1) + 'G';
}
return ESC + (y + 1) + ';' + (x + 1) + 'H';
};
x.cursorMove = (x, y) => {
if (typeof x !== 'number') {
throw new TypeError('The `x` argument is required');
}
let ret = '';
if (x < 0) {
ret += ESC + (-x) + 'D';
} else if (x > 0) {
ret += ESC + x + 'C';
}
if (y < 0) {
ret += ESC + (-y) + 'A';
} else if (y > 0) {
ret += ESC + y + 'B';
}
return ret;
};
x.cursorUp = count => ESC + (typeof count === 'number' ? count : 1) + 'A';
x.cursorDown = count => ESC + (typeof count === 'number' ? count : 1) + 'B';
x.cursorForward = count => ESC + (typeof count === 'number' ? count : 1) + 'C';
x.cursorBackward = count => ESC + (typeof count === 'number' ? count : 1) + 'D';
x.cursorLeft = ESC + 'G';
x.cursorSavePosition = ESC + (isTerminalApp ? '7' : 's');
x.cursorRestorePosition = ESC + (isTerminalApp ? '8' : 'u');
x.cursorGetPosition = ESC + '6n';
x.cursorNextLine = ESC + 'E';
x.cursorPrevLine = ESC + 'F';
x.cursorHide = ESC + '?25l';
x.cursorShow = ESC + '?25h';
x.eraseLines = count => {
let clear = '';
for (let i = 0; i < count; i++) {
clear += x.eraseLine + (i < count - 1 ? x.cursorUp() : '');
}
if (count) {
clear += x.cursorLeft;
}
return clear;
};
x.eraseEndLine = ESC + 'K';
x.eraseStartLine = ESC + '1K';
x.eraseLine = ESC + '2K';
x.eraseDown = ESC + 'J';
x.eraseUp = ESC + '1J';
x.eraseScreen = ESC + '2J';
x.scrollUp = ESC + 'S';
x.scrollDown = ESC + 'T';
x.clearScreen = '\u001Bc';
x.clearTerminal = process.platform === 'win32' ?
`${x.eraseScreen}${ESC}0f` :
// 1. Erases the screen (Only done in case `2` is not supported)
// 2. Erases the whole screen including scrollback buffer
// 3. Moves cursor to the top-left position
// More info: https://www.real-world-systems.com/docs/ANSIcode.html
`${x.eraseScreen}${ESC}3J${ESC}H`;
x.beep = BEL;
x.link = (text, url) => {
return [
OSC,
'8',
SEP,
SEP,
url,
BEL,
text,
OSC,
'8',
SEP,
SEP,
BEL
].join('');
};
x.image = (buf, opts) => {
opts = opts || {};
let ret = OSC + '1337;File=inline=1';
if (opts.width) {
ret += `;width=${opts.width}`;
}
if (opts.height) {
ret += `;height=${opts.height}`;
}
if (opts.preserveAspectRatio === false) {
ret += ';preserveAspectRatio=0';
}
return ret + ':' + buf.toString('base64') + BEL;
};
x.iTerm = {};
x.iTerm.setCwd = cwd => OSC + '50;CurrentDir=' + (cwd || process.cwd()) + BEL;
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+82
View File
@@ -0,0 +1,82 @@
{
"_from": "ansi-escapes@^3.2.0",
"_id": "ansi-escapes@3.2.0",
"_inBundle": false,
"_integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
"_location": "/ansi-escapes",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "ansi-escapes@^3.2.0",
"name": "ansi-escapes",
"escapedName": "ansi-escapes",
"rawSpec": "^3.2.0",
"saveSpec": null,
"fetchSpec": "^3.2.0"
},
"_requiredBy": [
"/inquirer"
],
"_resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
"_shasum": "8780b98ff9dbf5638152d1f1fe5c1d7b4442976b",
"_spec": "ansi-escapes@^3.2.0",
"_where": "/home/erik/Documents/workspace_brackets/a1_BME_Project_Ohm/om/node_modules/inquirer",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/ansi-escapes/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "ANSI escape codes for manipulating the terminal",
"devDependencies": {
"ava": "*",
"xo": "*"
},
"engines": {
"node": ">=4"
},
"files": [
"index.js"
],
"homepage": "https://github.com/sindresorhus/ansi-escapes#readme",
"keywords": [
"ansi",
"terminal",
"console",
"cli",
"string",
"tty",
"escape",
"escapes",
"formatting",
"shell",
"xterm",
"log",
"logging",
"command-line",
"text",
"vt100",
"sequence",
"control",
"code",
"codes",
"cursor",
"iterm",
"iterm2"
],
"license": "MIT",
"name": "ansi-escapes",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/ansi-escapes.git"
},
"scripts": {
"test": "xo && ava"
},
"version": "3.2.0"
}
+184
View File
@@ -0,0 +1,184 @@
# ansi-escapes [![Build Status](https://travis-ci.org/sindresorhus/ansi-escapes.svg?branch=master)](https://travis-ci.org/sindresorhus/ansi-escapes)
> [ANSI escape codes](http://www.termsys.demon.co.uk/vtansi.htm) for manipulating the terminal
## Install
```
$ npm install ansi-escapes
```
## Usage
```js
const ansiEscapes = require('ansi-escapes');
// Moves the cursor two rows up and to the left
process.stdout.write(ansiEscapes.cursorUp(2) + ansiEscapes.cursorLeft);
//=> '\u001B[2A\u001B[1000D'
```
## API
### cursorTo(x, [y])
Set the absolute position of the cursor. `x0` `y0` is the top left of the screen.
### cursorMove(x, [y])
Set the position of the cursor relative to its current position.
### cursorUp(count)
Move cursor up a specific amount of rows. Default is `1`.
### cursorDown(count)
Move cursor down a specific amount of rows. Default is `1`.
### cursorForward(count)
Move cursor forward a specific amount of rows. Default is `1`.
### cursorBackward(count)
Move cursor backward a specific amount of rows. Default is `1`.
### cursorLeft
Move cursor to the left side.
### cursorSavePosition
Save cursor position.
### cursorRestorePosition
Restore saved cursor position.
### cursorGetPosition
Get cursor position.
### cursorNextLine
Move cursor to the next line.
### cursorPrevLine
Move cursor to the previous line.
### cursorHide
Hide cursor.
### cursorShow
Show cursor.
### eraseLines(count)
Erase from the current cursor position up the specified amount of rows.
### eraseEndLine
Erase from the current cursor position to the end of the current line.
### eraseStartLine
Erase from the current cursor position to the start of the current line.
### eraseLine
Erase the entire current line.
### eraseDown
Erase the screen from the current line down to the bottom of the screen.
### eraseUp
Erase the screen from the current line up to the top of the screen.
### eraseScreen
Erase the screen and move the cursor the top left position.
### scrollUp
Scroll display up one line.
### scrollDown
Scroll display down one line.
### clearScreen
Clear the terminal screen. (Viewport)
### clearTerminal
Clear the whole terminal, including scrollback buffer. (Not just the visible part of it)
### beep
Output a beeping sound.
### link(text, url)
Create a clickable link.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) Use [`supports-hyperlinks`](https://github.com/jamestalmage/supports-hyperlinks) to detect link support.
### image(input, [options])
Display an image.
*Currently only supported on iTerm2 >=3*
See [term-img](https://github.com/sindresorhus/term-img) for a higher-level module.
#### input
Type: `Buffer`
Buffer of an image. Usually read in with `fs.readFile()`.
#### options
##### width
##### height
Type: `string` `number`
The width and height are given as a number followed by a unit, or the word "auto".
- `N`: N character cells.
- `Npx`: N pixels.
- `N%`: N percent of the session's width or height.
- `auto`: The image's inherent size will be used to determine an appropriate dimension.
##### preserveAspectRatio
Type: `boolean`<br>
Default: `true`
### iTerm.setCwd([path])
Type: `string`<br>
Default: `process.cwd()`
[Inform iTerm2](https://www.iterm2.com/documentation-escape-codes.html) of the current directory to help semantic history and enable [Cmd-clicking relative paths](https://coderwall.com/p/b7e82q/quickly-open-files-in-iterm-with-cmd-click).
## Related
- [ansi-styles](https://github.com/chalk/ansi-styles) - ANSI escape codes for styling strings in the terminal
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)
+10
View File
@@ -0,0 +1,10 @@
'use strict';
module.exports = () => {
const pattern = [
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))'
].join('|');
return new RegExp(pattern, 'g');
};
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+85
View File
@@ -0,0 +1,85 @@
{
"_from": "ansi-regex@^3.0.0",
"_id": "ansi-regex@3.0.0",
"_inBundle": false,
"_integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"_location": "/ansi-regex",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "ansi-regex@^3.0.0",
"name": "ansi-regex",
"escapedName": "ansi-regex",
"rawSpec": "^3.0.0",
"saveSpec": null,
"fetchSpec": "^3.0.0"
},
"_requiredBy": [
"/strip-ansi"
],
"_resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"_shasum": "ed0317c322064f79466c02966bddb605ab37d998",
"_spec": "ansi-regex@^3.0.0",
"_where": "/home/erik/Documents/workspace_brackets/BME_Project_Ohm/om/node_modules/strip-ansi",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/chalk/ansi-regex/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "Regular expression for matching ANSI escape codes",
"devDependencies": {
"ava": "*",
"xo": "*"
},
"engines": {
"node": ">=4"
},
"files": [
"index.js"
],
"homepage": "https://github.com/chalk/ansi-regex#readme",
"keywords": [
"ansi",
"styles",
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"string",
"tty",
"escape",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"command-line",
"text",
"regex",
"regexp",
"re",
"match",
"test",
"find",
"pattern"
],
"license": "MIT",
"name": "ansi-regex",
"repository": {
"type": "git",
"url": "git+https://github.com/chalk/ansi-regex.git"
},
"scripts": {
"test": "xo && ava",
"view-supported": "node fixtures/view-codes.js"
},
"version": "3.0.0"
}
+46
View File
@@ -0,0 +1,46 @@
# ansi-regex [![Build Status](https://travis-ci.org/chalk/ansi-regex.svg?branch=master)](https://travis-ci.org/chalk/ansi-regex)
> Regular expression for matching [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code)
## Install
```
$ npm install ansi-regex
```
## Usage
```js
const ansiRegex = require('ansi-regex');
ansiRegex().test('\u001B[4mcake\u001B[0m');
//=> true
ansiRegex().test('cake');
//=> false
'\u001B[4mcake\u001B[0m'.match(ansiRegex());
//=> ['\u001B[4m', '\u001B[0m']
```
## FAQ
### Why do you test for codes not in the ECMA 48 standard?
Some of the codes we run as a test are codes that we acquired finding various lists of non-standard or manufacturer specific codes. We test for both standard and non-standard codes, as most of them follow the same or similar format and can be safely matched in strings without the risk of removing actual string content. There are a few non-standard control codes that do not follow the traditional format (i.e. they end in numbers) thus forcing us to exclude them from the test because we cannot reliably match them.
On the historical side, those ECMA standards were established in the early 90's whereas the VT100, for example, was designed in the mid/late 70's. At that point in time, control codes were still pretty ungoverned and engineers used them for a multitude of things, namely to activate hardware ports that may have been proprietary. Somewhere else you see a similar 'anarchy' of codes is in the x86 architecture for processors; there are a ton of "interrupts" that can mean different things on certain brands of processors, most of which have been phased out.
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)
## License
MIT
+165
View File
@@ -0,0 +1,165 @@
'use strict';
const colorConvert = require('color-convert');
const wrapAnsi16 = (fn, offset) => function () {
const code = fn.apply(colorConvert, arguments);
return `\u001B[${code + offset}m`;
};
const wrapAnsi256 = (fn, offset) => function () {
const code = fn.apply(colorConvert, arguments);
return `\u001B[${38 + offset};5;${code}m`;
};
const wrapAnsi16m = (fn, offset) => function () {
const rgb = fn.apply(colorConvert, arguments);
return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
};
function assembleStyles() {
const codes = new Map();
const styles = {
modifier: {
reset: [0, 0],
// 21 isn't widely supported and 22 does the same thing
bold: [1, 22],
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29]
},
color: {
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
gray: [90, 39],
// Bright color
redBright: [91, 39],
greenBright: [92, 39],
yellowBright: [93, 39],
blueBright: [94, 39],
magentaBright: [95, 39],
cyanBright: [96, 39],
whiteBright: [97, 39]
},
bgColor: {
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
// Bright color
bgBlackBright: [100, 49],
bgRedBright: [101, 49],
bgGreenBright: [102, 49],
bgYellowBright: [103, 49],
bgBlueBright: [104, 49],
bgMagentaBright: [105, 49],
bgCyanBright: [106, 49],
bgWhiteBright: [107, 49]
}
};
// Fix humans
styles.color.grey = styles.color.gray;
for (const groupName of Object.keys(styles)) {
const group = styles[groupName];
for (const styleName of Object.keys(group)) {
const style = group[styleName];
styles[styleName] = {
open: `\u001B[${style[0]}m`,
close: `\u001B[${style[1]}m`
};
group[styleName] = styles[styleName];
codes.set(style[0], style[1]);
}
Object.defineProperty(styles, groupName, {
value: group,
enumerable: false
});
Object.defineProperty(styles, 'codes', {
value: codes,
enumerable: false
});
}
const ansi2ansi = n => n;
const rgb2rgb = (r, g, b) => [r, g, b];
styles.color.close = '\u001B[39m';
styles.bgColor.close = '\u001B[49m';
styles.color.ansi = {
ansi: wrapAnsi16(ansi2ansi, 0)
};
styles.color.ansi256 = {
ansi256: wrapAnsi256(ansi2ansi, 0)
};
styles.color.ansi16m = {
rgb: wrapAnsi16m(rgb2rgb, 0)
};
styles.bgColor.ansi = {
ansi: wrapAnsi16(ansi2ansi, 10)
};
styles.bgColor.ansi256 = {
ansi256: wrapAnsi256(ansi2ansi, 10)
};
styles.bgColor.ansi16m = {
rgb: wrapAnsi16m(rgb2rgb, 10)
};
for (let key of Object.keys(colorConvert)) {
if (typeof colorConvert[key] !== 'object') {
continue;
}
const suite = colorConvert[key];
if (key === 'ansi16') {
key = 'ansi';
}
if ('ansi16' in suite) {
styles.color.ansi[key] = wrapAnsi16(suite.ansi16, 0);
styles.bgColor.ansi[key] = wrapAnsi16(suite.ansi16, 10);
}
if ('ansi256' in suite) {
styles.color.ansi256[key] = wrapAnsi256(suite.ansi256, 0);
styles.bgColor.ansi256[key] = wrapAnsi256(suite.ansi256, 10);
}
if ('rgb' in suite) {
styles.color.ansi16m[key] = wrapAnsi16m(suite.rgb, 0);
styles.bgColor.ansi16m[key] = wrapAnsi16m(suite.rgb, 10);
}
}
return styles;
}
// Make the export immutable
Object.defineProperty(module, 'exports', {
enumerable: true,
get: assembleStyles
});
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+88
View File
@@ -0,0 +1,88 @@
{
"_from": "ansi-styles@^3.2.1",
"_id": "ansi-styles@3.2.1",
"_inBundle": false,
"_integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"_location": "/ansi-styles",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "ansi-styles@^3.2.1",
"name": "ansi-styles",
"escapedName": "ansi-styles",
"rawSpec": "^3.2.1",
"saveSpec": null,
"fetchSpec": "^3.2.1"
},
"_requiredBy": [
"/chalk"
],
"_resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"_shasum": "41fbb20243e50b12be0f04b8dedbf07520ce841d",
"_spec": "ansi-styles@^3.2.1",
"_where": "/home/erik/Documents/workspace_brackets/BME_Project_Ohm/om/node_modules/chalk",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"ava": {
"require": "babel-polyfill"
},
"bugs": {
"url": "https://github.com/chalk/ansi-styles/issues"
},
"bundleDependencies": false,
"dependencies": {
"color-convert": "^1.9.0"
},
"deprecated": false,
"description": "ANSI escape codes for styling strings in the terminal",
"devDependencies": {
"ava": "*",
"babel-polyfill": "^6.23.0",
"svg-term-cli": "^2.1.1",
"xo": "*"
},
"engines": {
"node": ">=4"
},
"files": [
"index.js"
],
"homepage": "https://github.com/chalk/ansi-styles#readme",
"keywords": [
"ansi",
"styles",
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"string",
"tty",
"escape",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"log",
"logging",
"command-line",
"text"
],
"license": "MIT",
"name": "ansi-styles",
"repository": {
"type": "git",
"url": "git+https://github.com/chalk/ansi-styles.git"
},
"scripts": {
"screenshot": "svg-term --command='node screenshot' --out=screenshot.svg --padding=3 --width=55 --height=3 --at=1000 --no-cursor",
"test": "xo && ava"
},
"version": "3.2.1"
}
+147
View File
@@ -0,0 +1,147 @@
# ansi-styles [![Build Status](https://travis-ci.org/chalk/ansi-styles.svg?branch=master)](https://travis-ci.org/chalk/ansi-styles)
> [ANSI escape codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles) for styling strings in the terminal
You probably want the higher-level [chalk](https://github.com/chalk/chalk) module for styling your strings.
<img src="https://cdn.rawgit.com/chalk/ansi-styles/8261697c95bf34b6c7767e2cbe9941a851d59385/screenshot.svg" width="900">
## Install
```
$ npm install ansi-styles
```
## Usage
```js
const style = require('ansi-styles');
console.log(`${style.green.open}Hello world!${style.green.close}`);
// Color conversion between 16/256/truecolor
// NOTE: If conversion goes to 16 colors or 256 colors, the original color
// may be degraded to fit that color palette. This means terminals
// that do not support 16 million colors will best-match the
// original color.
console.log(style.bgColor.ansi.hsl(120, 80, 72) + 'Hello world!' + style.bgColor.close);
console.log(style.color.ansi256.rgb(199, 20, 250) + 'Hello world!' + style.color.close);
console.log(style.color.ansi16m.hex('#ABCDEF') + 'Hello world!' + style.color.close);
```
## API
Each style has an `open` and `close` property.
## Styles
### Modifiers
- `reset`
- `bold`
- `dim`
- `italic` *(Not widely supported)*
- `underline`
- `inverse`
- `hidden`
- `strikethrough` *(Not widely supported)*
### Colors
- `black`
- `red`
- `green`
- `yellow`
- `blue`
- `magenta`
- `cyan`
- `white`
- `gray` ("bright black")
- `redBright`
- `greenBright`
- `yellowBright`
- `blueBright`
- `magentaBright`
- `cyanBright`
- `whiteBright`
### Background colors
- `bgBlack`
- `bgRed`
- `bgGreen`
- `bgYellow`
- `bgBlue`
- `bgMagenta`
- `bgCyan`
- `bgWhite`
- `bgBlackBright`
- `bgRedBright`
- `bgGreenBright`
- `bgYellowBright`
- `bgBlueBright`
- `bgMagentaBright`
- `bgCyanBright`
- `bgWhiteBright`
## Advanced usage
By default, you get a map of styles, but the styles are also available as groups. They are non-enumerable so they don't show up unless you access them explicitly. This makes it easier to expose only a subset in a higher-level module.
- `style.modifier`
- `style.color`
- `style.bgColor`
###### Example
```js
console.log(style.color.green.open);
```
Raw escape codes (i.e. without the CSI escape prefix `\u001B[` and render mode postfix `m`) are available under `style.codes`, which returns a `Map` with the open codes as keys and close codes as values.
###### Example
```js
console.log(style.codes.get(36));
//=> 39
```
## [256 / 16 million (TrueColor) support](https://gist.github.com/XVilka/8346728)
`ansi-styles` uses the [`color-convert`](https://github.com/Qix-/color-convert) package to allow for converting between various colors and ANSI escapes, with support for 256 and 16 million colors.
To use these, call the associated conversion function with the intended output, for example:
```js
style.color.ansi.rgb(100, 200, 15); // RGB to 16 color ansi foreground code
style.bgColor.ansi.rgb(100, 200, 15); // RGB to 16 color ansi background code
style.color.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code
style.bgColor.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code
style.color.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color foreground code
style.bgColor.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color background code
```
## Related
- [ansi-escapes](https://github.com/sindresorhus/ansi-escapes) - ANSI escape codes for manipulating the terminal
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)
## License
MIT
+2
View File
@@ -0,0 +1,2 @@
node_modules
*.log
+4
View File
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.8
- 0.10
Generated Vendored
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2011 Mark Cavage, All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE
+50
View File
@@ -0,0 +1,50 @@
node-asn1 is a library for encoding and decoding ASN.1 datatypes in pure JS.
Currently BER encoding is supported; at some point I'll likely have to do DER.
## Usage
Mostly, if you're *actually* needing to read and write ASN.1, you probably don't
need this readme to explain what and why. If you have no idea what ASN.1 is,
see this: ftp://ftp.rsa.com/pub/pkcs/ascii/layman.asc
The source is pretty much self-explanatory, and has read/write methods for the
common types out there.
### Decoding
The following reads an ASN.1 sequence with a boolean.
var Ber = require('asn1').Ber;
var reader = new Ber.Reader(new Buffer([0x30, 0x03, 0x01, 0x01, 0xff]));
reader.readSequence();
console.log('Sequence len: ' + reader.length);
if (reader.peek() === Ber.Boolean)
console.log(reader.readBoolean());
### Encoding
The following generates the same payload as above.
var Ber = require('asn1').Ber;
var writer = new Ber.Writer();
writer.startSequence();
writer.writeBoolean(true);
writer.endSequence();
console.log(writer.buffer);
## Installation
npm install asn1
## License
MIT.
## Bugs
See <https://github.com/mcavage/node-asn1/issues>.
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
module.exports = {
newInvalidAsn1Error: function(msg) {
var e = new Error();
e.name = 'InvalidAsn1Error';
e.message = msg || '';
return e;
}
};
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
var errors = require('./errors');
var types = require('./types');
var Reader = require('./reader');
var Writer = require('./writer');
///--- Exports
module.exports = {
Reader: Reader,
Writer: Writer
};
for (var t in types) {
if (types.hasOwnProperty(t))
module.exports[t] = types[t];
}
for (var e in errors) {
if (errors.hasOwnProperty(e))
module.exports[e] = errors[e];
}
+261
View File
@@ -0,0 +1,261 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
var assert = require('assert');
var ASN1 = require('./types');
var errors = require('./errors');
///--- Globals
var newInvalidAsn1Error = errors.newInvalidAsn1Error;
///--- API
function Reader(data) {
if (!data || !Buffer.isBuffer(data))
throw new TypeError('data must be a node Buffer');
this._buf = data;
this._size = data.length;
// These hold the "current" state
this._len = 0;
this._offset = 0;
}
Object.defineProperty(Reader.prototype, 'length', {
enumerable: true,
get: function () { return (this._len); }
});
Object.defineProperty(Reader.prototype, 'offset', {
enumerable: true,
get: function () { return (this._offset); }
});
Object.defineProperty(Reader.prototype, 'remain', {
get: function () { return (this._size - this._offset); }
});
Object.defineProperty(Reader.prototype, 'buffer', {
get: function () { return (this._buf.slice(this._offset)); }
});
/**
* Reads a single byte and advances offset; you can pass in `true` to make this
* a "peek" operation (i.e., get the byte, but don't advance the offset).
*
* @param {Boolean} peek true means don't move offset.
* @return {Number} the next byte, null if not enough data.
*/
Reader.prototype.readByte = function(peek) {
if (this._size - this._offset < 1)
return null;
var b = this._buf[this._offset] & 0xff;
if (!peek)
this._offset += 1;
return b;
};
Reader.prototype.peek = function() {
return this.readByte(true);
};
/**
* Reads a (potentially) variable length off the BER buffer. This call is
* not really meant to be called directly, as callers have to manipulate
* the internal buffer afterwards.
*
* As a result of this call, you can call `Reader.length`, until the
* next thing called that does a readLength.
*
* @return {Number} the amount of offset to advance the buffer.
* @throws {InvalidAsn1Error} on bad ASN.1
*/
Reader.prototype.readLength = function(offset) {
if (offset === undefined)
offset = this._offset;
if (offset >= this._size)
return null;
var lenB = this._buf[offset++] & 0xff;
if (lenB === null)
return null;
if ((lenB & 0x80) == 0x80) {
lenB &= 0x7f;
if (lenB == 0)
throw newInvalidAsn1Error('Indefinite length not supported');
if (lenB > 4)
throw newInvalidAsn1Error('encoding too long');
if (this._size - offset < lenB)
return null;
this._len = 0;
for (var i = 0; i < lenB; i++)
this._len = (this._len << 8) + (this._buf[offset++] & 0xff);
} else {
// Wasn't a variable length
this._len = lenB;
}
return offset;
};
/**
* Parses the next sequence in this BER buffer.
*
* To get the length of the sequence, call `Reader.length`.
*
* @return {Number} the sequence's tag.
*/
Reader.prototype.readSequence = function(tag) {
var seq = this.peek();
if (seq === null)
return null;
if (tag !== undefined && tag !== seq)
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
': got 0x' + seq.toString(16));
var o = this.readLength(this._offset + 1); // stored in `length`
if (o === null)
return null;
this._offset = o;
return seq;
};
Reader.prototype.readInt = function() {
return this._readTag(ASN1.Integer);
};
Reader.prototype.readBoolean = function() {
return (this._readTag(ASN1.Boolean) === 0 ? false : true);
};
Reader.prototype.readEnumeration = function() {
return this._readTag(ASN1.Enumeration);
};
Reader.prototype.readString = function(tag, retbuf) {
if (!tag)
tag = ASN1.OctetString;
var b = this.peek();
if (b === null)
return null;
if (b !== tag)
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
': got 0x' + b.toString(16));
var o = this.readLength(this._offset + 1); // stored in `length`
if (o === null)
return null;
if (this.length > this._size - o)
return null;
this._offset = o;
if (this.length === 0)
return retbuf ? new Buffer(0) : '';
var str = this._buf.slice(this._offset, this._offset + this.length);
this._offset += this.length;
return retbuf ? str : str.toString('utf8');
};
Reader.prototype.readOID = function(tag) {
if (!tag)
tag = ASN1.OID;
var b = this.readString(tag, true);
if (b === null)
return null;
var values = [];
var value = 0;
for (var i = 0; i < b.length; i++) {
var byte = b[i] & 0xff;
value <<= 7;
value += byte & 0x7f;
if ((byte & 0x80) == 0) {
values.push(value);
value = 0;
}
}
value = values.shift();
values.unshift(value % 40);
values.unshift((value / 40) >> 0);
return values.join('.');
};
Reader.prototype._readTag = function(tag) {
assert.ok(tag !== undefined);
var b = this.peek();
if (b === null)
return null;
if (b !== tag)
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
': got 0x' + b.toString(16));
var o = this.readLength(this._offset + 1); // stored in `length`
if (o === null)
return null;
if (this.length > 4)
throw newInvalidAsn1Error('Integer too long: ' + this.length);
if (this.length > this._size - o)
return null;
this._offset = o;
var fb = this._buf[this._offset];
var value = 0;
for (var i = 0; i < this.length; i++) {
value <<= 8;
value |= (this._buf[this._offset++] & 0xff);
}
if ((fb & 0x80) == 0x80 && i !== 4)
value -= (1 << (i * 8));
return value >> 0;
};
///--- Exported API
module.exports = Reader;
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
module.exports = {
EOC: 0,
Boolean: 1,
Integer: 2,
BitString: 3,
OctetString: 4,
Null: 5,
OID: 6,
ObjectDescriptor: 7,
External: 8,
Real: 9, // float
Enumeration: 10,
PDV: 11,
Utf8String: 12,
RelativeOID: 13,
Sequence: 16,
Set: 17,
NumericString: 18,
PrintableString: 19,
T61String: 20,
VideotexString: 21,
IA5String: 22,
UTCTime: 23,
GeneralizedTime: 24,
GraphicString: 25,
VisibleString: 26,
GeneralString: 28,
UniversalString: 29,
CharacterString: 30,
BMPString: 31,
Constructor: 32,
Context: 128
};
+316
View File
@@ -0,0 +1,316 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
var assert = require('assert');
var ASN1 = require('./types');
var errors = require('./errors');
///--- Globals
var newInvalidAsn1Error = errors.newInvalidAsn1Error;
var DEFAULT_OPTS = {
size: 1024,
growthFactor: 8
};
///--- Helpers
function merge(from, to) {
assert.ok(from);
assert.equal(typeof(from), 'object');
assert.ok(to);
assert.equal(typeof(to), 'object');
var keys = Object.getOwnPropertyNames(from);
keys.forEach(function(key) {
if (to[key])
return;
var value = Object.getOwnPropertyDescriptor(from, key);
Object.defineProperty(to, key, value);
});
return to;
}
///--- API
function Writer(options) {
options = merge(DEFAULT_OPTS, options || {});
this._buf = new Buffer(options.size || 1024);
this._size = this._buf.length;
this._offset = 0;
this._options = options;
// A list of offsets in the buffer where we need to insert
// sequence tag/len pairs.
this._seq = [];
}
Object.defineProperty(Writer.prototype, 'buffer', {
get: function () {
if (this._seq.length)
throw new InvalidAsn1Error(this._seq.length + ' unended sequence(s)');
return (this._buf.slice(0, this._offset));
}
});
Writer.prototype.writeByte = function(b) {
if (typeof(b) !== 'number')
throw new TypeError('argument must be a Number');
this._ensure(1);
this._buf[this._offset++] = b;
};
Writer.prototype.writeInt = function(i, tag) {
if (typeof(i) !== 'number')
throw new TypeError('argument must be a Number');
if (typeof(tag) !== 'number')
tag = ASN1.Integer;
var sz = 4;
while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
(sz > 1)) {
sz--;
i <<= 8;
}
if (sz > 4)
throw new InvalidAsn1Error('BER ints cannot be > 0xffffffff');
this._ensure(2 + sz);
this._buf[this._offset++] = tag;
this._buf[this._offset++] = sz;
while (sz-- > 0) {
this._buf[this._offset++] = ((i & 0xff000000) >>> 24);
i <<= 8;
}
};
Writer.prototype.writeNull = function() {
this.writeByte(ASN1.Null);
this.writeByte(0x00);
};
Writer.prototype.writeEnumeration = function(i, tag) {
if (typeof(i) !== 'number')
throw new TypeError('argument must be a Number');
if (typeof(tag) !== 'number')
tag = ASN1.Enumeration;
return this.writeInt(i, tag);
};
Writer.prototype.writeBoolean = function(b, tag) {
if (typeof(b) !== 'boolean')
throw new TypeError('argument must be a Boolean');
if (typeof(tag) !== 'number')
tag = ASN1.Boolean;
this._ensure(3);
this._buf[this._offset++] = tag;
this._buf[this._offset++] = 0x01;
this._buf[this._offset++] = b ? 0xff : 0x00;
};
Writer.prototype.writeString = function(s, tag) {
if (typeof(s) !== 'string')
throw new TypeError('argument must be a string (was: ' + typeof(s) + ')');
if (typeof(tag) !== 'number')
tag = ASN1.OctetString;
var len = Buffer.byteLength(s);
this.writeByte(tag);
this.writeLength(len);
if (len) {
this._ensure(len);
this._buf.write(s, this._offset);
this._offset += len;
}
};
Writer.prototype.writeBuffer = function(buf, tag) {
if (typeof(tag) !== 'number')
throw new TypeError('tag must be a number');
if (!Buffer.isBuffer(buf))
throw new TypeError('argument must be a buffer');
this.writeByte(tag);
this.writeLength(buf.length);
this._ensure(buf.length);
buf.copy(this._buf, this._offset, 0, buf.length);
this._offset += buf.length;
};
Writer.prototype.writeStringArray = function(strings) {
if ((!strings instanceof Array))
throw new TypeError('argument must be an Array[String]');
var self = this;
strings.forEach(function(s) {
self.writeString(s);
});
};
// This is really to solve DER cases, but whatever for now
Writer.prototype.writeOID = function(s, tag) {
if (typeof(s) !== 'string')
throw new TypeError('argument must be a string');
if (typeof(tag) !== 'number')
tag = ASN1.OID;
if (!/^([0-9]+\.){3,}[0-9]+$/.test(s))
throw new Error('argument is not a valid OID string');
function encodeOctet(bytes, octet) {
if (octet < 128) {
bytes.push(octet);
} else if (octet < 16384) {
bytes.push((octet >>> 7) | 0x80);
bytes.push(octet & 0x7F);
} else if (octet < 2097152) {
bytes.push((octet >>> 14) | 0x80);
bytes.push(((octet >>> 7) | 0x80) & 0xFF);
bytes.push(octet & 0x7F);
} else if (octet < 268435456) {
bytes.push((octet >>> 21) | 0x80);
bytes.push(((octet >>> 14) | 0x80) & 0xFF);
bytes.push(((octet >>> 7) | 0x80) & 0xFF);
bytes.push(octet & 0x7F);
} else {
bytes.push(((octet >>> 28) | 0x80) & 0xFF);
bytes.push(((octet >>> 21) | 0x80) & 0xFF);
bytes.push(((octet >>> 14) | 0x80) & 0xFF);
bytes.push(((octet >>> 7) | 0x80) & 0xFF);
bytes.push(octet & 0x7F);
}
}
var tmp = s.split('.');
var bytes = [];
bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));
tmp.slice(2).forEach(function(b) {
encodeOctet(bytes, parseInt(b, 10));
});
var self = this;
this._ensure(2 + bytes.length);
this.writeByte(tag);
this.writeLength(bytes.length);
bytes.forEach(function(b) {
self.writeByte(b);
});
};
Writer.prototype.writeLength = function(len) {
if (typeof(len) !== 'number')
throw new TypeError('argument must be a Number');
this._ensure(4);
if (len <= 0x7f) {
this._buf[this._offset++] = len;
} else if (len <= 0xff) {
this._buf[this._offset++] = 0x81;
this._buf[this._offset++] = len;
} else if (len <= 0xffff) {
this._buf[this._offset++] = 0x82;
this._buf[this._offset++] = len >> 8;
this._buf[this._offset++] = len;
} else if (len <= 0xffffff) {
this._buf[this._offset++] = 0x83;
this._buf[this._offset++] = len >> 16;
this._buf[this._offset++] = len >> 8;
this._buf[this._offset++] = len;
} else {
throw new InvalidAsn1ERror('Length too long (> 4 bytes)');
}
};
Writer.prototype.startSequence = function(tag) {
if (typeof(tag) !== 'number')
tag = ASN1.Sequence | ASN1.Constructor;
this.writeByte(tag);
this._seq.push(this._offset);
this._ensure(3);
this._offset += 3;
};
Writer.prototype.endSequence = function() {
var seq = this._seq.pop();
var start = seq + 3;
var len = this._offset - start;
if (len <= 0x7f) {
this._shift(start, len, -2);
this._buf[seq] = len;
} else if (len <= 0xff) {
this._shift(start, len, -1);
this._buf[seq] = 0x81;
this._buf[seq + 1] = len;
} else if (len <= 0xffff) {
this._buf[seq] = 0x82;
this._buf[seq + 1] = len >> 8;
this._buf[seq + 2] = len;
} else if (len <= 0xffffff) {
this._shift(start, len, 1);
this._buf[seq] = 0x83;
this._buf[seq + 1] = len >> 16;
this._buf[seq + 2] = len >> 8;
this._buf[seq + 3] = len;
} else {
throw new InvalidAsn1Error('Sequence too long');
}
};
Writer.prototype._shift = function(start, len, shift) {
assert.ok(start !== undefined);
assert.ok(len !== undefined);
assert.ok(shift);
this._buf.copy(this._buf, start + shift, start, start + len);
this._offset += shift;
};
Writer.prototype._ensure = function(len) {
assert.ok(len);
if (this._size - this._offset < len) {
var sz = this._size * this._options.growthFactor;
if (sz - this._offset < len)
sz += len;
var buf = new Buffer(sz);
this._buf.copy(buf, 0, 0, this._offset);
this._buf = buf;
this._size = sz;
}
};
///--- Exported API
module.exports = Writer;
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
// If you have no idea what ASN.1 or BER is, see this:
// ftp://ftp.rsa.com/pub/pkcs/ascii/layman.asc
var Ber = require('./ber/index');
///--- Exported API
module.exports = {
Ber: Ber,
BerReader: Ber.Reader,
BerWriter: Ber.Writer
};
+65
View File
@@ -0,0 +1,65 @@
{
"_from": "asn1@0.2.3",
"_id": "asn1@0.2.3",
"_inBundle": false,
"_integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
"_location": "/asn1",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "asn1@0.2.3",
"name": "asn1",
"escapedName": "asn1",
"rawSpec": "0.2.3",
"saveSpec": null,
"fetchSpec": "0.2.3"
},
"_requiredBy": [
"/ldapjs"
],
"_resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
"_shasum": "dac8787713c9966849fc8180777ebe9c1ddf3b86",
"_spec": "asn1@0.2.3",
"_where": "/home/erik/Documents/workspace_brackets/a1_BME_Project_Ohm/om/node_modules/ldapjs",
"author": {
"name": "Mark Cavage",
"email": "mcavage@gmail.com"
},
"bugs": {
"url": "https://github.com/mcavage/node-asn1/issues"
},
"bundleDependencies": false,
"contributors": [
{
"name": "David Gwynne",
"email": "loki@animata.net"
},
{
"name": "Yunong Xiao",
"email": "yunong@joyent.com"
},
{
"name": "Alex Wilson",
"email": "alex.wilson@joyent.com"
}
],
"dependencies": {},
"deprecated": false,
"description": "Contains parsers and serializers for ASN.1 (currently BER only)",
"devDependencies": {
"tap": "0.4.8"
},
"homepage": "https://github.com/mcavage/node-asn1#readme",
"license": "MIT",
"main": "lib/index.js",
"name": "asn1",
"repository": {
"type": "git",
"url": "git://github.com/mcavage/node-asn1.git"
},
"scripts": {
"test": "tap ./tst"
},
"version": "0.2.3"
}
+208
View File
@@ -0,0 +1,208 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
var test = require('tap').test;
///--- Globals
var BerReader;
///--- Tests
test('load library', function(t) {
BerReader = require('../../lib/index').BerReader;
t.ok(BerReader);
try {
new BerReader();
t.fail('Should have thrown');
} catch (e) {
t.ok(e instanceof TypeError, 'Should have been a type error');
}
t.end();
});
test('read byte', function(t) {
var reader = new BerReader(new Buffer([0xde]));
t.ok(reader);
t.equal(reader.readByte(), 0xde, 'wrong value');
t.end();
});
test('read 1 byte int', function(t) {
var reader = new BerReader(new Buffer([0x02, 0x01, 0x03]));
t.ok(reader);
t.equal(reader.readInt(), 0x03, 'wrong value');
t.equal(reader.length, 0x01, 'wrong length');
t.end();
});
test('read 2 byte int', function(t) {
var reader = new BerReader(new Buffer([0x02, 0x02, 0x7e, 0xde]));
t.ok(reader);
t.equal(reader.readInt(), 0x7ede, 'wrong value');
t.equal(reader.length, 0x02, 'wrong length');
t.end();
});
test('read 3 byte int', function(t) {
var reader = new BerReader(new Buffer([0x02, 0x03, 0x7e, 0xde, 0x03]));
t.ok(reader);
t.equal(reader.readInt(), 0x7ede03, 'wrong value');
t.equal(reader.length, 0x03, 'wrong length');
t.end();
});
test('read 4 byte int', function(t) {
var reader = new BerReader(new Buffer([0x02, 0x04, 0x7e, 0xde, 0x03, 0x01]));
t.ok(reader);
t.equal(reader.readInt(), 0x7ede0301, 'wrong value');
t.equal(reader.length, 0x04, 'wrong length');
t.end();
});
test('read 1 byte negative int', function(t) {
var reader = new BerReader(new Buffer([0x02, 0x01, 0xdc]));
t.ok(reader);
t.equal(reader.readInt(), -36, 'wrong value');
t.equal(reader.length, 0x01, 'wrong length');
t.end();
});
test('read 2 byte negative int', function(t) {
var reader = new BerReader(new Buffer([0x02, 0x02, 0xc0, 0x4e]));
t.ok(reader);
t.equal(reader.readInt(), -16306, 'wrong value');
t.equal(reader.length, 0x02, 'wrong length');
t.end();
});
test('read 3 byte negative int', function(t) {
var reader = new BerReader(new Buffer([0x02, 0x03, 0xff, 0x00, 0x19]));
t.ok(reader);
t.equal(reader.readInt(), -65511, 'wrong value');
t.equal(reader.length, 0x03, 'wrong length');
t.end();
});
test('read 4 byte negative int', function(t) {
var reader = new BerReader(new Buffer([0x02, 0x04, 0x91, 0x7c, 0x22, 0x1f]));
t.ok(reader);
t.equal(reader.readInt(), -1854135777, 'wrong value');
t.equal(reader.length, 0x04, 'wrong length');
t.end();
});
test('read boolean true', function(t) {
var reader = new BerReader(new Buffer([0x01, 0x01, 0xff]));
t.ok(reader);
t.equal(reader.readBoolean(), true, 'wrong value');
t.equal(reader.length, 0x01, 'wrong length');
t.end();
});
test('read boolean false', function(t) {
var reader = new BerReader(new Buffer([0x01, 0x01, 0x00]));
t.ok(reader);
t.equal(reader.readBoolean(), false, 'wrong value');
t.equal(reader.length, 0x01, 'wrong length');
t.end();
});
test('read enumeration', function(t) {
var reader = new BerReader(new Buffer([0x0a, 0x01, 0x20]));
t.ok(reader);
t.equal(reader.readEnumeration(), 0x20, 'wrong value');
t.equal(reader.length, 0x01, 'wrong length');
t.end();
});
test('read string', function(t) {
var dn = 'cn=foo,ou=unit,o=test';
var buf = new Buffer(dn.length + 2);
buf[0] = 0x04;
buf[1] = Buffer.byteLength(dn);
buf.write(dn, 2);
var reader = new BerReader(buf);
t.ok(reader);
t.equal(reader.readString(), dn, 'wrong value');
t.equal(reader.length, dn.length, 'wrong length');
t.end();
});
test('read sequence', function(t) {
var reader = new BerReader(new Buffer([0x30, 0x03, 0x01, 0x01, 0xff]));
t.ok(reader);
t.equal(reader.readSequence(), 0x30, 'wrong value');
t.equal(reader.length, 0x03, 'wrong length');
t.equal(reader.readBoolean(), true, 'wrong value');
t.equal(reader.length, 0x01, 'wrong length');
t.end();
});
test('anonymous LDAPv3 bind', function(t) {
var BIND = new Buffer(14);
BIND[0] = 0x30; // Sequence
BIND[1] = 12; // len
BIND[2] = 0x02; // ASN.1 Integer
BIND[3] = 1; // len
BIND[4] = 0x04; // msgid (make up 4)
BIND[5] = 0x60; // Bind Request
BIND[6] = 7; // len
BIND[7] = 0x02; // ASN.1 Integer
BIND[8] = 1; // len
BIND[9] = 0x03; // v3
BIND[10] = 0x04; // String (bind dn)
BIND[11] = 0; // len
BIND[12] = 0x80; // ContextSpecific (choice)
BIND[13] = 0; // simple bind
// Start testing ^^
var ber = new BerReader(BIND);
t.equal(ber.readSequence(), 48, 'Not an ASN.1 Sequence');
t.equal(ber.length, 12, 'Message length should be 12');
t.equal(ber.readInt(), 4, 'Message id should have been 4');
t.equal(ber.readSequence(), 96, 'Bind Request should have been 96');
t.equal(ber.length, 7, 'Bind length should have been 7');
t.equal(ber.readInt(), 3, 'LDAP version should have been 3');
t.equal(ber.readString(), '', 'Bind DN should have been empty');
t.equal(ber.length, 0, 'string length should have been 0');
t.equal(ber.readByte(), 0x80, 'Should have been ContextSpecific (choice)');
t.equal(ber.readByte(), 0, 'Should have been simple bind');
t.equal(null, ber.readByte(), 'Should be out of data');
t.end();
});
test('long string', function(t) {
var buf = new Buffer(256);
var o;
var s =
'2;649;CN=Red Hat CS 71GA Demo,O=Red Hat CS 71GA Demo,C=US;' +
'CN=RHCS Agent - admin01,UID=admin01,O=redhat,C=US [1] This is ' +
'Teena Vradmin\'s description.';
buf[0] = 0x04;
buf[1] = 0x81;
buf[2] = 0x94;
buf.write(s, 3);
var ber = new BerReader(buf.slice(0, 3 + s.length));
t.equal(ber.readString(), s);
t.end();
});
+370
View File
@@ -0,0 +1,370 @@
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
var test = require('tap').test;
var sys = require('sys');
///--- Globals
var BerWriter;
var BerReader;
///--- Tests
test('load library', function(t) {
BerWriter = require('../../lib/index').BerWriter;
t.ok(BerWriter);
t.ok(new BerWriter());
t.end();
});
test('write byte', function(t) {
var writer = new BerWriter();
writer.writeByte(0xC2);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 1, 'Wrong length');
t.equal(ber[0], 0xC2, 'value wrong');
t.end();
});
test('write 1 byte int', function(t) {
var writer = new BerWriter();
writer.writeInt(0x7f);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 3, 'Wrong length for an int: ' + ber.length);
t.equal(ber[0], 0x02, 'ASN.1 tag wrong (2) -> ' + ber[0]);
t.equal(ber[1], 0x01, 'length wrong(1) -> ' + ber[1]);
t.equal(ber[2], 0x7f, 'value wrong(3) -> ' + ber[2]);
t.end();
});
test('write 2 byte int', function(t) {
var writer = new BerWriter();
writer.writeInt(0x7ffe);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 4, 'Wrong length for an int');
t.equal(ber[0], 0x02, 'ASN.1 tag wrong');
t.equal(ber[1], 0x02, 'length wrong');
t.equal(ber[2], 0x7f, 'value wrong (byte 1)');
t.equal(ber[3], 0xfe, 'value wrong (byte 2)');
t.end();
});
test('write 3 byte int', function(t) {
var writer = new BerWriter();
writer.writeInt(0x7ffffe);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 5, 'Wrong length for an int');
t.equal(ber[0], 0x02, 'ASN.1 tag wrong');
t.equal(ber[1], 0x03, 'length wrong');
t.equal(ber[2], 0x7f, 'value wrong (byte 1)');
t.equal(ber[3], 0xff, 'value wrong (byte 2)');
t.equal(ber[4], 0xfe, 'value wrong (byte 3)');
t.end();
});
test('write 4 byte int', function(t) {
var writer = new BerWriter();
writer.writeInt(0x7ffffffe);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 6, 'Wrong length for an int');
t.equal(ber[0], 0x02, 'ASN.1 tag wrong');
t.equal(ber[1], 0x04, 'length wrong');
t.equal(ber[2], 0x7f, 'value wrong (byte 1)');
t.equal(ber[3], 0xff, 'value wrong (byte 2)');
t.equal(ber[4], 0xff, 'value wrong (byte 3)');
t.equal(ber[5], 0xfe, 'value wrong (byte 4)');
t.end();
});
test('write 1 byte negative int', function(t) {
var writer = new BerWriter();
writer.writeInt(-128);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 3, 'Wrong length for an int');
t.equal(ber[0], 0x02, 'ASN.1 tag wrong');
t.equal(ber[1], 0x01, 'length wrong');
t.equal(ber[2], 0x80, 'value wrong (byte 1)');
t.end();
});
test('write 2 byte negative int', function(t) {
var writer = new BerWriter();
writer.writeInt(-22400);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 4, 'Wrong length for an int');
t.equal(ber[0], 0x02, 'ASN.1 tag wrong');
t.equal(ber[1], 0x02, 'length wrong');
t.equal(ber[2], 0xa8, 'value wrong (byte 1)');
t.equal(ber[3], 0x80, 'value wrong (byte 2)');
t.end();
});
test('write 3 byte negative int', function(t) {
var writer = new BerWriter();
writer.writeInt(-481653);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 5, 'Wrong length for an int');
t.equal(ber[0], 0x02, 'ASN.1 tag wrong');
t.equal(ber[1], 0x03, 'length wrong');
t.equal(ber[2], 0xf8, 'value wrong (byte 1)');
t.equal(ber[3], 0xa6, 'value wrong (byte 2)');
t.equal(ber[4], 0x8b, 'value wrong (byte 3)');
t.end();
});
test('write 4 byte negative int', function(t) {
var writer = new BerWriter();
writer.writeInt(-1522904131);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 6, 'Wrong length for an int');
t.equal(ber[0], 0x02, 'ASN.1 tag wrong');
t.equal(ber[1], 0x04, 'length wrong');
t.equal(ber[2], 0xa5, 'value wrong (byte 1)');
t.equal(ber[3], 0x3a, 'value wrong (byte 2)');
t.equal(ber[4], 0x53, 'value wrong (byte 3)');
t.equal(ber[5], 0xbd, 'value wrong (byte 4)');
t.end();
});
test('write boolean', function(t) {
var writer = new BerWriter();
writer.writeBoolean(true);
writer.writeBoolean(false);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 6, 'Wrong length');
t.equal(ber[0], 0x01, 'tag wrong');
t.equal(ber[1], 0x01, 'length wrong');
t.equal(ber[2], 0xff, 'value wrong');
t.equal(ber[3], 0x01, 'tag wrong');
t.equal(ber[4], 0x01, 'length wrong');
t.equal(ber[5], 0x00, 'value wrong');
t.end();
});
test('write string', function(t) {
var writer = new BerWriter();
writer.writeString('hello world');
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 13, 'wrong length');
t.equal(ber[0], 0x04, 'wrong tag');
t.equal(ber[1], 11, 'wrong length');
t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value');
t.end();
});
test('write buffer', function(t) {
var writer = new BerWriter();
// write some stuff to start with
writer.writeString('hello world');
var ber = writer.buffer;
var buf = new Buffer([0x04, 0x0b, 0x30, 0x09, 0x02, 0x01, 0x0f, 0x01, 0x01,
0xff, 0x01, 0x01, 0xff]);
writer.writeBuffer(buf.slice(2, buf.length), 0x04);
ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 26, 'wrong length');
t.equal(ber[0], 0x04, 'wrong tag');
t.equal(ber[1], 11, 'wrong length');
t.equal(ber.slice(2, 13).toString('utf8'), 'hello world', 'wrong value');
t.equal(ber[13], buf[0], 'wrong tag');
t.equal(ber[14], buf[1], 'wrong length');
for (var i = 13, j = 0; i < ber.length && j < buf.length; i++, j++) {
t.equal(ber[i], buf[j], 'buffer contents not identical');
}
t.end();
});
test('write string array', function(t) {
var writer = new BerWriter();
writer.writeStringArray(['hello world', 'fubar!']);
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 21, 'wrong length');
t.equal(ber[0], 0x04, 'wrong tag');
t.equal(ber[1], 11, 'wrong length');
t.equal(ber.slice(2, 13).toString('utf8'), 'hello world', 'wrong value');
t.equal(ber[13], 0x04, 'wrong tag');
t.equal(ber[14], 6, 'wrong length');
t.equal(ber.slice(15).toString('utf8'), 'fubar!', 'wrong value');
t.end();
});
test('resize internal buffer', function(t) {
var writer = new BerWriter({size: 2});
writer.writeString('hello world');
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 13, 'wrong length');
t.equal(ber[0], 0x04, 'wrong tag');
t.equal(ber[1], 11, 'wrong length');
t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value');
t.end();
});
test('sequence', function(t) {
var writer = new BerWriter({size: 25});
writer.startSequence();
writer.writeString('hello world');
writer.endSequence();
var ber = writer.buffer;
t.ok(ber);
console.log(ber);
t.equal(ber.length, 15, 'wrong length');
t.equal(ber[0], 0x30, 'wrong tag');
t.equal(ber[1], 13, 'wrong length');
t.equal(ber[2], 0x04, 'wrong tag');
t.equal(ber[3], 11, 'wrong length');
t.equal(ber.slice(4).toString('utf8'), 'hello world', 'wrong value');
t.end();
});
test('nested sequence', function(t) {
var writer = new BerWriter({size: 25});
writer.startSequence();
writer.writeString('hello world');
writer.startSequence();
writer.writeString('hello world');
writer.endSequence();
writer.endSequence();
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 30, 'wrong length');
t.equal(ber[0], 0x30, 'wrong tag');
t.equal(ber[1], 28, 'wrong length');
t.equal(ber[2], 0x04, 'wrong tag');
t.equal(ber[3], 11, 'wrong length');
t.equal(ber.slice(4, 15).toString('utf8'), 'hello world', 'wrong value');
t.equal(ber[15], 0x30, 'wrong tag');
t.equal(ber[16], 13, 'wrong length');
t.equal(ber[17], 0x04, 'wrong tag');
t.equal(ber[18], 11, 'wrong length');
t.equal(ber.slice(19, 30).toString('utf8'), 'hello world', 'wrong value');
t.end();
});
test('LDAP bind message', function(t) {
var dn = 'cn=foo,ou=unit,o=test';
var writer = new BerWriter();
writer.startSequence();
writer.writeInt(3); // msgid = 3
writer.startSequence(0x60); // ldap bind
writer.writeInt(3); // ldap v3
writer.writeString(dn);
writer.writeByte(0x80);
writer.writeByte(0x00);
writer.endSequence();
writer.endSequence();
var ber = writer.buffer;
t.ok(ber);
t.equal(ber.length, 35, 'wrong length (buffer)');
t.equal(ber[0], 0x30, 'wrong tag');
t.equal(ber[1], 33, 'wrong length');
t.equal(ber[2], 0x02, 'wrong tag');
t.equal(ber[3], 1, 'wrong length');
t.equal(ber[4], 0x03, 'wrong value');
t.equal(ber[5], 0x60, 'wrong tag');
t.equal(ber[6], 28, 'wrong length');
t.equal(ber[7], 0x02, 'wrong tag');
t.equal(ber[8], 1, 'wrong length');
t.equal(ber[9], 0x03, 'wrong value');
t.equal(ber[10], 0x04, 'wrong tag');
t.equal(ber[11], dn.length, 'wrong length');
t.equal(ber.slice(12, 33).toString('utf8'), dn, 'wrong value');
t.equal(ber[33], 0x80, 'wrong tag');
t.equal(ber[34], 0x00, 'wrong len');
t.end();
});
test('Write OID', function(t) {
var oid = '1.2.840.113549.1.1.1';
var writer = new BerWriter();
writer.writeOID(oid);
var ber = writer.buffer;
t.ok(ber);
console.log(require('util').inspect(ber));
console.log(require('util').inspect(new Buffer([0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d,
0x01, 0x01, 0x01])));
t.end();
});
+6
View File
@@ -0,0 +1,6 @@
Dave Eddy <dave@daveeddy.com>
Fred Kuo <fred.kuo@joyent.com>
Lars-Magnus Skog <ralphtheninja@riseup.net>
Mark Cavage <mcavage@gmail.com>
Patrick Mooney <pmooney@pfmooney.com>
Rob Gulewich <robert.gulewich@joyent.com>
+14
View File
@@ -0,0 +1,14 @@
# assert-plus Changelog
## 1.0.0
- *BREAKING* assert.number (and derivatives) now accept Infinity as valid input
- Add assert.finite check. Previous assert.number callers should use this if
they expect Infinity inputs to throw.
## 0.2.0
- Fix `assert.object(null)` so it throws
- Fix optional/arrayOf exports for non-type-of asserts
- Add optiona/arrayOf exports for Stream/Date/Regex/uuid
- Add basic unit test coverage
+162
View File
@@ -0,0 +1,162 @@
# assert-plus
This library is a super small wrapper over node's assert module that has two
things: (1) the ability to disable assertions with the environment variable
NODE\_NDEBUG, and (2) some API wrappers for argument testing. Like
`assert.string(myArg, 'myArg')`. As a simple example, most of my code looks
like this:
```javascript
var assert = require('assert-plus');
function fooAccount(options, callback) {
assert.object(options, 'options');
assert.number(options.id, 'options.id');
assert.bool(options.isManager, 'options.isManager');
assert.string(options.name, 'options.name');
assert.arrayOfString(options.email, 'options.email');
assert.func(callback, 'callback');
// Do stuff
callback(null, {});
}
```
# API
All methods that *aren't* part of node's core assert API are simply assumed to
take an argument, and then a string 'name' that's not a message; `AssertionError`
will be thrown if the assertion fails with a message like:
AssertionError: foo (string) is required
at test (/home/mark/work/foo/foo.js:3:9)
at Object.<anonymous> (/home/mark/work/foo/foo.js:15:1)
at Module._compile (module.js:446:26)
at Object..js (module.js:464:10)
at Module.load (module.js:353:31)
at Function._load (module.js:311:12)
at Array.0 (module.js:484:10)
at EventEmitter._tickCallback (node.js:190:38)
from:
```javascript
function test(foo) {
assert.string(foo, 'foo');
}
```
There you go. You can check that arrays are of a homogeneous type with `Arrayof$Type`:
```javascript
function test(foo) {
assert.arrayOfString(foo, 'foo');
}
```
You can assert IFF an argument is not `undefined` (i.e., an optional arg):
```javascript
assert.optionalString(foo, 'foo');
```
Lastly, you can opt-out of assertion checking altogether by setting the
environment variable `NODE_NDEBUG=1`. This is pseudo-useful if you have
lots of assertions, and don't want to pay `typeof ()` taxes to v8 in
production. Be advised: The standard functions re-exported from `assert` are
also disabled in assert-plus if NDEBUG is specified. Using them directly from
the `assert` module avoids this behavior.
The complete list of APIs is:
* assert.array
* assert.bool
* assert.buffer
* assert.func
* assert.number
* assert.finite
* assert.object
* assert.string
* assert.stream
* assert.date
* assert.regexp
* assert.uuid
* assert.arrayOfArray
* assert.arrayOfBool
* assert.arrayOfBuffer
* assert.arrayOfFunc
* assert.arrayOfNumber
* assert.arrayOfFinite
* assert.arrayOfObject
* assert.arrayOfString
* assert.arrayOfStream
* assert.arrayOfDate
* assert.arrayOfRegexp
* assert.arrayOfUuid
* assert.optionalArray
* assert.optionalBool
* assert.optionalBuffer
* assert.optionalFunc
* assert.optionalNumber
* assert.optionalFinite
* assert.optionalObject
* assert.optionalString
* assert.optionalStream
* assert.optionalDate
* assert.optionalRegexp
* assert.optionalUuid
* assert.optionalArrayOfArray
* assert.optionalArrayOfBool
* assert.optionalArrayOfBuffer
* assert.optionalArrayOfFunc
* assert.optionalArrayOfNumber
* assert.optionalArrayOfFinite
* assert.optionalArrayOfObject
* assert.optionalArrayOfString
* assert.optionalArrayOfStream
* assert.optionalArrayOfDate
* assert.optionalArrayOfRegexp
* assert.optionalArrayOfUuid
* assert.AssertionError
* assert.fail
* assert.ok
* assert.equal
* assert.notEqual
* assert.deepEqual
* assert.notDeepEqual
* assert.strictEqual
* assert.notStrictEqual
* assert.throws
* assert.doesNotThrow
* assert.ifError
# Installation
npm install assert-plus
## License
The MIT License (MIT)
Copyright (c) 2012 Mark Cavage
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## Bugs
See <https://github.com/mcavage/node-assert-plus/issues>.
+211
View File
@@ -0,0 +1,211 @@
// Copyright (c) 2012, Mark Cavage. All rights reserved.
// Copyright 2015 Joyent, Inc.
var assert = require('assert');
var Stream = require('stream').Stream;
var util = require('util');
///--- Globals
/* JSSTYLED */
var UUID_REGEXP = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
///--- Internal
function _capitalize(str) {
return (str.charAt(0).toUpperCase() + str.slice(1));
}
function _toss(name, expected, oper, arg, actual) {
throw new assert.AssertionError({
message: util.format('%s (%s) is required', name, expected),
actual: (actual === undefined) ? typeof (arg) : actual(arg),
expected: expected,
operator: oper || '===',
stackStartFunction: _toss.caller
});
}
function _getClass(arg) {
return (Object.prototype.toString.call(arg).slice(8, -1));
}
function noop() {
// Why even bother with asserts?
}
///--- Exports
var types = {
bool: {
check: function (arg) { return typeof (arg) === 'boolean'; }
},
func: {
check: function (arg) { return typeof (arg) === 'function'; }
},
string: {
check: function (arg) { return typeof (arg) === 'string'; }
},
object: {
check: function (arg) {
return typeof (arg) === 'object' && arg !== null;
}
},
number: {
check: function (arg) {
return typeof (arg) === 'number' && !isNaN(arg);
}
},
finite: {
check: function (arg) {
return typeof (arg) === 'number' && !isNaN(arg) && isFinite(arg);
}
},
buffer: {
check: function (arg) { return Buffer.isBuffer(arg); },
operator: 'Buffer.isBuffer'
},
array: {
check: function (arg) { return Array.isArray(arg); },
operator: 'Array.isArray'
},
stream: {
check: function (arg) { return arg instanceof Stream; },
operator: 'instanceof',
actual: _getClass
},
date: {
check: function (arg) { return arg instanceof Date; },
operator: 'instanceof',
actual: _getClass
},
regexp: {
check: function (arg) { return arg instanceof RegExp; },
operator: 'instanceof',
actual: _getClass
},
uuid: {
check: function (arg) {
return typeof (arg) === 'string' && UUID_REGEXP.test(arg);
},
operator: 'isUUID'
}
};
function _setExports(ndebug) {
var keys = Object.keys(types);
var out;
/* re-export standard assert */
if (process.env.NODE_NDEBUG) {
out = noop;
} else {
out = function (arg, msg) {
if (!arg) {
_toss(msg, 'true', arg);
}
};
}
/* standard checks */
keys.forEach(function (k) {
if (ndebug) {
out[k] = noop;
return;
}
var type = types[k];
out[k] = function (arg, msg) {
if (!type.check(arg)) {
_toss(msg, k, type.operator, arg, type.actual);
}
};
});
/* optional checks */
keys.forEach(function (k) {
var name = 'optional' + _capitalize(k);
if (ndebug) {
out[name] = noop;
return;
}
var type = types[k];
out[name] = function (arg, msg) {
if (arg === undefined || arg === null) {
return;
}
if (!type.check(arg)) {
_toss(msg, k, type.operator, arg, type.actual);
}
};
});
/* arrayOf checks */
keys.forEach(function (k) {
var name = 'arrayOf' + _capitalize(k);
if (ndebug) {
out[name] = noop;
return;
}
var type = types[k];
var expected = '[' + k + ']';
out[name] = function (arg, msg) {
if (!Array.isArray(arg)) {
_toss(msg, expected, type.operator, arg, type.actual);
}
var i;
for (i = 0; i < arg.length; i++) {
if (!type.check(arg[i])) {
_toss(msg, expected, type.operator, arg, type.actual);
}
}
};
});
/* optionalArrayOf checks */
keys.forEach(function (k) {
var name = 'optionalArrayOf' + _capitalize(k);
if (ndebug) {
out[name] = noop;
return;
}
var type = types[k];
var expected = '[' + k + ']';
out[name] = function (arg, msg) {
if (arg === undefined || arg === null) {
return;
}
if (!Array.isArray(arg)) {
_toss(msg, expected, type.operator, arg, type.actual);
}
var i;
for (i = 0; i < arg.length; i++) {
if (!type.check(arg[i])) {
_toss(msg, expected, type.operator, arg, type.actual);
}
}
};
});
/* re-export built-in assertions */
Object.keys(assert).forEach(function (k) {
if (k === 'AssertionError') {
out[k] = assert[k];
return;
}
if (ndebug) {
out[k] = noop;
return;
}
out[k] = assert[k];
});
/* export ourselves (for unit tests _only_) */
out._setExports = _setExports;
return out;
}
module.exports = _setExports(process.env.NODE_NDEBUG);
+84
View File
@@ -0,0 +1,84 @@
{
"_from": "assert-plus@^1.0.0",
"_id": "assert-plus@1.0.0",
"_inBundle": false,
"_integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"_location": "/assert-plus",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "assert-plus@^1.0.0",
"name": "assert-plus",
"escapedName": "assert-plus",
"rawSpec": "^1.0.0",
"saveSpec": null,
"fetchSpec": "^1.0.0"
},
"_requiredBy": [
"/dashdash",
"/ldapjs",
"/verror"
],
"_resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"_shasum": "f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525",
"_spec": "assert-plus@^1.0.0",
"_where": "/home/erik/Documents/workspace_brackets/a1_BME_Project_Ohm/om/node_modules/ldapjs",
"author": {
"name": "Mark Cavage",
"email": "mcavage@gmail.com"
},
"bugs": {
"url": "https://github.com/mcavage/node-assert-plus/issues"
},
"bundleDependencies": false,
"contributors": [
{
"name": "Dave Eddy",
"email": "dave@daveeddy.com"
},
{
"name": "Fred Kuo",
"email": "fred.kuo@joyent.com"
},
{
"name": "Lars-Magnus Skog",
"email": "ralphtheninja@riseup.net"
},
{
"name": "Mark Cavage",
"email": "mcavage@gmail.com"
},
{
"name": "Patrick Mooney",
"email": "pmooney@pfmooney.com"
},
{
"name": "Rob Gulewich",
"email": "robert.gulewich@joyent.com"
}
],
"dependencies": {},
"deprecated": false,
"description": "Extra assertions on top of node's assert module",
"devDependencies": {
"faucet": "0.0.1",
"tape": "4.2.2"
},
"engines": {
"node": ">=0.8"
},
"homepage": "https://github.com/mcavage/node-assert-plus#readme",
"license": "MIT",
"main": "./assert.js",
"name": "assert-plus",
"optionalDependencies": {},
"repository": {
"type": "git",
"url": "git+https://github.com/mcavage/node-assert-plus.git"
},
"scripts": {
"test": "tape tests/*.js | ./node_modules/.bin/faucet"
},
"version": "1.0.0"
}
+272
View File
@@ -0,0 +1,272 @@
#v2.6.2
- Updated lodash to squelch a security warning (#1620)
# v2.6.1
- Updated lodash to prevent `npm audit` warnings. (#1532, #1533)
- Made `async-es` more optimized for webpack users (#1517)
- Fixed a stack overflow with large collections and a synchronous iterator (#1514)
- Various small fixes/chores (#1505, #1511, #1527, #1530)
# v2.6.0
- Added missing aliases for many methods. Previously, you could not (e.g.) `require('async/find')` or use `async.anyLimit`. (#1483)
- Improved `queue` performance. (#1448, #1454)
- Add missing sourcemap (#1452, #1453)
- Various doc updates (#1448, #1471, #1483)
# v2.5.0
- Added `concatLimit`, the `Limit` equivalent of [`concat`](https://caolan.github.io/async/docs.html#concat) ([#1426](https://github.com/caolan/async/issues/1426), [#1430](https://github.com/caolan/async/pull/1430))
- `concat` improvements: it now preserves order, handles falsy values and the `iteratee` callback takes a variable number of arguments ([#1437](https://github.com/caolan/async/issues/1437), [#1436](https://github.com/caolan/async/pull/1436))
- Fixed an issue in `queue` where there was a size discrepancy between `workersList().length` and `running()` ([#1428](https://github.com/caolan/async/issues/1428), [#1429](https://github.com/caolan/async/pull/1429))
- Various doc fixes ([#1422](https://github.com/caolan/async/issues/1422), [#1424](https://github.com/caolan/async/pull/1424))
# v2.4.1
- Fixed a bug preventing functions wrapped with `timeout()` from being re-used. ([#1418](https://github.com/caolan/async/issues/1418), [#1419](https://github.com/caolan/async/issues/1419))
# v2.4.0
- Added `tryEach`, for running async functions in parallel, where you only expect one to succeed. ([#1365](https://github.com/caolan/async/issues/1365), [#687](https://github.com/caolan/async/issues/687))
- Improved performance, most notably in `parallel` and `waterfall` ([#1395](https://github.com/caolan/async/issues/1395))
- Added `queue.remove()`, for removing items in a `queue` ([#1397](https://github.com/caolan/async/issues/1397), [#1391](https://github.com/caolan/async/issues/1391))
- Fixed using `eval`, preventing Async from running in pages with Content Security Policy ([#1404](https://github.com/caolan/async/issues/1404), [#1403](https://github.com/caolan/async/issues/1403))
- Fixed errors thrown in an `asyncify`ed function's callback being caught by the underlying Promise ([#1408](https://github.com/caolan/async/issues/1408))
- Fixed timing of `queue.empty()` ([#1367](https://github.com/caolan/async/issues/1367))
- Various doc fixes ([#1314](https://github.com/caolan/async/issues/1314), [#1394](https://github.com/caolan/async/issues/1394), [#1412](https://github.com/caolan/async/issues/1412))
# v2.3.0
- Added support for ES2017 `async` functions. Wherever you can pass a Node-style/CPS function that uses a callback, you can also pass an `async` function. Previously, you had to wrap `async` functions with `asyncify`. The caveat is that it will only work if `async` functions are supported natively in your environment, transpiled implementations can't be detected. ([#1386](https://github.com/caolan/async/issues/1386), [#1390](https://github.com/caolan/async/issues/1390))
- Small doc fix ([#1392](https://github.com/caolan/async/issues/1392))
# v2.2.0
- Added `groupBy`, and the `Series`/`Limit` equivalents, analogous to [`_.groupBy`](http://lodash.com/docs#groupBy) ([#1364](https://github.com/caolan/async/issues/1364))
- Fixed `transform` bug when `callback` was not passed ([#1381](https://github.com/caolan/async/issues/1381))
- Added note about `reflect` to `parallel` docs ([#1385](https://github.com/caolan/async/issues/1385))
# v2.1.5
- Fix `auto` bug when function names collided with Array.prototype ([#1358](https://github.com/caolan/async/issues/1358))
- Improve some error messages ([#1349](https://github.com/caolan/async/issues/1349))
- Avoid stack overflow case in queue
- Fixed an issue in `some`, `every` and `find` where processing would continue after the result was determined.
- Cleanup implementations of `some`, `every` and `find`
# v2.1.3
- Make bundle size smaller
- Create optimized hotpath for `filter` in array case.
# v2.1.2
- Fixed a stackoverflow bug with `detect`, `some`, `every` on large inputs ([#1293](https://github.com/caolan/async/issues/1293)).
# v2.1.0
- `retry` and `retryable` now support an optional `errorFilter` function that determines if the `task` should retry on the error ([#1256](https://github.com/caolan/async/issues/1256), [#1261](https://github.com/caolan/async/issues/1261))
- Optimized array iteration in `race`, `cargo`, `queue`, and `priorityQueue` ([#1253](https://github.com/caolan/async/issues/1253))
- Added alias documentation to doc site ([#1251](https://github.com/caolan/async/issues/1251), [#1254](https://github.com/caolan/async/issues/1254))
- Added [BootStrap scrollspy](http://getbootstrap.com/javascript/#scrollspy) to docs to highlight in the sidebar the current method being viewed ([#1289](https://github.com/caolan/async/issues/1289), [#1300](https://github.com/caolan/async/issues/1300))
- Various minor doc fixes ([#1263](https://github.com/caolan/async/issues/1263), [#1264](https://github.com/caolan/async/issues/1264), [#1271](https://github.com/caolan/async/issues/1271), [#1278](https://github.com/caolan/async/issues/1278), [#1280](https://github.com/caolan/async/issues/1280), [#1282](https://github.com/caolan/async/issues/1282), [#1302](https://github.com/caolan/async/issues/1302))
# v2.0.1
- Significantly optimized all iteration based collection methods such as `each`, `map`, `filter`, etc ([#1245](https://github.com/caolan/async/issues/1245), [#1246](https://github.com/caolan/async/issues/1246), [#1247](https://github.com/caolan/async/issues/1247)).
# v2.0.0
Lots of changes here!
First and foremost, we have a slick new [site for docs](https://caolan.github.io/async/). Special thanks to [**@hargasinski**](https://github.com/hargasinski) for his work converting our old docs to `jsdoc` format and implementing the new website. Also huge ups to [**@ivanseidel**](https://github.com/ivanseidel) for designing our new logo. It was a long process for both of these tasks, but I think these changes turned out extraordinary well.
The biggest feature is modularization. You can now `require("async/series")` to only require the `series` function. Every Async library function is available this way. You still can `require("async")` to require the entire library, like you could do before.
We also provide Async as a collection of ES2015 modules. You can now `import {each} from 'async-es'` or `import waterfall from 'async-es/waterfall'`. If you are using only a few Async functions, and are using a ES bundler such as Rollup, this can significantly lower your build size.
Major thanks to [**@Kikobeats**](github.com/Kikobeats), [**@aearly**](github.com/aearly) and [**@megawac**](github.com/megawac) for doing the majority of the modularization work, as well as [**@jdalton**](github.com/jdalton) and [**@Rich-Harris**](github.com/Rich-Harris) for advisory work on the general modularization strategy.
Another one of the general themes of the 2.0 release is standardization of what an "async" function is. We are now more strictly following the node-style continuation passing style. That is, an async function is a function that:
1. Takes a variable number of arguments
2. The last argument is always a callback
3. The callback can accept any number of arguments
4. The first argument passed to the callback will be treated as an error result, if the argument is truthy
5. Any number of result arguments can be passed after the "error" argument
6. The callback is called once and exactly once, either on the same tick or later tick of the JavaScript event loop.
There were several cases where Async accepted some functions that did not strictly have these properties, most notably `auto`, `every`, `some`, `filter`, `reject` and `detect`.
Another theme is performance. We have eliminated internal deferrals in all cases where they make sense. For example, in `waterfall` and `auto`, there was a `setImmediate` between each task -- these deferrals have been removed. A `setImmediate` call can add up to 1ms of delay. This might not seem like a lot, but it can add up if you are using many Async functions in the course of processing a HTTP request, for example. Nearly all asynchronous functions that do I/O already have some sort of deferral built in, so the extra deferral is unnecessary. The trade-off of this change is removing our built-in stack-overflow defense. Many synchronous callback calls in series can quickly overflow the JS call stack. If you do have a function that is sometimes synchronous (calling its callback on the same tick), and are running into stack overflows, wrap it with `async.ensureAsync()`.
Another big performance win has been re-implementing `queue`, `cargo`, and `priorityQueue` with [doubly linked lists](https://en.wikipedia.org/wiki/Doubly_linked_list) instead of arrays. This has lead to queues being an order of [magnitude faster on large sets of tasks](https://github.com/caolan/async/pull/1205).
## New Features
- Async is now modularized. Individual functions can be `require()`d from the main package. (`require('async/auto')`) ([#984](https://github.com/caolan/async/issues/984), [#996](https://github.com/caolan/async/issues/996))
- Async is also available as a collection of ES2015 modules in the new `async-es` package. (`import {forEachSeries} from 'async-es'`) ([#984](https://github.com/caolan/async/issues/984), [#996](https://github.com/caolan/async/issues/996))
- Added `race`, analogous to `Promise.race()`. It will run an array of async tasks in parallel and will call its callback with the result of the first task to respond. ([#568](https://github.com/caolan/async/issues/568), [#1038](https://github.com/caolan/async/issues/1038))
- Collection methods now accept ES2015 iterators. Maps, Sets, and anything that implements the iterator spec can now be passed directly to `each`, `map`, `parallel`, etc.. ([#579](https://github.com/caolan/async/issues/579), [#839](https://github.com/caolan/async/issues/839), [#1074](https://github.com/caolan/async/issues/1074))
- Added `mapValues`, for mapping over the properties of an object and returning an object with the same keys. ([#1157](https://github.com/caolan/async/issues/1157), [#1177](https://github.com/caolan/async/issues/1177))
- Added `timeout`, a wrapper for an async function that will make the task time-out after the specified time. ([#1007](https://github.com/caolan/async/issues/1007), [#1027](https://github.com/caolan/async/issues/1027))
- Added `reflect` and `reflectAll`, analagous to [`Promise.reflect()`](http://bluebirdjs.com/docs/api/reflect.html), a wrapper for async tasks that always succeeds, by gathering results and errors into an object. ([#942](https://github.com/caolan/async/issues/942), [#1012](https://github.com/caolan/async/issues/1012), [#1095](https://github.com/caolan/async/issues/1095))
- `constant` supports dynamic arguments -- it will now always use its last argument as the callback. ([#1016](https://github.com/caolan/async/issues/1016), [#1052](https://github.com/caolan/async/issues/1052))
- `setImmediate` and `nextTick` now support arguments to partially apply to the deferred function, like the node-native versions do. ([#940](https://github.com/caolan/async/issues/940), [#1053](https://github.com/caolan/async/issues/1053))
- `auto` now supports resolving cyclic dependencies using [Kahn's algorithm](https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm) ([#1140](https://github.com/caolan/async/issues/1140)).
- Added `autoInject`, a relative of `auto` that automatically spreads a task's dependencies as arguments to the task function. ([#608](https://github.com/caolan/async/issues/608), [#1055](https://github.com/caolan/async/issues/1055), [#1099](https://github.com/caolan/async/issues/1099), [#1100](https://github.com/caolan/async/issues/1100))
- You can now limit the concurrency of `auto` tasks. ([#635](https://github.com/caolan/async/issues/635), [#637](https://github.com/caolan/async/issues/637))
- Added `retryable`, a relative of `retry` that wraps an async function, making it retry when called. ([#1058](https://github.com/caolan/async/issues/1058))
- `retry` now supports specifying a function that determines the next time interval, useful for exponential backoff, logging and other retry strategies. ([#1161](https://github.com/caolan/async/issues/1161))
- `retry` will now pass all of the arguments the task function was resolved with to the callback ([#1231](https://github.com/caolan/async/issues/1231)).
- Added `q.unsaturated` -- callback called when a `queue`'s number of running workers falls below a threshold. ([#868](https://github.com/caolan/async/issues/868), [#1030](https://github.com/caolan/async/issues/1030), [#1033](https://github.com/caolan/async/issues/1033), [#1034](https://github.com/caolan/async/issues/1034))
- Added `q.error` -- a callback called whenever a `queue` task calls its callback with an error. ([#1170](https://github.com/caolan/async/issues/1170))
- `applyEach` and `applyEachSeries` now pass results to the final callback. ([#1088](https://github.com/caolan/async/issues/1088))
## Breaking changes
- Calling a callback more than once is considered an error, and an error will be thrown. This had an explicit breaking change in `waterfall`. If you were relying on this behavior, you should more accurately represent your control flow as an event emitter or stream. ([#814](https://github.com/caolan/async/issues/814), [#815](https://github.com/caolan/async/issues/815), [#1048](https://github.com/caolan/async/issues/1048), [#1050](https://github.com/caolan/async/issues/1050))
- `auto` task functions now always take the callback as the last argument. If a task has dependencies, the `results` object will be passed as the first argument. To migrate old task functions, wrap them with [`_.flip`](https://lodash.com/docs#flip) ([#1036](https://github.com/caolan/async/issues/1036), [#1042](https://github.com/caolan/async/issues/1042))
- Internal `setImmediate` calls have been refactored away. This may make existing flows vulnerable to stack overflows if you use many synchronous functions in series. Use `ensureAsync` to work around this. ([#696](https://github.com/caolan/async/issues/696), [#704](https://github.com/caolan/async/issues/704), [#1049](https://github.com/caolan/async/issues/1049), [#1050](https://github.com/caolan/async/issues/1050))
- `map` used to return an object when iterating over an object. `map` now always returns an array, like in other libraries. The previous object behavior has been split out into `mapValues`. ([#1157](https://github.com/caolan/async/issues/1157), [#1177](https://github.com/caolan/async/issues/1177))
- `filter`, `reject`, `some`, `every`, `detect` and their families like `{METHOD}Series` and `{METHOD}Limit` now expect an error as the first callback argument, rather than just a simple boolean. Pass `null` as the first argument, or use `fs.access` instead of `fs.exists`. ([#118](https://github.com/caolan/async/issues/118), [#774](https://github.com/caolan/async/issues/774), [#1028](https://github.com/caolan/async/issues/1028), [#1041](https://github.com/caolan/async/issues/1041))
- `{METHOD}` and `{METHOD}Series` are now implemented in terms of `{METHOD}Limit`. This is a major internal simplification, and is not expected to cause many problems, but it does subtly affect how functions execute internally. ([#778](https://github.com/caolan/async/issues/778), [#847](https://github.com/caolan/async/issues/847))
- `retry`'s callback is now optional. Previously, omitting the callback would partially apply the function, meaning it could be passed directly as a task to `series` or `auto`. The partially applied "control-flow" behavior has been separated out into `retryable`. ([#1054](https://github.com/caolan/async/issues/1054), [#1058](https://github.com/caolan/async/issues/1058))
- The test function for `whilst`, `until`, and `during` used to be passed non-error args from the iteratee function's callback, but this led to weirdness where the first call of the test function would be passed no args. We have made it so the test function is never passed extra arguments, and only the `doWhilst`, `doUntil`, and `doDuring` functions pass iteratee callback arguments to the test function ([#1217](https://github.com/caolan/async/issues/1217), [#1224](https://github.com/caolan/async/issues/1224))
- The `q.tasks` array has been renamed `q._tasks` and is now implemented as a doubly linked list (DLL). Any code that used to interact with this array will need to be updated to either use the provided helpers or support DLLs ([#1205](https://github.com/caolan/async/issues/1205)).
- The timing of the `q.saturated()` callback in a `queue` has been modified to better reflect when tasks pushed to the queue will start queueing. ([#724](https://github.com/caolan/async/issues/724), [#1078](https://github.com/caolan/async/issues/1078))
- Removed `iterator` method in favour of [ES2015 iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators ) which natively supports arrays ([#1237](https://github.com/caolan/async/issues/1237))
- Dropped support for Component, Jam, SPM, and Volo ([#1175](https://github.com/caolan/async/issues/1175), #[#176](https://github.com/caolan/async/issues/176))
## Bug Fixes
- Improved handling of no dependency cases in `auto` & `autoInject` ([#1147](https://github.com/caolan/async/issues/1147)).
- Fixed a bug where the callback generated by `asyncify` with `Promises` could resolve twice ([#1197](https://github.com/caolan/async/issues/1197)).
- Fixed several documented optional callbacks not actually being optional ([#1223](https://github.com/caolan/async/issues/1223)).
## Other
- Added `someSeries` and `everySeries` for symmetry, as well as a complete set of `any`/`anyLimit`/`anySeries` and `all`/`/allLmit`/`allSeries` aliases.
- Added `find` as an alias for `detect. (as well as `findLimit` and `findSeries`).
- Various doc fixes ([#1005](https://github.com/caolan/async/issues/1005), [#1008](https://github.com/caolan/async/issues/1008), [#1010](https://github.com/caolan/async/issues/1010), [#1015](https://github.com/caolan/async/issues/1015), [#1021](https://github.com/caolan/async/issues/1021), [#1037](https://github.com/caolan/async/issues/1037), [#1039](https://github.com/caolan/async/issues/1039), [#1051](https://github.com/caolan/async/issues/1051), [#1102](https://github.com/caolan/async/issues/1102), [#1107](https://github.com/caolan/async/issues/1107), [#1121](https://github.com/caolan/async/issues/1121), [#1123](https://github.com/caolan/async/issues/1123), [#1129](https://github.com/caolan/async/issues/1129), [#1135](https://github.com/caolan/async/issues/1135), [#1138](https://github.com/caolan/async/issues/1138), [#1141](https://github.com/caolan/async/issues/1141), [#1153](https://github.com/caolan/async/issues/1153), [#1216](https://github.com/caolan/async/issues/1216), [#1217](https://github.com/caolan/async/issues/1217), [#1232](https://github.com/caolan/async/issues/1232), [#1233](https://github.com/caolan/async/issues/1233), [#1236](https://github.com/caolan/async/issues/1236), [#1238](https://github.com/caolan/async/issues/1238))
Thank you [**@aearly**](github.com/aearly) and [**@megawac**](github.com/megawac) for taking the lead on version 2 of async.
------------------------------------------
# v1.5.2
- Allow using `"constructor"` as an argument in `memoize` ([#998](https://github.com/caolan/async/issues/998))
- Give a better error messsage when `auto` dependency checking fails ([#994](https://github.com/caolan/async/issues/994))
- Various doc updates ([#936](https://github.com/caolan/async/issues/936), [#956](https://github.com/caolan/async/issues/956), [#979](https://github.com/caolan/async/issues/979), [#1002](https://github.com/caolan/async/issues/1002))
# v1.5.1
- Fix issue with `pause` in `queue` with concurrency enabled ([#946](https://github.com/caolan/async/issues/946))
- `while` and `until` now pass the final result to callback ([#963](https://github.com/caolan/async/issues/963))
- `auto` will properly handle concurrency when there is no callback ([#966](https://github.com/caolan/async/issues/966))
- `auto` will no. properly stop execution when an error occurs ([#988](https://github.com/caolan/async/issues/988), [#993](https://github.com/caolan/async/issues/993))
- Various doc fixes ([#971](https://github.com/caolan/async/issues/971), [#980](https://github.com/caolan/async/issues/980))
# v1.5.0
- Added `transform`, analogous to [`_.transform`](http://lodash.com/docs#transform) ([#892](https://github.com/caolan/async/issues/892))
- `map` now returns an object when an object is passed in, rather than array with non-numeric keys. `map` will begin always returning an array with numeric indexes in the next major release. ([#873](https://github.com/caolan/async/issues/873))
- `auto` now accepts an optional `concurrency` argument to limit the number o. running tasks ([#637](https://github.com/caolan/async/issues/637))
- Added `queue#workersList()`, to retrieve the lis. of currently running tasks. ([#891](https://github.com/caolan/async/issues/891))
- Various code simplifications ([#896](https://github.com/caolan/async/issues/896), [#904](https://github.com/caolan/async/issues/904))
- Various doc fixes :scroll: ([#890](https://github.com/caolan/async/issues/890), [#894](https://github.com/caolan/async/issues/894), [#903](https://github.com/caolan/async/issues/903), [#905](https://github.com/caolan/async/issues/905), [#912](https://github.com/caolan/async/issues/912))
# v1.4.2
- Ensure coverage files don't get published on npm ([#879](https://github.com/caolan/async/issues/879))
# v1.4.1
- Add in overlooked `detectLimit` method ([#866](https://github.com/caolan/async/issues/866))
- Removed unnecessary files from npm releases ([#861](https://github.com/caolan/async/issues/861))
- Removed usage of a reserved word to prevent :boom: in older environments ([#870](https://github.com/caolan/async/issues/870))
# v1.4.0
- `asyncify` now supports promises ([#840](https://github.com/caolan/async/issues/840))
- Added `Limit` versions of `filter` and `reject` ([#836](https://github.com/caolan/async/issues/836))
- Add `Limit` versions of `detect`, `some` and `every` ([#828](https://github.com/caolan/async/issues/828), [#829](https://github.com/caolan/async/issues/829))
- `some`, `every` and `detect` now short circuit early ([#828](https://github.com/caolan/async/issues/828), [#829](https://github.com/caolan/async/issues/829))
- Improve detection of the global object ([#804](https://github.com/caolan/async/issues/804)), enabling use in WebWorkers
- `whilst` now called with arguments from iterator ([#823](https://github.com/caolan/async/issues/823))
- `during` now gets called with arguments from iterator ([#824](https://github.com/caolan/async/issues/824))
- Code simplifications and optimizations aplenty ([diff](https://github.com/caolan/async/compare/v1.3.0...v1.4.0))
# v1.3.0
New Features:
- Added `constant`
- Added `asyncify`/`wrapSync` for making sync functions work with callbacks. ([#671](https://github.com/caolan/async/issues/671), [#806](https://github.com/caolan/async/issues/806))
- Added `during` and `doDuring`, which are like `whilst` with an async truth test. ([#800](https://github.com/caolan/async/issues/800))
- `retry` now accepts an `interval` parameter to specify a delay between retries. ([#793](https://github.com/caolan/async/issues/793))
- `async` should work better in Web Workers due to better `root` detection ([#804](https://github.com/caolan/async/issues/804))
- Callbacks are now optional in `whilst`, `doWhilst`, `until`, and `doUntil` ([#642](https://github.com/caolan/async/issues/642))
- Various internal updates ([#786](https://github.com/caolan/async/issues/786), [#801](https://github.com/caolan/async/issues/801), [#802](https://github.com/caolan/async/issues/802), [#803](https://github.com/caolan/async/issues/803))
- Various doc fixes ([#790](https://github.com/caolan/async/issues/790), [#794](https://github.com/caolan/async/issues/794))
Bug Fixes:
- `cargo` now exposes the `payload` size, and `cargo.payload` can be changed on the fly after the `cargo` is created. ([#740](https://github.com/caolan/async/issues/740), [#744](https://github.com/caolan/async/issues/744), [#783](https://github.com/caolan/async/issues/783))
# v1.2.1
Bug Fix:
- Small regression with synchronous iterator behavior in `eachSeries` with a 1-element array. Before 1.1.0, `eachSeries`'s callback was called on the same tick, which this patch restores. In 2.0.0, it will be called on the next tick. ([#782](https://github.com/caolan/async/issues/782))
# v1.2.0
New Features:
- Added `timesLimit` ([#743](https://github.com/caolan/async/issues/743))
- `concurrency` can be changed after initialization in `queue` by setting `q.concurrency`. The new concurrency will be reflected the next time a task is processed. ([#747](https://github.com/caolan/async/issues/747), [#772](https://github.com/caolan/async/issues/772))
Bug Fixes:
- Fixed a regression in `each` and family with empty arrays that have additional properties. ([#775](https://github.com/caolan/async/issues/775), [#777](https://github.com/caolan/async/issues/777))
# v1.1.1
Bug Fix:
- Small regression with synchronous iterator behavior in `eachSeries` with a 1-element array. Before 1.1.0, `eachSeries`'s callback was called on the same tick, which this patch restores. In 2.0.0, it will be called on the next tick. ([#782](https://github.com/caolan/async/issues/782))
# v1.1.0
New Features:
- `cargo` now supports all of the same methods and event callbacks as `queue`.
- Added `ensureAsync` - A wrapper that ensures an async function calls its callback on a later tick. ([#769](https://github.com/caolan/async/issues/769))
- Optimized `map`, `eachOf`, and `waterfall` families of functions
- Passing a `null` or `undefined` array to `map`, `each`, `parallel` and families will be treated as an empty array ([#667](https://github.com/caolan/async/issues/667)).
- The callback is now optional for the composed results of `compose` and `seq`. ([#618](https://github.com/caolan/async/issues/618))
- Reduced file size by 4kb, (minified version by 1kb)
- Added code coverage through `nyc` and `coveralls` ([#768](https://github.com/caolan/async/issues/768))
Bug Fixes:
- `forever` will no longer stack overflow with a synchronous iterator ([#622](https://github.com/caolan/async/issues/622))
- `eachLimit` and other limit functions will stop iterating once an error occurs ([#754](https://github.com/caolan/async/issues/754))
- Always pass `null` in callbacks when there is no error ([#439](https://github.com/caolan/async/issues/439))
- Ensure proper conditions when calling `drain()` after pushing an empty data set to a queue ([#668](https://github.com/caolan/async/issues/668))
- `each` and family will properly handle an empty array ([#578](https://github.com/caolan/async/issues/578))
- `eachSeries` and family will finish if the underlying array is modified during execution ([#557](https://github.com/caolan/async/issues/557))
- `queue` will throw if a non-function is passed to `q.push()` ([#593](https://github.com/caolan/async/issues/593))
- Doc fixes ([#629](https://github.com/caolan/async/issues/629), [#766](https://github.com/caolan/async/issues/766))
# v1.0.0
No known breaking changes, we are simply complying with semver from here on out.
Changes:
- Start using a changelog!
- Add `forEachOf` for iterating over Objects (or to iterate Arrays with indexes available) ([#168](https://github.com/caolan/async/issues/168) [#704](https://github.com/caolan/async/issues/704) [#321](https://github.com/caolan/async/issues/321))
- Detect deadlocks in `auto` ([#663](https://github.com/caolan/async/issues/663))
- Better support for require.js ([#527](https://github.com/caolan/async/issues/527))
- Throw if queue created with concurrency `0` ([#714](https://github.com/caolan/async/issues/714))
- Fix unneeded iteration in `queue.resume()` ([#758](https://github.com/caolan/async/issues/758))
- Guard against timer mocking overriding `setImmediate` ([#609](https://github.com/caolan/async/issues/609) [#611](https://github.com/caolan/async/issues/611))
- Miscellaneous doc fixes ([#542](https://github.com/caolan/async/issues/542) [#596](https://github.com/caolan/async/issues/596) [#615](https://github.com/caolan/async/issues/615) [#628](https://github.com/caolan/async/issues/628) [#631](https://github.com/caolan/async/issues/631) [#690](https://github.com/caolan/async/issues/690) [#729](https://github.com/caolan/async/issues/729))
- Use single noop function internally ([#546](https://github.com/caolan/async/issues/546))
- Optimize internal `_each`, `_map` and `_keys` functions.
Generated Vendored
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2010-2018 Caolan McMahon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+56
View File
@@ -0,0 +1,56 @@
![Async Logo](https://raw.githubusercontent.com/caolan/async/master/logo/async-logo_readme.jpg)
[![Build Status via Travis CI](https://travis-ci.org/caolan/async.svg?branch=master)](https://travis-ci.org/caolan/async)
[![NPM version](https://img.shields.io/npm/v/async.svg)](https://www.npmjs.com/package/async)
[![Coverage Status](https://coveralls.io/repos/caolan/async/badge.svg?branch=master)](https://coveralls.io/r/caolan/async?branch=master)
[![Join the chat at https://gitter.im/caolan/async](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/caolan/async?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![libhive - Open source examples](https://www.libhive.com/providers/npm/packages/async/examples/badge.svg)](https://www.libhive.com/providers/npm/packages/async)
[![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/async/badge?style=rounded)](https://www.jsdelivr.com/package/npm/async)
Async is a utility module which provides straight-forward, powerful functions for working with [asynchronous JavaScript](http://caolan.github.io/async/global.html). Although originally designed for use with [Node.js](https://nodejs.org/) and installable via `npm install --save async`, it can also be used directly in the browser.
This version of the package is optimized for the Node.js environment. If you use Async with webpack, install [`async-es`](https://www.npmjs.com/package/async-es) instead.
For Documentation, visit <https://caolan.github.io/async/>
*For Async v1.5.x documentation, go [HERE](https://github.com/caolan/async/blob/v1.5.2/README.md)*
```javascript
// for use with Node-style callbacks...
var async = require("async");
var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"};
var configs = {};
async.forEachOf(obj, (value, key, callback) => {
fs.readFile(__dirname + value, "utf8", (err, data) => {
if (err) return callback(err);
try {
configs[key] = JSON.parse(data);
} catch (e) {
return callback(e);
}
callback();
});
}, err => {
if (err) console.error(err.message);
// configs is now a map of JSON data
doSomethingWith(configs);
});
```
```javascript
var async = require("async");
// ...or ES2017 async functions
async.mapLimit(urls, 5, async function(url) {
const response = await fetch(url)
return response.body
}, (err, results) => {
if (err) throw err
// results is now an array of the response bodies
console.log(results)
})
```
Generated Vendored
+50
View File
@@ -0,0 +1,50 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createTester = require('./internal/createTester');
var _createTester2 = _interopRequireDefault(_createTester);
var _doParallel = require('./internal/doParallel');
var _doParallel2 = _interopRequireDefault(_doParallel);
var _notId = require('./internal/notId');
var _notId2 = _interopRequireDefault(_notId);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Returns `true` if every element in `coll` satisfies an async test. If any
* iteratee call returns `false`, the main `callback` is immediately called.
*
* @name every
* @static
* @memberOf module:Collections
* @method
* @alias all
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
* in the collection in parallel.
* The iteratee must complete with a boolean result value.
* Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called after all the
* `iteratee` functions have finished. Result will be either `true` or `false`
* depending on the values of the async tests. Invoked with (err, result).
* @example
*
* async.every(['file1','file2','file3'], function(filePath, callback) {
* fs.access(filePath, function(err) {
* callback(null, !err)
* });
* }, function(err, result) {
* // if result is true then every file exists
* });
*/
exports.default = (0, _doParallel2.default)((0, _createTester2.default)(_notId2.default, _notId2.default));
module.exports = exports['default'];
+42
View File
@@ -0,0 +1,42 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createTester = require('./internal/createTester');
var _createTester2 = _interopRequireDefault(_createTester);
var _doParallelLimit = require('./internal/doParallelLimit');
var _doParallelLimit2 = _interopRequireDefault(_doParallelLimit);
var _notId = require('./internal/notId');
var _notId2 = _interopRequireDefault(_notId);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* The same as [`every`]{@link module:Collections.every} but runs a maximum of `limit` async operations at a time.
*
* @name everyLimit
* @static
* @memberOf module:Collections
* @method
* @see [async.every]{@link module:Collections.every}
* @alias allLimit
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {number} limit - The maximum number of async operations at a time.
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
* in the collection in parallel.
* The iteratee must complete with a boolean result value.
* Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called after all the
* `iteratee` functions have finished. Result will be either `true` or `false`
* depending on the values of the async tests. Invoked with (err, result).
*/
exports.default = (0, _doParallelLimit2.default)((0, _createTester2.default)(_notId2.default, _notId2.default));
module.exports = exports['default'];
+37
View File
@@ -0,0 +1,37 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _everyLimit = require('./everyLimit');
var _everyLimit2 = _interopRequireDefault(_everyLimit);
var _doLimit = require('./internal/doLimit');
var _doLimit2 = _interopRequireDefault(_doLimit);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* The same as [`every`]{@link module:Collections.every} but runs only a single async operation at a time.
*
* @name everySeries
* @static
* @memberOf module:Collections
* @method
* @see [async.every]{@link module:Collections.every}
* @alias allSeries
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
* in the collection in series.
* The iteratee must complete with a boolean result value.
* Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called after all the
* `iteratee` functions have finished. Result will be either `true` or `false`
* depending on the values of the async tests. Invoked with (err, result).
*/
exports.default = (0, _doLimit2.default)(_everyLimit2.default, 1);
module.exports = exports['default'];
Generated Vendored
+52
View File
@@ -0,0 +1,52 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createTester = require('./internal/createTester');
var _createTester2 = _interopRequireDefault(_createTester);
var _doParallel = require('./internal/doParallel');
var _doParallel2 = _interopRequireDefault(_doParallel);
var _identity = require('lodash/identity');
var _identity2 = _interopRequireDefault(_identity);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Returns `true` if at least one element in the `coll` satisfies an async test.
* If any iteratee call returns `true`, the main `callback` is immediately
* called.
*
* @name some
* @static
* @memberOf module:Collections
* @method
* @alias any
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
* in the collections in parallel.
* The iteratee should complete with a boolean `result` value.
* Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called as soon as any
* iteratee returns `true`, or after all the iteratee functions have finished.
* Result will be either `true` or `false` depending on the values of the async
* tests. Invoked with (err, result).
* @example
*
* async.some(['file1','file2','file3'], function(filePath, callback) {
* fs.access(filePath, function(err) {
* callback(null, !err)
* });
* }, function(err, result) {
* // if result is true then at least one of the files exists
* });
*/
exports.default = (0, _doParallel2.default)((0, _createTester2.default)(Boolean, _identity2.default));
module.exports = exports['default'];
+43
View File
@@ -0,0 +1,43 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createTester = require('./internal/createTester');
var _createTester2 = _interopRequireDefault(_createTester);
var _doParallelLimit = require('./internal/doParallelLimit');
var _doParallelLimit2 = _interopRequireDefault(_doParallelLimit);
var _identity = require('lodash/identity');
var _identity2 = _interopRequireDefault(_identity);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* The same as [`some`]{@link module:Collections.some} but runs a maximum of `limit` async operations at a time.
*
* @name someLimit
* @static
* @memberOf module:Collections
* @method
* @see [async.some]{@link module:Collections.some}
* @alias anyLimit
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {number} limit - The maximum number of async operations at a time.
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
* in the collections in parallel.
* The iteratee should complete with a boolean `result` value.
* Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called as soon as any
* iteratee returns `true`, or after all the iteratee functions have finished.
* Result will be either `true` or `false` depending on the values of the async
* tests. Invoked with (err, result).
*/
exports.default = (0, _doParallelLimit2.default)((0, _createTester2.default)(Boolean, _identity2.default));
module.exports = exports['default'];
+38
View File
@@ -0,0 +1,38 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _someLimit = require('./someLimit');
var _someLimit2 = _interopRequireDefault(_someLimit);
var _doLimit = require('./internal/doLimit');
var _doLimit2 = _interopRequireDefault(_doLimit);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* The same as [`some`]{@link module:Collections.some} but runs only a single async operation at a time.
*
* @name someSeries
* @static
* @memberOf module:Collections
* @method
* @see [async.some]{@link module:Collections.some}
* @alias anySeries
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {AsyncFunction} iteratee - An async truth test to apply to each item
* in the collections in series.
* The iteratee should complete with a boolean `result` value.
* Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called as soon as any
* iteratee returns `true`, or after all the iteratee functions have finished.
* Result will be either `true` or `false` depending on the values of the async
* tests. Invoked with (err, result).
*/
exports.default = (0, _doLimit2.default)(_someLimit2.default, 1);
module.exports = exports['default'];
+68
View File
@@ -0,0 +1,68 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function (fn /*, ...args*/) {
var args = (0, _slice2.default)(arguments, 1);
return function () /*callArgs*/{
var callArgs = (0, _slice2.default)(arguments);
return fn.apply(null, args.concat(callArgs));
};
};
var _slice = require('./internal/slice');
var _slice2 = _interopRequireDefault(_slice);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
;
/**
* Creates a continuation function with some arguments already applied.
*
* Useful as a shorthand when combined with other control flow functions. Any
* arguments passed to the returned function are added to the arguments
* originally passed to apply.
*
* @name apply
* @static
* @memberOf module:Utils
* @method
* @category Util
* @param {Function} fn - The function you want to eventually apply all
* arguments to. Invokes with (arguments...).
* @param {...*} arguments... - Any number of arguments to automatically apply
* when the continuation is called.
* @returns {Function} the partially-applied function
* @example
*
* // using apply
* async.parallel([
* async.apply(fs.writeFile, 'testfile1', 'test1'),
* async.apply(fs.writeFile, 'testfile2', 'test2')
* ]);
*
*
* // the same process without using apply
* async.parallel([
* function(callback) {
* fs.writeFile('testfile1', 'test1', callback);
* },
* function(callback) {
* fs.writeFile('testfile2', 'test2', callback);
* }
* ]);
*
* // It's possible to pass any number of additional arguments when calling the
* // continuation:
*
* node> var fn = async.apply(sys.puts, 'one');
* node> fn('two', 'three');
* one
* two
* three
*/
module.exports = exports['default'];
+51
View File
@@ -0,0 +1,51 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _applyEach = require('./internal/applyEach');
var _applyEach2 = _interopRequireDefault(_applyEach);
var _map = require('./map');
var _map2 = _interopRequireDefault(_map);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Applies the provided arguments to each function in the array, calling
* `callback` after all functions have completed. If you only provide the first
* argument, `fns`, then it will return a function which lets you pass in the
* arguments as if it were a single function call. If more arguments are
* provided, `callback` is required while `args` is still optional.
*
* @name applyEach
* @static
* @memberOf module:ControlFlow
* @method
* @category Control Flow
* @param {Array|Iterable|Object} fns - A collection of {@link AsyncFunction}s
* to all call with the same arguments
* @param {...*} [args] - any number of separate arguments to pass to the
* function.
* @param {Function} [callback] - the final argument should be the callback,
* called when all functions have completed processing.
* @returns {Function} - If only the first argument, `fns`, is provided, it will
* return a function which lets you pass in the arguments as if it were a single
* function call. The signature is `(..args, callback)`. If invoked with any
* arguments, `callback` is required.
* @example
*
* async.applyEach([enableSearch, updateSchema], 'bucket', callback);
*
* // partial application example:
* async.each(
* buckets,
* async.applyEach([enableSearch, updateSchema]),
* callback
* );
*/
exports.default = (0, _applyEach2.default)(_map2.default);
module.exports = exports['default'];
+37
View File
@@ -0,0 +1,37 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _applyEach = require('./internal/applyEach');
var _applyEach2 = _interopRequireDefault(_applyEach);
var _mapSeries = require('./mapSeries');
var _mapSeries2 = _interopRequireDefault(_mapSeries);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* The same as [`applyEach`]{@link module:ControlFlow.applyEach} but runs only a single async operation at a time.
*
* @name applyEachSeries
* @static
* @memberOf module:ControlFlow
* @method
* @see [async.applyEach]{@link module:ControlFlow.applyEach}
* @category Control Flow
* @param {Array|Iterable|Object} fns - A collection of {@link AsyncFunction}s to all
* call with the same arguments
* @param {...*} [args] - any number of separate arguments to pass to the
* function.
* @param {Function} [callback] - the final argument should be the callback,
* called when all functions have completed processing.
* @returns {Function} - If only the first argument is provided, it will return
* a function which lets you pass in the arguments as if it were a single
* function call.
*/
exports.default = (0, _applyEach2.default)(_mapSeries2.default);
module.exports = exports['default'];
+110
View File
@@ -0,0 +1,110 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = asyncify;
var _isObject = require('lodash/isObject');
var _isObject2 = _interopRequireDefault(_isObject);
var _initialParams = require('./internal/initialParams');
var _initialParams2 = _interopRequireDefault(_initialParams);
var _setImmediate = require('./internal/setImmediate');
var _setImmediate2 = _interopRequireDefault(_setImmediate);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Take a sync function and make it async, passing its return value to a
* callback. This is useful for plugging sync functions into a waterfall,
* series, or other async functions. Any arguments passed to the generated
* function will be passed to the wrapped function (except for the final
* callback argument). Errors thrown will be passed to the callback.
*
* If the function passed to `asyncify` returns a Promise, that promises's
* resolved/rejected state will be used to call the callback, rather than simply
* the synchronous return value.
*
* This also means you can asyncify ES2017 `async` functions.
*
* @name asyncify
* @static
* @memberOf module:Utils
* @method
* @alias wrapSync
* @category Util
* @param {Function} func - The synchronous function, or Promise-returning
* function to convert to an {@link AsyncFunction}.
* @returns {AsyncFunction} An asynchronous wrapper of the `func`. To be
* invoked with `(args..., callback)`.
* @example
*
* // passing a regular synchronous function
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(JSON.parse),
* function (data, next) {
* // data is the result of parsing the text.
* // If there was a parsing error, it would have been caught.
* }
* ], callback);
*
* // passing a function returning a promise
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(function (contents) {
* return db.model.create(contents);
* }),
* function (model, next) {
* // `model` is the instantiated model object.
* // If there was an error, this function would be skipped.
* }
* ], callback);
*
* // es2017 example, though `asyncify` is not needed if your JS environment
* // supports async functions out of the box
* var q = async.queue(async.asyncify(async function(file) {
* var intermediateStep = await processFile(file);
* return await somePromise(intermediateStep)
* }));
*
* q.push(files);
*/
function asyncify(func) {
return (0, _initialParams2.default)(function (args, callback) {
var result;
try {
result = func.apply(this, args);
} catch (e) {
return callback(e);
}
// if result is Promise object
if ((0, _isObject2.default)(result) && typeof result.then === 'function') {
result.then(function (value) {
invokeCallback(callback, null, value);
}, function (err) {
invokeCallback(callback, err.message ? err : new Error(err));
});
} else {
callback(null, result);
}
});
}
function invokeCallback(callback, error, value) {
try {
callback(error, value);
} catch (e) {
(0, _setImmediate2.default)(rethrow, e);
}
}
function rethrow(error) {
throw error;
}
module.exports = exports['default'];
Generated Vendored
+289
View File
@@ -0,0 +1,289 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function (tasks, concurrency, callback) {
if (typeof concurrency === 'function') {
// concurrency is optional, shift the args.
callback = concurrency;
concurrency = null;
}
callback = (0, _once2.default)(callback || _noop2.default);
var keys = (0, _keys2.default)(tasks);
var numTasks = keys.length;
if (!numTasks) {
return callback(null);
}
if (!concurrency) {
concurrency = numTasks;
}
var results = {};
var runningTasks = 0;
var hasError = false;
var listeners = Object.create(null);
var readyTasks = [];
// for cycle detection:
var readyToCheck = []; // tasks that have been identified as reachable
// without the possibility of returning to an ancestor task
var uncheckedDependencies = {};
(0, _baseForOwn2.default)(tasks, function (task, key) {
if (!(0, _isArray2.default)(task)) {
// no dependencies
enqueueTask(key, [task]);
readyToCheck.push(key);
return;
}
var dependencies = task.slice(0, task.length - 1);
var remainingDependencies = dependencies.length;
if (remainingDependencies === 0) {
enqueueTask(key, task);
readyToCheck.push(key);
return;
}
uncheckedDependencies[key] = remainingDependencies;
(0, _arrayEach2.default)(dependencies, function (dependencyName) {
if (!tasks[dependencyName]) {
throw new Error('async.auto task `' + key + '` has a non-existent dependency `' + dependencyName + '` in ' + dependencies.join(', '));
}
addListener(dependencyName, function () {
remainingDependencies--;
if (remainingDependencies === 0) {
enqueueTask(key, task);
}
});
});
});
checkForDeadlocks();
processQueue();
function enqueueTask(key, task) {
readyTasks.push(function () {
runTask(key, task);
});
}
function processQueue() {
if (readyTasks.length === 0 && runningTasks === 0) {
return callback(null, results);
}
while (readyTasks.length && runningTasks < concurrency) {
var run = readyTasks.shift();
run();
}
}
function addListener(taskName, fn) {
var taskListeners = listeners[taskName];
if (!taskListeners) {
taskListeners = listeners[taskName] = [];
}
taskListeners.push(fn);
}
function taskComplete(taskName) {
var taskListeners = listeners[taskName] || [];
(0, _arrayEach2.default)(taskListeners, function (fn) {
fn();
});
processQueue();
}
function runTask(key, task) {
if (hasError) return;
var taskCallback = (0, _onlyOnce2.default)(function (err, result) {
runningTasks--;
if (arguments.length > 2) {
result = (0, _slice2.default)(arguments, 1);
}
if (err) {
var safeResults = {};
(0, _baseForOwn2.default)(results, function (val, rkey) {
safeResults[rkey] = val;
});
safeResults[key] = result;
hasError = true;
listeners = Object.create(null);
callback(err, safeResults);
} else {
results[key] = result;
taskComplete(key);
}
});
runningTasks++;
var taskFn = (0, _wrapAsync2.default)(task[task.length - 1]);
if (task.length > 1) {
taskFn(results, taskCallback);
} else {
taskFn(taskCallback);
}
}
function checkForDeadlocks() {
// Kahn's algorithm
// https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
// http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html
var currentTask;
var counter = 0;
while (readyToCheck.length) {
currentTask = readyToCheck.pop();
counter++;
(0, _arrayEach2.default)(getDependents(currentTask), function (dependent) {
if (--uncheckedDependencies[dependent] === 0) {
readyToCheck.push(dependent);
}
});
}
if (counter !== numTasks) {
throw new Error('async.auto cannot execute tasks due to a recursive dependency');
}
}
function getDependents(taskName) {
var result = [];
(0, _baseForOwn2.default)(tasks, function (task, key) {
if ((0, _isArray2.default)(task) && (0, _baseIndexOf2.default)(task, taskName, 0) >= 0) {
result.push(key);
}
});
return result;
}
};
var _arrayEach = require('lodash/_arrayEach');
var _arrayEach2 = _interopRequireDefault(_arrayEach);
var _baseForOwn = require('lodash/_baseForOwn');
var _baseForOwn2 = _interopRequireDefault(_baseForOwn);
var _baseIndexOf = require('lodash/_baseIndexOf');
var _baseIndexOf2 = _interopRequireDefault(_baseIndexOf);
var _isArray = require('lodash/isArray');
var _isArray2 = _interopRequireDefault(_isArray);
var _keys = require('lodash/keys');
var _keys2 = _interopRequireDefault(_keys);
var _noop = require('lodash/noop');
var _noop2 = _interopRequireDefault(_noop);
var _slice = require('./internal/slice');
var _slice2 = _interopRequireDefault(_slice);
var _once = require('./internal/once');
var _once2 = _interopRequireDefault(_once);
var _onlyOnce = require('./internal/onlyOnce');
var _onlyOnce2 = _interopRequireDefault(_onlyOnce);
var _wrapAsync = require('./internal/wrapAsync');
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
module.exports = exports['default'];
/**
* Determines the best order for running the {@link AsyncFunction}s in `tasks`, based on
* their requirements. Each function can optionally depend on other functions
* being completed first, and each function is run as soon as its requirements
* are satisfied.
*
* If any of the {@link AsyncFunction}s pass an error to their callback, the `auto` sequence
* will stop. Further tasks will not execute (so any other functions depending
* on it will not run), and the main `callback` is immediately called with the
* error.
*
* {@link AsyncFunction}s also receive an object containing the results of functions which
* have completed so far as the first argument, if they have dependencies. If a
* task function has no dependencies, it will only be passed a callback.
*
* @name auto
* @static
* @memberOf module:ControlFlow
* @method
* @category Control Flow
* @param {Object} tasks - An object. Each of its properties is either a
* function or an array of requirements, with the {@link AsyncFunction} itself the last item
* in the array. The object's key of a property serves as the name of the task
* defined by that property, i.e. can be used when specifying requirements for
* other tasks. The function receives one or two arguments:
* * a `results` object, containing the results of the previously executed
* functions, only passed if the task has any dependencies,
* * a `callback(err, result)` function, which must be called when finished,
* passing an `error` (which can be `null`) and the result of the function's
* execution.
* @param {number} [concurrency=Infinity] - An optional `integer` for
* determining the maximum number of tasks that can be run in parallel. By
* default, as many as possible.
* @param {Function} [callback] - An optional callback which is called when all
* the tasks have been completed. It receives the `err` argument if any `tasks`
* pass an error to their callback. Results are always returned; however, if an
* error occurs, no further `tasks` will be performed, and the results object
* will only contain partial results. Invoked with (err, results).
* @returns undefined
* @example
*
* async.auto({
* // this function will just be passed a callback
* readData: async.apply(fs.readFile, 'data.txt', 'utf-8'),
* showData: ['readData', function(results, cb) {
* // results.readData is the file's contents
* // ...
* }]
* }, callback);
*
* async.auto({
* get_data: function(callback) {
* console.log('in get_data');
* // async code to get some data
* callback(null, 'data', 'converted to array');
* },
* make_folder: function(callback) {
* console.log('in make_folder');
* // async code to create a directory to store a file in
* // this is run at the same time as getting the data
* callback(null, 'folder');
* },
* write_file: ['get_data', 'make_folder', function(results, callback) {
* console.log('in write_file', JSON.stringify(results));
* // once there is some data and the directory exists,
* // write the data to a file in the directory
* callback(null, 'filename');
* }],
* email_link: ['write_file', function(results, callback) {
* console.log('in email_link', JSON.stringify(results));
* // once the file is written let's email a link to it...
* // results.write_file contains the filename returned by write_file.
* callback(null, {'file':results.write_file, 'email':'user@example.com'});
* }]
* }, function(err, results) {
* console.log('err = ', err);
* console.log('results = ', results);
* });
*/
+170
View File
@@ -0,0 +1,170 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = autoInject;
var _auto = require('./auto');
var _auto2 = _interopRequireDefault(_auto);
var _baseForOwn = require('lodash/_baseForOwn');
var _baseForOwn2 = _interopRequireDefault(_baseForOwn);
var _arrayMap = require('lodash/_arrayMap');
var _arrayMap2 = _interopRequireDefault(_arrayMap);
var _isArray = require('lodash/isArray');
var _isArray2 = _interopRequireDefault(_isArray);
var _trim = require('lodash/trim');
var _trim2 = _interopRequireDefault(_trim);
var _wrapAsync = require('./internal/wrapAsync');
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var FN_ARGS = /^(?:async\s+)?(function)?\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /(=.+)?(\s*)$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function parseParams(func) {
func = func.toString().replace(STRIP_COMMENTS, '');
func = func.match(FN_ARGS)[2].replace(' ', '');
func = func ? func.split(FN_ARG_SPLIT) : [];
func = func.map(function (arg) {
return (0, _trim2.default)(arg.replace(FN_ARG, ''));
});
return func;
}
/**
* A dependency-injected version of the [async.auto]{@link module:ControlFlow.auto} function. Dependent
* tasks are specified as parameters to the function, after the usual callback
* parameter, with the parameter names matching the names of the tasks it
* depends on. This can provide even more readable task graphs which can be
* easier to maintain.
*
* If a final callback is specified, the task results are similarly injected,
* specified as named parameters after the initial error parameter.
*
* The autoInject function is purely syntactic sugar and its semantics are
* otherwise equivalent to [async.auto]{@link module:ControlFlow.auto}.
*
* @name autoInject
* @static
* @memberOf module:ControlFlow
* @method
* @see [async.auto]{@link module:ControlFlow.auto}
* @category Control Flow
* @param {Object} tasks - An object, each of whose properties is an {@link AsyncFunction} of
* the form 'func([dependencies...], callback). The object's key of a property
* serves as the name of the task defined by that property, i.e. can be used
* when specifying requirements for other tasks.
* * The `callback` parameter is a `callback(err, result)` which must be called
* when finished, passing an `error` (which can be `null`) and the result of
* the function's execution. The remaining parameters name other tasks on
* which the task is dependent, and the results from those tasks are the
* arguments of those parameters.
* @param {Function} [callback] - An optional callback which is called when all
* the tasks have been completed. It receives the `err` argument if any `tasks`
* pass an error to their callback, and a `results` object with any completed
* task results, similar to `auto`.
* @example
*
* // The example from `auto` can be rewritten as follows:
* async.autoInject({
* get_data: function(callback) {
* // async code to get some data
* callback(null, 'data', 'converted to array');
* },
* make_folder: function(callback) {
* // async code to create a directory to store a file in
* // this is run at the same time as getting the data
* callback(null, 'folder');
* },
* write_file: function(get_data, make_folder, callback) {
* // once there is some data and the directory exists,
* // write the data to a file in the directory
* callback(null, 'filename');
* },
* email_link: function(write_file, callback) {
* // once the file is written let's email a link to it...
* // write_file contains the filename returned by write_file.
* callback(null, {'file':write_file, 'email':'user@example.com'});
* }
* }, function(err, results) {
* console.log('err = ', err);
* console.log('email_link = ', results.email_link);
* });
*
* // If you are using a JS minifier that mangles parameter names, `autoInject`
* // will not work with plain functions, since the parameter names will be
* // collapsed to a single letter identifier. To work around this, you can
* // explicitly specify the names of the parameters your task function needs
* // in an array, similar to Angular.js dependency injection.
*
* // This still has an advantage over plain `auto`, since the results a task
* // depends on are still spread into arguments.
* async.autoInject({
* //...
* write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback) {
* callback(null, 'filename');
* }],
* email_link: ['write_file', function(write_file, callback) {
* callback(null, {'file':write_file, 'email':'user@example.com'});
* }]
* //...
* }, function(err, results) {
* console.log('err = ', err);
* console.log('email_link = ', results.email_link);
* });
*/
function autoInject(tasks, callback) {
var newTasks = {};
(0, _baseForOwn2.default)(tasks, function (taskFn, key) {
var params;
var fnIsAsync = (0, _wrapAsync.isAsync)(taskFn);
var hasNoDeps = !fnIsAsync && taskFn.length === 1 || fnIsAsync && taskFn.length === 0;
if ((0, _isArray2.default)(taskFn)) {
params = taskFn.slice(0, -1);
taskFn = taskFn[taskFn.length - 1];
newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn);
} else if (hasNoDeps) {
// no dependencies, use the function as-is
newTasks[key] = taskFn;
} else {
params = parseParams(taskFn);
if (taskFn.length === 0 && !fnIsAsync && params.length === 0) {
throw new Error("autoInject task functions require explicit parameters.");
}
// remove callback param
if (!fnIsAsync) params.pop();
newTasks[key] = params.concat(newTask);
}
function newTask(results, taskCb) {
var newArgs = (0, _arrayMap2.default)(params, function (name) {
return results[name];
});
newArgs.push(taskCb);
(0, _wrapAsync2.default)(taskFn).apply(null, newArgs);
}
});
(0, _auto2.default)(newTasks, callback);
}
module.exports = exports['default'];
+17
View File
@@ -0,0 +1,17 @@
{
"name": "async",
"main": "dist/async.js",
"ignore": [
"bower_components",
"lib",
"mocha_test",
"node_modules",
"perf",
"support",
"**/.*",
"*.config.js",
"*.json",
"index.js",
"Makefile"
]
}
+94
View File
@@ -0,0 +1,94 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = cargo;
var _queue = require('./internal/queue');
var _queue2 = _interopRequireDefault(_queue);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* A cargo of tasks for the worker function to complete. Cargo inherits all of
* the same methods and event callbacks as [`queue`]{@link module:ControlFlow.queue}.
* @typedef {Object} CargoObject
* @memberOf module:ControlFlow
* @property {Function} length - A function returning the number of items
* waiting to be processed. Invoke like `cargo.length()`.
* @property {number} payload - An `integer` for determining how many tasks
* should be process per round. This property can be changed after a `cargo` is
* created to alter the payload on-the-fly.
* @property {Function} push - Adds `task` to the `queue`. The callback is
* called once the `worker` has finished processing the task. Instead of a
* single task, an array of `tasks` can be submitted. The respective callback is
* used for every task in the list. Invoke like `cargo.push(task, [callback])`.
* @property {Function} saturated - A callback that is called when the
* `queue.length()` hits the concurrency and further tasks will be queued.
* @property {Function} empty - A callback that is called when the last item
* from the `queue` is given to a `worker`.
* @property {Function} drain - A callback that is called when the last item
* from the `queue` has returned from the `worker`.
* @property {Function} idle - a function returning false if there are items
* waiting or being processed, or true if not. Invoke like `cargo.idle()`.
* @property {Function} pause - a function that pauses the processing of tasks
* until `resume()` is called. Invoke like `cargo.pause()`.
* @property {Function} resume - a function that resumes the processing of
* queued tasks when the queue is paused. Invoke like `cargo.resume()`.
* @property {Function} kill - a function that removes the `drain` callback and
* empties remaining tasks from the queue forcing it to go idle. Invoke like `cargo.kill()`.
*/
/**
* Creates a `cargo` object with the specified payload. Tasks added to the
* cargo will be processed altogether (up to the `payload` limit). If the
* `worker` is in progress, the task is queued until it becomes available. Once
* the `worker` has completed some tasks, each callback of those tasks is
* called. Check out [these](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) [animations](https://camo.githubusercontent.com/f4810e00e1c5f5f8addbe3e9f49064fd5d102699/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130312f38346339323036362d356632392d313165322d383134662d3964336430323431336266642e676966)
* for how `cargo` and `queue` work.
*
* While [`queue`]{@link module:ControlFlow.queue} passes only one task to one of a group of workers
* at a time, cargo passes an array of tasks to a single worker, repeating
* when the worker is finished.
*
* @name cargo
* @static
* @memberOf module:ControlFlow
* @method
* @see [async.queue]{@link module:ControlFlow.queue}
* @category Control Flow
* @param {AsyncFunction} worker - An asynchronous function for processing an array
* of queued tasks. Invoked with `(tasks, callback)`.
* @param {number} [payload=Infinity] - An optional `integer` for determining
* how many tasks should be processed per round; if omitted, the default is
* unlimited.
* @returns {module:ControlFlow.CargoObject} A cargo object to manage the tasks. Callbacks can
* attached as certain properties to listen for specific events during the
* lifecycle of the cargo and inner queue.
* @example
*
* // create a cargo object with payload 2
* var cargo = async.cargo(function(tasks, callback) {
* for (var i=0; i<tasks.length; i++) {
* console.log('hello ' + tasks[i].name);
* }
* callback();
* }, 2);
*
* // add some items
* cargo.push({name: 'foo'}, function(err) {
* console.log('finished processing foo');
* });
* cargo.push({name: 'bar'}, function(err) {
* console.log('finished processing bar');
* });
* cargo.push({name: 'baz'}, function(err) {
* console.log('finished processing baz');
* });
*/
function cargo(worker, payload) {
return (0, _queue2.default)(worker, 1, payload);
}
module.exports = exports['default'];
+58
View File
@@ -0,0 +1,58 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function () /*...args*/{
return _seq2.default.apply(null, (0, _slice2.default)(arguments).reverse());
};
var _seq = require('./seq');
var _seq2 = _interopRequireDefault(_seq);
var _slice = require('./internal/slice');
var _slice2 = _interopRequireDefault(_slice);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
;
/**
* Creates a function which is a composition of the passed asynchronous
* functions. Each function consumes the return value of the function that
* follows. Composing functions `f()`, `g()`, and `h()` would produce the result
* of `f(g(h()))`, only this version uses callbacks to obtain the return values.
*
* Each function is executed with the `this` binding of the composed function.
*
* @name compose
* @static
* @memberOf module:ControlFlow
* @method
* @category Control Flow
* @param {...AsyncFunction} functions - the asynchronous functions to compose
* @returns {Function} an asynchronous function that is the composed
* asynchronous `functions`
* @example
*
* function add1(n, callback) {
* setTimeout(function () {
* callback(null, n + 1);
* }, 10);
* }
*
* function mul3(n, callback) {
* setTimeout(function () {
* callback(null, n * 3);
* }, 10);
* }
*
* var add1mul3 = async.compose(mul3, add1);
* add1mul3(4, function (err, result) {
* // result now equals 15
* });
*/
module.exports = exports['default'];
+43
View File
@@ -0,0 +1,43 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _doLimit = require('./internal/doLimit');
var _doLimit2 = _interopRequireDefault(_doLimit);
var _concatLimit = require('./concatLimit');
var _concatLimit2 = _interopRequireDefault(_concatLimit);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Applies `iteratee` to each item in `coll`, concatenating the results. Returns
* the concatenated list. The `iteratee`s are called in parallel, and the
* results are concatenated as they return. There is no guarantee that the
* results array will be returned in the original order of `coll` passed to the
* `iteratee` function.
*
* @name concat
* @static
* @memberOf module:Collections
* @method
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {AsyncFunction} iteratee - A function to apply to each item in `coll`,
* which should use an array as its result. Invoked with (item, callback).
* @param {Function} [callback(err)] - A callback which is called after all the
* `iteratee` functions have finished, or an error occurs. Results is an array
* containing the concatenated results of the `iteratee` function. Invoked with
* (err, results).
* @example
*
* async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files) {
* // files is now a list of filenames that exist in the 3 directories
* });
*/
exports.default = (0, _doLimit2.default)(_concatLimit2.default, Infinity);
module.exports = exports['default'];
+65
View File
@@ -0,0 +1,65 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function (coll, limit, iteratee, callback) {
callback = callback || _noop2.default;
var _iteratee = (0, _wrapAsync2.default)(iteratee);
(0, _mapLimit2.default)(coll, limit, function (val, callback) {
_iteratee(val, function (err /*, ...args*/) {
if (err) return callback(err);
return callback(null, (0, _slice2.default)(arguments, 1));
});
}, function (err, mapResults) {
var result = [];
for (var i = 0; i < mapResults.length; i++) {
if (mapResults[i]) {
result = _concat.apply(result, mapResults[i]);
}
}
return callback(err, result);
});
};
var _noop = require('lodash/noop');
var _noop2 = _interopRequireDefault(_noop);
var _wrapAsync = require('./internal/wrapAsync');
var _wrapAsync2 = _interopRequireDefault(_wrapAsync);
var _slice = require('./internal/slice');
var _slice2 = _interopRequireDefault(_slice);
var _mapLimit = require('./mapLimit');
var _mapLimit2 = _interopRequireDefault(_mapLimit);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _concat = Array.prototype.concat;
/**
* The same as [`concat`]{@link module:Collections.concat} but runs a maximum of `limit` async operations at a time.
*
* @name concatLimit
* @static
* @memberOf module:Collections
* @method
* @see [async.concat]{@link module:Collections.concat}
* @category Collection
* @param {Array|Iterable|Object} coll - A collection to iterate over.
* @param {number} limit - The maximum number of async operations at a time.
* @param {AsyncFunction} iteratee - A function to apply to each item in `coll`,
* which should use an array as its result. Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called after all the
* `iteratee` functions have finished, or an error occurs. Results is an array
* containing the concatenated results of the `iteratee` function. Invoked with
* (err, results).
*/
module.exports = exports['default'];
Loaded 100 of 7001 files, more files were not shown because too many files have changed in this diff. Show more