Dieses Repository beinhaltet HTML- und Javascript Code zur einer NotizenWebApp auf Basis von Web Storage. Zudem sind Mocha/Chai Tests im Browser enthalten. https://meinenotizen.netlify.app/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. #!/usr/bin/env node
  2. var fs = require('fs'),
  3. connect = require('connect'),
  4. serveIndex = require('serve-index'),
  5. logger = require('morgan'),
  6. WebSocket = require('faye-websocket'),
  7. path = require('path'),
  8. url = require('url'),
  9. http = require('http'),
  10. send = require('send'),
  11. open = require('opn'),
  12. es = require("event-stream"),
  13. os = require('os'),
  14. chokidar = require('chokidar');
  15. require('colors');
  16. var INJECTED_CODE = fs.readFileSync(path.join(__dirname, "injected.html"), "utf8");
  17. var LiveServer = {
  18. server: null,
  19. watcher: null,
  20. logLevel: 2
  21. };
  22. function escape(html){
  23. return String(html)
  24. .replace(/&(?!\w+;)/g, '&')
  25. .replace(/</g, '&lt;')
  26. .replace(/>/g, '&gt;')
  27. .replace(/"/g, '&quot;');
  28. }
  29. // Based on connect.static(), but streamlined and with added code injecter
  30. function staticServer(root) {
  31. var isFile = false;
  32. try { // For supporting mounting files instead of just directories
  33. isFile = fs.statSync(root).isFile();
  34. } catch (e) {
  35. if (e.code !== "ENOENT") throw e;
  36. }
  37. return function(req, res, next) {
  38. if (req.method !== "GET" && req.method !== "HEAD") return next();
  39. var reqpath = isFile ? "" : url.parse(req.url).pathname;
  40. var hasNoOrigin = !req.headers.origin;
  41. var injectCandidates = [ new RegExp("</body>", "i"), new RegExp("</svg>"), new RegExp("</head>", "i")];
  42. var injectTag = null;
  43. function directory() {
  44. var pathname = url.parse(req.originalUrl).pathname;
  45. res.statusCode = 301;
  46. res.setHeader('Location', pathname + '/');
  47. res.end('Redirecting to ' + escape(pathname) + '/');
  48. }
  49. function file(filepath /*, stat*/) {
  50. var x = path.extname(filepath).toLocaleLowerCase(), match,
  51. possibleExtensions = [ "", ".html", ".htm", ".xhtml", ".php", ".svg" ];
  52. if (hasNoOrigin && (possibleExtensions.indexOf(x) > -1)) {
  53. // TODO: Sync file read here is not nice, but we need to determine if the html should be injected or not
  54. var contents = fs.readFileSync(filepath, "utf8");
  55. for (var i = 0; i < injectCandidates.length; ++i) {
  56. match = injectCandidates[i].exec(contents);
  57. if (match) {
  58. injectTag = match[0];
  59. break;
  60. }
  61. }
  62. if (injectTag === null && LiveServer.logLevel >= 3) {
  63. console.warn("Failed to inject refresh script!".yellow,
  64. "Couldn't find any of the tags ", injectCandidates, "from", filepath);
  65. }
  66. }
  67. }
  68. function error(err) {
  69. if (err.status === 404) return next();
  70. next(err);
  71. }
  72. function inject(stream) {
  73. if (injectTag) {
  74. // We need to modify the length given to browser
  75. var len = INJECTED_CODE.length + res.getHeader('Content-Length');
  76. res.setHeader('Content-Length', len);
  77. var originalPipe = stream.pipe;
  78. stream.pipe = function(resp) {
  79. originalPipe.call(stream, es.replace(new RegExp(injectTag, "i"), INJECTED_CODE + injectTag)).pipe(resp);
  80. };
  81. }
  82. }
  83. send(req, reqpath, { root: root })
  84. .on('error', error)
  85. .on('directory', directory)
  86. .on('file', file)
  87. .on('stream', inject)
  88. .pipe(res);
  89. };
  90. }
  91. /**
  92. * Rewrite request URL and pass it back to the static handler.
  93. * @param staticHandler {function} Next handler
  94. * @param file {string} Path to the entry point file
  95. */
  96. function entryPoint(staticHandler, file) {
  97. if (!file) return function(req, res, next) { next(); };
  98. return function(req, res, next) {
  99. req.url = "/" + file;
  100. staticHandler(req, res, next);
  101. };
  102. }
  103. /**
  104. * Start a live server with parameters given as an object
  105. * @param host {string} Address to bind to (default: 0.0.0.0)
  106. * @param port {number} Port number (default: 8080)
  107. * @param root {string} Path to root directory (default: cwd)
  108. * @param watch {array} Paths to exclusively watch for changes
  109. * @param ignore {array} Paths to ignore when watching files for changes
  110. * @param ignorePattern {regexp} Ignore files by RegExp
  111. * @param noCssInject Don't inject CSS changes, just reload as with any other file change
  112. * @param open {(string|string[])} Subpath(s) to open in browser, use false to suppress launch (default: server root)
  113. * @param mount {array} Mount directories onto a route, e.g. [['/components', './node_modules']].
  114. * @param logLevel {number} 0 = errors only, 1 = some, 2 = lots
  115. * @param file {string} Path to the entry point file
  116. * @param wait {number} Server will wait for all changes, before reloading
  117. * @param htpasswd {string} Path to htpasswd file to enable HTTP Basic authentication
  118. * @param middleware {array} Append middleware to stack, e.g. [function(req, res, next) { next(); }].
  119. */
  120. LiveServer.start = function(options) {
  121. options = options || {};
  122. var host = options.host || '0.0.0.0';
  123. var port = options.port !== undefined ? options.port : 8080; // 0 means random
  124. var root = options.root || process.cwd();
  125. var mount = options.mount || [];
  126. var watchPaths = options.watch || [root];
  127. LiveServer.logLevel = options.logLevel === undefined ? 2 : options.logLevel;
  128. var openPath = (options.open === undefined || options.open === true) ?
  129. "" : ((options.open === null || options.open === false) ? null : options.open);
  130. if (options.noBrowser) openPath = null; // Backwards compatibility with 0.7.0
  131. var file = options.file;
  132. var staticServerHandler = staticServer(root);
  133. var wait = options.wait === undefined ? 100 : options.wait;
  134. var browser = options.browser || null;
  135. var htpasswd = options.htpasswd || null;
  136. var cors = options.cors || false;
  137. var https = options.https || null;
  138. var proxy = options.proxy || [];
  139. var middleware = options.middleware || [];
  140. var noCssInject = options.noCssInject;
  141. var httpsModule = options.httpsModule;
  142. if (httpsModule) {
  143. try {
  144. require.resolve(httpsModule);
  145. } catch (e) {
  146. console.error(("HTTPS module \"" + httpsModule + "\" you've provided was not found.").red);
  147. console.error("Did you do", "\"npm install " + httpsModule + "\"?");
  148. return;
  149. }
  150. } else {
  151. httpsModule = "https";
  152. }
  153. // Setup a web server
  154. var app = connect();
  155. // Add logger. Level 2 logs only errors
  156. if (LiveServer.logLevel === 2) {
  157. app.use(logger('dev', {
  158. skip: function (req, res) { return res.statusCode < 400; }
  159. }));
  160. // Level 2 or above logs all requests
  161. } else if (LiveServer.logLevel > 2) {
  162. app.use(logger('dev'));
  163. }
  164. if (options.spa) {
  165. middleware.push("spa");
  166. }
  167. // Add middleware
  168. middleware.map(function(mw) {
  169. if (typeof mw === "string") {
  170. var ext = path.extname(mw).toLocaleLowerCase();
  171. if (ext !== ".js") {
  172. mw = require(path.join(__dirname, "middleware", mw + ".js"));
  173. } else {
  174. mw = require(mw);
  175. }
  176. }
  177. app.use(mw);
  178. });
  179. // Use http-auth if configured
  180. if (htpasswd !== null) {
  181. var auth = require('http-auth');
  182. var basic = auth.basic({
  183. realm: "Please authorize",
  184. file: htpasswd
  185. });
  186. app.use(auth.connect(basic));
  187. }
  188. if (cors) {
  189. app.use(require("cors")({
  190. origin: true, // reflecting request origin
  191. credentials: true // allowing requests with credentials
  192. }));
  193. }
  194. mount.forEach(function(mountRule) {
  195. var mountPath = path.resolve(process.cwd(), mountRule[1]);
  196. if (!options.watch) // Auto add mount paths to wathing but only if exclusive path option is not given
  197. watchPaths.push(mountPath);
  198. app.use(mountRule[0], staticServer(mountPath));
  199. if (LiveServer.logLevel >= 1)
  200. console.log('Mapping %s to "%s"', mountRule[0], mountPath);
  201. });
  202. proxy.forEach(function(proxyRule) {
  203. var proxyOpts = url.parse(proxyRule[1]);
  204. proxyOpts.via = true;
  205. proxyOpts.preserveHost = true;
  206. app.use(proxyRule[0], require('proxy-middleware')(proxyOpts));
  207. if (LiveServer.logLevel >= 1)
  208. console.log('Mapping %s to "%s"', proxyRule[0], proxyRule[1]);
  209. });
  210. app.use(staticServerHandler) // Custom static server
  211. .use(entryPoint(staticServerHandler, file))
  212. .use(serveIndex(root, { icons: true }));
  213. var server, protocol;
  214. if (https !== null) {
  215. var httpsConfig = https;
  216. if (typeof https === "string") {
  217. httpsConfig = require(path.resolve(process.cwd(), https));
  218. }
  219. server = require(httpsModule).createServer(httpsConfig, app);
  220. protocol = "https";
  221. } else {
  222. server = http.createServer(app);
  223. protocol = "http";
  224. }
  225. // Handle server startup errors
  226. server.addListener('error', function(e) {
  227. if (e.code === 'EADDRINUSE') {
  228. var serveURL = protocol + '://' + host + ':' + port;
  229. console.log('%s is already in use. Trying another port.'.yellow, serveURL);
  230. setTimeout(function() {
  231. server.listen(0, host);
  232. }, 1000);
  233. } else {
  234. console.error(e.toString().red);
  235. LiveServer.shutdown();
  236. }
  237. });
  238. // Handle successful server
  239. server.addListener('listening', function(/*e*/) {
  240. LiveServer.server = server;
  241. var address = server.address();
  242. var serveHost = address.address === "0.0.0.0" ? "127.0.0.1" : address.address;
  243. var openHost = host === "0.0.0.0" ? "127.0.0.1" : host;
  244. var serveURL = protocol + '://' + serveHost + ':' + address.port;
  245. var openURL = protocol + '://' + openHost + ':' + address.port;
  246. var serveURLs = [ serveURL ];
  247. if (LiveServer.logLevel > 2 && address.address === "0.0.0.0") {
  248. var ifaces = os.networkInterfaces();
  249. serveURLs = Object.keys(ifaces)
  250. .map(function(iface) {
  251. return ifaces[iface];
  252. })
  253. // flatten address data, use only IPv4
  254. .reduce(function(data, addresses) {
  255. addresses.filter(function(addr) {
  256. return addr.family === "IPv4";
  257. }).forEach(function(addr) {
  258. data.push(addr);
  259. });
  260. return data;
  261. }, [])
  262. .map(function(addr) {
  263. return protocol + "://" + addr.address + ":" + address.port;
  264. });
  265. }
  266. // Output
  267. if (LiveServer.logLevel >= 1) {
  268. if (serveURL === openURL)
  269. if (serveURLs.length === 1) {
  270. console.log(("Serving \"%s\" at %s").green, root, serveURLs[0]);
  271. } else {
  272. console.log(("Serving \"%s\" at\n\t%s").green, root, serveURLs.join("\n\t"));
  273. }
  274. else
  275. console.log(("Serving \"%s\" at %s (%s)").green, root, openURL, serveURL);
  276. }
  277. // Launch browser
  278. if (openPath !== null)
  279. if (typeof openPath === "object") {
  280. openPath.forEach(function(p) {
  281. open(openURL + p, {app: browser});
  282. });
  283. } else {
  284. open(openURL + openPath, {app: browser});
  285. }
  286. });
  287. // Setup server to listen at port
  288. server.listen(port, host);
  289. // WebSocket
  290. var clients = [];
  291. server.addListener('upgrade', function(request, socket, head) {
  292. var ws = new WebSocket(request, socket, head);
  293. ws.onopen = function() { ws.send('connected'); };
  294. if (wait > 0) {
  295. (function() {
  296. var wssend = ws.send;
  297. var waitTimeout;
  298. ws.send = function() {
  299. var args = arguments;
  300. if (waitTimeout) clearTimeout(waitTimeout);
  301. waitTimeout = setTimeout(function(){
  302. wssend.apply(ws, args);
  303. }, wait);
  304. };
  305. })();
  306. }
  307. ws.onclose = function() {
  308. clients = clients.filter(function (x) {
  309. return x !== ws;
  310. });
  311. };
  312. clients.push(ws);
  313. });
  314. var ignored = [
  315. function(testPath) { // Always ignore dotfiles (important e.g. because editor hidden temp files)
  316. return testPath !== "." && /(^[.#]|(?:__|~)$)/.test(path.basename(testPath));
  317. }
  318. ];
  319. if (options.ignore) {
  320. ignored = ignored.concat(options.ignore);
  321. }
  322. if (options.ignorePattern) {
  323. ignored.push(options.ignorePattern);
  324. }
  325. // Setup file watcher
  326. LiveServer.watcher = chokidar.watch(watchPaths, {
  327. ignored: ignored,
  328. ignoreInitial: true
  329. });
  330. function handleChange(changePath) {
  331. var cssChange = path.extname(changePath) === ".css" && !noCssInject;
  332. if (LiveServer.logLevel >= 1) {
  333. if (cssChange)
  334. console.log("CSS change detected".magenta, changePath);
  335. else console.log("Change detected".cyan, changePath);
  336. }
  337. clients.forEach(function(ws) {
  338. if (ws)
  339. ws.send(cssChange ? 'refreshcss' : 'reload');
  340. });
  341. }
  342. LiveServer.watcher
  343. .on("change", handleChange)
  344. .on("add", handleChange)
  345. .on("unlink", handleChange)
  346. .on("addDir", handleChange)
  347. .on("unlinkDir", handleChange)
  348. .on("ready", function () {
  349. if (LiveServer.logLevel >= 1)
  350. console.log("Ready for changes".cyan);
  351. })
  352. .on("error", function (err) {
  353. console.log("ERROR:".red, err);
  354. });
  355. return server;
  356. };
  357. LiveServer.shutdown = function() {
  358. var watcher = LiveServer.watcher;
  359. if (watcher) {
  360. watcher.close();
  361. }
  362. var server = LiveServer.server;
  363. if (server)
  364. server.close();
  365. };
  366. module.exports = LiveServer;