(function () { /* Imports */ var Meteor = Package.meteor.Meteor; var Log = Package.logging.Log; var _ = Package.underscore._; var RoutePolicy = Package.routepolicy.RoutePolicy; var Boilerplate = Package['boilerplate-generator'].Boilerplate; var WebAppHashing = Package['webapp-hashing'].WebAppHashing; /* Package-scope variables */ var WebApp, main, WebAppInternals; (function () { ////////////////////////////////////////////////////////////////////////////////////// // // // packages/webapp/webapp_server.js // // // ////////////////////////////////////////////////////////////////////////////////////// // ////////// Requires ////////// // 1 // 2 var fs = Npm.require("fs"); // 3 var http = Npm.require("http"); // 4 var os = Npm.require("os"); // 5 var path = Npm.require("path"); // 6 var url = Npm.require("url"); // 7 var crypto = Npm.require("crypto"); // 8 // 9 var connect = Npm.require('connect'); // 10 var useragent = Npm.require('useragent'); // 11 var send = Npm.require('send'); // 12 // 13 var Future = Npm.require('fibers/future'); // 14 var Fiber = Npm.require('fibers'); // 15 // 16 var SHORT_SOCKET_TIMEOUT = 5*1000; // 17 var LONG_SOCKET_TIMEOUT = 120*1000; // 18 // 19 WebApp = {}; // 20 WebAppInternals = {}; // 21 // 22 WebAppInternals.NpmModules = { // 23 connect: { // 24 version: Npm.require('connect/package.json').version, // 25 module: connect // 26 } // 27 }; // 28 // 29 WebApp.defaultArch = 'web.browser'; // 30 // 31 // XXX maps archs to manifests // 32 WebApp.clientPrograms = {}; // 33 // 34 // XXX maps archs to program path on filesystem // 35 var archPath = {}; // 36 // 37 var bundledJsCssPrefix; // 38 // 39 var sha1 = function (contents) { // 40 var hash = crypto.createHash('sha1'); // 41 hash.update(contents); // 42 return hash.digest('hex'); // 43 }; // 44 // 45 var readUtf8FileSync = function (filename) { // 46 return Meteor.wrapAsync(fs.readFile)(filename, 'utf8'); // 47 }; // 48 // 49 // #BrowserIdentification // 50 // // 51 // We have multiple places that want to identify the browser: the // 52 // unsupported browser page, the appcache package, and, eventually // 53 // delivering browser polyfills only as needed. // 54 // // 55 // To avoid detecting the browser in multiple places ad-hoc, we create a // 56 // Meteor "browser" object. It uses but does not expose the npm // 57 // useragent module (we could choose a different mechanism to identify // 58 // the browser in the future if we wanted to). The browser object // 59 // contains // 60 // // 61 // * `name`: the name of the browser in camel case // 62 // * `major`, `minor`, `patch`: integers describing the browser version // 63 // // 64 // Also here is an early version of a Meteor `request` object, intended // 65 // to be a high-level description of the request without exposing // 66 // details of connect's low-level `req`. Currently it contains: // 67 // // 68 // * `browser`: browser identification object described above // 69 // * `url`: parsed url, including parsed query params // 70 // // 71 // As a temporary hack there is a `categorizeRequest` function on WebApp which // 72 // converts a connect `req` to a Meteor `request`. This can go away once smart // 73 // packages such as appcache are being passed a `request` object directly when // 74 // they serve content. // 75 // // 76 // This allows `request` to be used uniformly: it is passed to the html // 77 // attributes hook, and the appcache package can use it when deciding // 78 // whether to generate a 404 for the manifest. // 79 // // 80 // Real routing / server side rendering will probably refactor this // 81 // heavily. // 82 // 83 // 84 // e.g. "Mobile Safari" => "mobileSafari" // 85 var camelCase = function (name) { // 86 var parts = name.split(' '); // 87 parts[0] = parts[0].toLowerCase(); // 88 for (var i = 1; i < parts.length; ++i) { // 89 parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substr(1); // 90 } // 91 return parts.join(''); // 92 }; // 93 // 94 var identifyBrowser = function (userAgentString) { // 95 var userAgent = useragent.lookup(userAgentString); // 96 return { // 97 name: camelCase(userAgent.family), // 98 major: +userAgent.major, // 99 minor: +userAgent.minor, // 100 patch: +userAgent.patch // 101 }; // 102 }; // 103 // 104 // XXX Refactor as part of implementing real routing. // 105 WebAppInternals.identifyBrowser = identifyBrowser; // 106 // 107 WebApp.categorizeRequest = function (req) { // 108 return { // 109 browser: identifyBrowser(req.headers['user-agent']), // 110 url: url.parse(req.url, true) // 111 }; // 112 }; // 113 // 114 // HTML attribute hooks: functions to be called to determine any attributes to // 115 // be added to the '' tag. Each function is passed a 'request' object (see // 116 // #BrowserIdentification) and should return null or object. // 117 var htmlAttributeHooks = []; // 118 var getHtmlAttributes = function (request) { // 119 var combinedAttributes = {}; // 120 _.each(htmlAttributeHooks || [], function (hook) { // 121 var attributes = hook(request); // 122 if (attributes === null) // 123 return; // 124 if (typeof attributes !== 'object') // 125 throw Error("HTML attribute hook must return null or object"); // 126 _.extend(combinedAttributes, attributes); // 127 }); // 128 return combinedAttributes; // 129 }; // 130 WebApp.addHtmlAttributeHook = function (hook) { // 131 htmlAttributeHooks.push(hook); // 132 }; // 133 // 134 // Serve app HTML for this URL? // 135 var appUrl = function (url) { // 136 if (url === '/favicon.ico' || url === '/robots.txt') // 137 return false; // 138 // 139 // NOTE: app.manifest is not a web standard like favicon.ico and // 140 // robots.txt. It is a file name we have chosen to use for HTML5 // 141 // appcache URLs. It is included here to prevent using an appcache // 142 // then removing it from poisoning an app permanently. Eventually, // 143 // once we have server side routing, this won't be needed as // 144 // unknown URLs with return a 404 automatically. // 145 if (url === '/app.manifest') // 146 return false; // 147 // 148 // Avoid serving app HTML for declared routes such as /sockjs/. // 149 if (RoutePolicy.classify(url)) // 150 return false; // 151 // 152 // we currently return app HTML on all URLs by default // 153 return true; // 154 }; // 155 // 156 // 157 // We need to calculate the client hash after all packages have loaded // 158 // to give them a chance to populate __meteor_runtime_config__. // 159 // // 160 // Calculating the hash during startup means that packages can only // 161 // populate __meteor_runtime_config__ during load, not during startup. // 162 // // 163 // Calculating instead it at the beginning of main after all startup // 164 // hooks had run would allow packages to also populate // 165 // __meteor_runtime_config__ during startup, but that's too late for // 166 // autoupdate because it needs to have the client hash at startup to // 167 // insert the auto update version itself into // 168 // __meteor_runtime_config__ to get it to the client. // 169 // // 170 // An alternative would be to give autoupdate a "post-start, // 171 // pre-listen" hook to allow it to insert the auto update version at // 172 // the right moment. // 173 // 174 Meteor.startup(function () { // 175 var calculateClientHash = WebAppHashing.calculateClientHash; // 176 WebApp.clientHash = function (archName) { // 177 archName = archName || WebApp.defaultArch; // 178 return calculateClientHash(WebApp.clientPrograms[archName].manifest); // 179 }; // 180 // 181 WebApp.calculateClientHashRefreshable = function (archName) { // 182 archName = archName || WebApp.defaultArch; // 183 return calculateClientHash(WebApp.clientPrograms[archName].manifest, // 184 function (name) { // 185 return name === "css"; // 186 }); // 187 }; // 188 WebApp.calculateClientHashNonRefreshable = function (archName) { // 189 archName = archName || WebApp.defaultArch; // 190 return calculateClientHash(WebApp.clientPrograms[archName].manifest, // 191 function (name) { // 192 return name !== "css"; // 193 }); // 194 }; // 195 WebApp.calculateClientHashCordova = function () { // 196 var archName = 'web.cordova'; // 197 if (! WebApp.clientPrograms[archName]) // 198 return 'none'; // 199 // 200 return calculateClientHash( // 201 WebApp.clientPrograms[archName].manifest, null, _.pick( // 202 __meteor_runtime_config__, 'PUBLIC_SETTINGS')); // 203 }; // 204 }); // 205 // 206 // 207 // 208 // When we have a request pending, we want the socket timeout to be long, to // 209 // give ourselves a while to serve it, and to allow sockjs long polls to // 210 // complete. On the other hand, we want to close idle sockets relatively // 211 // quickly, so that we can shut down relatively promptly but cleanly, without // 212 // cutting off anyone's response. // 213 WebApp._timeoutAdjustmentRequestCallback = function (req, res) { // 214 // this is really just req.socket.setTimeout(LONG_SOCKET_TIMEOUT); // 215 req.setTimeout(LONG_SOCKET_TIMEOUT); // 216 // Insert our new finish listener to run BEFORE the existing one which removes // 217 // the response from the socket. // 218 var finishListeners = res.listeners('finish'); // 219 // XXX Apparently in Node 0.12 this event is now called 'prefinish'. // 220 // https://github.com/joyent/node/commit/7c9b6070 // 221 res.removeAllListeners('finish'); // 222 res.on('finish', function () { // 223 res.setTimeout(SHORT_SOCKET_TIMEOUT); // 224 }); // 225 _.each(finishListeners, function (l) { res.on('finish', l); }); // 226 }; // 227 // 228 // 229 // Will be updated by main before we listen. // 230 // Map from client arch to boilerplate object. // 231 // Boilerplate object has: // 232 // - func: XXX // 233 // - baseData: XXX // 234 var boilerplateByArch = {}; // 235 // 236 // Given a request (as returned from `categorizeRequest`), return the // 237 // boilerplate HTML to serve for that request. Memoizes on HTML // 238 // attributes (used by, eg, appcache) and whether inline scripts are // 239 // currently allowed. // 240 // XXX so far this function is always called with arch === 'web.browser' // 241 var memoizedBoilerplate = {}; // 242 var getBoilerplate = function (request, arch) { // 243 // 244 var htmlAttributes = getHtmlAttributes(request); // 245 // 246 // The only thing that changes from request to request (for now) are // 247 // the HTML attributes (used by, eg, appcache) and whether inline // 248 // scripts are allowed, so we can memoize based on that. // 249 var memHash = JSON.stringify({ // 250 inlineScriptsAllowed: inlineScriptsAllowed, // 251 htmlAttributes: htmlAttributes, // 252 arch: arch // 253 }); // 254 // 255 if (! memoizedBoilerplate[memHash]) { // 256 memoizedBoilerplate[memHash] = boilerplateByArch[arch].toHTML({ // 257 htmlAttributes: htmlAttributes // 258 }); // 259 } // 260 return memoizedBoilerplate[memHash]; // 261 }; // 262 // 263 WebAppInternals.generateBoilerplateInstance = function (arch, // 264 manifest, // 265 additionalOptions) { // 266 additionalOptions = additionalOptions || {}; // 267 // 268 var runtimeConfig = _.extend( // 269 _.clone(__meteor_runtime_config__), // 270 additionalOptions.runtimeConfigOverrides || {} // 271 ); // 272 // 273 var jsCssPrefix; // 274 if (arch === 'web.cordova') { // 275 // in cordova we serve assets up directly from disk so it doesn't make // 276 // sense to use the prefix (ordinarily something like a CDN) and go out // 277 // to the internet for those files. // 278 jsCssPrefix = ''; // 279 } else { // 280 jsCssPrefix = bundledJsCssPrefix || // 281 __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ''; // 282 } // 283 // 284 return new Boilerplate(arch, manifest, // 285 _.extend({ // 286 pathMapper: function (itemPath) { // 287 return path.join(archPath[arch], itemPath); }, // 288 baseDataExtension: { // 289 additionalStaticJs: _.map( // 290 additionalStaticJs || [], // 291 function (contents, pathname) { // 292 return { // 293 pathname: pathname, // 294 contents: contents // 295 }; // 296 } // 297 ), // 298 // Convert to a JSON string, then get rid of most weird characters, then // 299 // wrap in double quotes. (The outermost JSON.stringify really ought to // 300 // just be "wrap in double quotes" but we use it to be safe.) This might // 301 // end up inside a ", but normal {{spacebars}} escaping escapes too much! See // 303 // https://github.com/meteor/meteor/issues/3730 // 304 meteorRuntimeConfig: JSON.stringify( // 305 encodeURIComponent(JSON.stringify(runtimeConfig))), // 306 rootUrlPathPrefix: __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '', // 307 bundledJsCssPrefix: jsCssPrefix, // 308 inlineScriptsAllowed: WebAppInternals.inlineScriptsAllowed(), // 309 inline: additionalOptions.inline // 310 } // 311 }, additionalOptions) // 312 ); // 313 }; // 314 // 315 // A mapping from url path to "info". Where "info" has the following fields: // 316 // - type: the type of file to be served // 317 // - cacheable: optionally, whether the file should be cached or not // 318 // - sourceMapUrl: optionally, the url of the source map // 319 // // 320 // Info also contains one of the following: // 321 // - content: the stringified content that should be served at this path // 322 // - absolutePath: the absolute path on disk to the file // 323 // 324 var staticFiles; // 325 // 326 // Serve static files from the manifest or added with // 327 // `addStaticJs`. Exported for tests. // 328 WebAppInternals.staticFilesMiddleware = function (staticFiles, req, res, next) { // 329 if ('GET' != req.method && 'HEAD' != req.method) { // 330 next(); // 331 return; // 332 } // 333 var pathname = connect.utils.parseUrl(req).pathname; // 334 try { // 335 pathname = decodeURIComponent(pathname); // 336 } catch (e) { // 337 next(); // 338 return; // 339 } // 340 // 341 var serveStaticJs = function (s) { // 342 res.writeHead(200, { // 343 'Content-type': 'application/javascript; charset=UTF-8' // 344 }); // 345 res.write(s); // 346 res.end(); // 347 }; // 348 // 349 if (pathname === "/meteor_runtime_config.js" && // 350 ! WebAppInternals.inlineScriptsAllowed()) { // 351 serveStaticJs("__meteor_runtime_config__ = " + // 352 JSON.stringify(__meteor_runtime_config__) + ";"); // 353 return; // 354 } else if (_.has(additionalStaticJs, pathname) && // 355 ! WebAppInternals.inlineScriptsAllowed()) { // 356 serveStaticJs(additionalStaticJs[pathname]); // 357 return; // 358 } // 359 // 360 if (!_.has(staticFiles, pathname)) { // 361 next(); // 362 return; // 363 } // 364 // 365 // We don't need to call pause because, unlike 'static', once we call into // 366 // 'send' and yield to the event loop, we never call another handler with // 367 // 'next'. // 368 // 369 var info = staticFiles[pathname]; // 370 // 371 // Cacheable files are files that should never change. Typically // 372 // named by their hash (eg meteor bundled js and css files). // 373 // We cache them ~forever (1yr). // 374 // // 375 // We cache non-cacheable files anyway. This isn't really correct, as users // 376 // can change the files and changes won't propagate immediately. However, if // 377 // we don't cache them, browsers will 'flicker' when rerendering // 378 // images. Eventually we will probably want to rewrite URLs of static assets // 379 // to include a query parameter to bust caches. That way we can both get // 380 // good caching behavior and allow users to change assets without delay. // 381 // https://github.com/meteor/meteor/issues/773 // 382 var maxAge = info.cacheable // 383 ? 1000 * 60 * 60 * 24 * 365 // 384 : 1000 * 60 * 60 * 24; // 385 // 386 // Set the X-SourceMap header, which current Chrome, FireFox, and Safari // 387 // understand. (The SourceMap header is slightly more spec-correct but FF // 388 // doesn't understand it.) // 389 // // 390 // You may also need to enable source maps in Chrome: open dev tools, click // 391 // the gear in the bottom right corner, and select "enable source maps". // 392 if (info.sourceMapUrl) { // 393 res.setHeader('X-SourceMap', // 394 __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + // 395 info.sourceMapUrl); // 396 } // 397 // 398 if (info.type === "js") { // 399 res.setHeader("Content-Type", "application/javascript; charset=UTF-8"); // 400 } else if (info.type === "css") { // 401 res.setHeader("Content-Type", "text/css; charset=UTF-8"); // 402 } else if (info.type === "json") { // 403 res.setHeader("Content-Type", "application/json; charset=UTF-8"); // 404 // XXX if it is a manifest we are serving, set additional headers // 405 if (/\/manifest.json$/.test(pathname)) { // 406 res.setHeader("Access-Control-Allow-Origin", "*"); // 407 } // 408 } // 409 // 410 if (info.content) { // 411 res.write(info.content); // 412 res.end(); // 413 } else { // 414 send(req, info.absolutePath) // 415 .maxage(maxAge) // 416 .hidden(true) // if we specified a dotfile in the manifest, serve it // 417 .on('error', function (err) { // 418 Log.error("Error serving static file " + err); // 419 res.writeHead(500); // 420 res.end(); // 421 }) // 422 .on('directory', function () { // 423 Log.error("Unexpected directory " + info.absolutePath); // 424 res.writeHead(500); // 425 res.end(); // 426 }) // 427 .pipe(res); // 428 } // 429 }; // 430 // 431 var getUrlPrefixForArch = function (arch) { // 432 // XXX we rely on the fact that arch names don't contain slashes // 433 // in that case we would need to uri escape it // 434 // 435 // We add '__' to the beginning of non-standard archs to "scope" the url // 436 // to Meteor internals. // 437 return arch === WebApp.defaultArch ? // 438 '' : '/' + '__' + arch.replace(/^web\./, ''); // 439 }; // 440 // 441 var runWebAppServer = function () { // 442 var shuttingDown = false; // 443 var syncQueue = new Meteor._SynchronousQueue(); // 444 // 445 var getItemPathname = function (itemUrl) { // 446 return decodeURIComponent(url.parse(itemUrl).pathname); // 447 }; // 448 // 449 WebAppInternals.reloadClientPrograms = function () { // 450 syncQueue.runTask(function() { // 451 staticFiles = {}; // 452 var generateClientProgram = function (clientPath, arch) { // 453 // read the control for the client we'll be serving up // 454 var clientJsonPath = path.join(__meteor_bootstrap__.serverDir, // 455 clientPath); // 456 var clientDir = path.dirname(clientJsonPath); // 457 var clientJson = JSON.parse(readUtf8FileSync(clientJsonPath)); // 458 if (clientJson.format !== "web-program-pre1") // 459 throw new Error("Unsupported format for client assets: " + // 460 JSON.stringify(clientJson.format)); // 461 // 462 if (! clientJsonPath || ! clientDir || ! clientJson) // 463 throw new Error("Client config file not parsed."); // 464 // 465 var urlPrefix = getUrlPrefixForArch(arch); // 466 // 467 var manifest = clientJson.manifest; // 468 _.each(manifest, function (item) { // 469 if (item.url && item.where === "client") { // 470 staticFiles[urlPrefix + getItemPathname(item.url)] = { // 471 absolutePath: path.join(clientDir, item.path), // 472 cacheable: item.cacheable, // 473 // Link from source to its map // 474 sourceMapUrl: item.sourceMapUrl, // 475 type: item.type // 476 }; // 477 // 478 if (item.sourceMap) { // 479 // Serve the source map too, under the specified URL. We assume all // 480 // source maps are cacheable. // 481 staticFiles[urlPrefix + getItemPathname(item.sourceMapUrl)] = { // 482 absolutePath: path.join(clientDir, item.sourceMap), // 483 cacheable: true // 484 }; // 485 } // 486 } // 487 }); // 488 // 489 var program = { // 490 manifest: manifest, // 491 version: WebAppHashing.calculateClientHash(manifest, null, _.pick( // 492 __meteor_runtime_config__, 'PUBLIC_SETTINGS')), // 493 PUBLIC_SETTINGS: __meteor_runtime_config__.PUBLIC_SETTINGS // 494 }; // 495 // 496 WebApp.clientPrograms[arch] = program; // 497 // 498 // Serve the program as a string at /foo//manifest.json // 499 // XXX change manifest.json -> program.json // 500 staticFiles[path.join(urlPrefix, 'manifest.json')] = { // 501 content: JSON.stringify(program), // 502 cacheable: true, // 503 type: "json" // 504 }; // 505 }; // 506 // 507 try { // 508 var clientPaths = __meteor_bootstrap__.configJson.clientPaths; // 509 _.each(clientPaths, function (clientPath, arch) { // 510 archPath[arch] = path.dirname(clientPath); // 511 generateClientProgram(clientPath, arch); // 512 }); // 513 // 514 // Exported for tests. // 515 WebAppInternals.staticFiles = staticFiles; // 516 } catch (e) { // 517 Log.error("Error reloading the client program: " + e.stack); // 518 process.exit(1); // 519 } // 520 }); // 521 }; // 522 // 523 WebAppInternals.generateBoilerplate = function () { // 524 // This boilerplate will be served to the mobile devices when used with // 525 // Meteor/Cordova for the Hot-Code Push and since the file will be served by // 526 // the device's server, it is important to set the DDP url to the actual // 527 // Meteor server accepting DDP connections and not the device's file server. // 528 var defaultOptionsForArch = { // 529 'web.cordova': { // 530 runtimeConfigOverrides: { // 531 // XXX We use absoluteUrl() here so that we serve https:// // 532 // URLs to cordova clients if force-ssl is in use. If we were // 533 // to use __meteor_runtime_config__.ROOT_URL instead of // 534 // absoluteUrl(), then Cordova clients would immediately get a // 535 // HCP setting their DDP_DEFAULT_CONNECTION_URL to // 536 // http://example.meteor.com. This breaks the app, because // 537 // force-ssl doesn't serve CORS headers on 302 // 538 // redirects. (Plus it's undesirable to have clients // 539 // connecting to http://example.meteor.com when force-ssl is // 540 // in use.) // 541 DDP_DEFAULT_CONNECTION_URL: process.env.MOBILE_DDP_URL || // 542 Meteor.absoluteUrl(), // 543 ROOT_URL: process.env.MOBILE_ROOT_URL || // 544 Meteor.absoluteUrl() // 545 } // 546 } // 547 }; // 548 // 549 syncQueue.runTask(function() { // 550 _.each(WebApp.clientPrograms, function (program, archName) { // 551 boilerplateByArch[archName] = // 552 WebAppInternals.generateBoilerplateInstance( // 553 archName, program.manifest, // 554 defaultOptionsForArch[archName]); // 555 }); // 556 // 557 // Clear the memoized boilerplate cache. // 558 memoizedBoilerplate = {}; // 559 // 560 // Configure CSS injection for the default arch // 561 // XXX implement the CSS injection for all archs? // 562 WebAppInternals.refreshableAssets = { // 563 allCss: boilerplateByArch[WebApp.defaultArch].baseData.css // 564 }; // 565 }); // 566 }; // 567 // 568 WebAppInternals.reloadClientPrograms(); // 569 // 570 // webserver // 571 var app = connect(); // 572 // 573 // Auto-compress any json, javascript, or text. // 574 app.use(connect.compress()); // 575 // 576 // Packages and apps can add handlers that run before any other Meteor // 577 // handlers via WebApp.rawConnectHandlers. // 578 var rawConnectHandlers = connect(); // 579 app.use(rawConnectHandlers); // 580 // 581 // We're not a proxy; reject (without crashing) attempts to treat us like // 582 // one. (See #1212.) // 583 app.use(function(req, res, next) { // 584 if (RoutePolicy.isValidUrl(req.url)) { // 585 next(); // 586 return; // 587 } // 588 res.writeHead(400); // 589 res.write("Not a proxy"); // 590 res.end(); // 591 }); // 592 // 593 // Strip off the path prefix, if it exists. // 594 app.use(function (request, response, next) { // 595 var pathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX; // 596 var url = Npm.require('url').parse(request.url); // 597 var pathname = url.pathname; // 598 // check if the path in the url starts with the path prefix (and the part // 599 // after the path prefix must start with a / if it exists.) // 600 if (pathPrefix && pathname.substring(0, pathPrefix.length) === pathPrefix && // 601 (pathname.length == pathPrefix.length // 602 || pathname.substring(pathPrefix.length, pathPrefix.length + 1) === "/")) { // 603 request.url = request.url.substring(pathPrefix.length); // 604 next(); // 605 } else if (pathname === "/favicon.ico" || pathname === "/robots.txt") { // 606 next(); // 607 } else if (pathPrefix) { // 608 response.writeHead(404); // 609 response.write("Unknown path"); // 610 response.end(); // 611 } else { // 612 next(); // 613 } // 614 }); // 615 // 616 // Parse the query string into res.query. Used by oauth_server, but it's // 617 // generally pretty handy.. // 618 app.use(connect.query()); // 619 // 620 // Serve static files from the manifest. // 621 // This is inspired by the 'static' middleware. // 622 app.use(function (req, res, next) { // 623 Fiber(function () { // 624 WebAppInternals.staticFilesMiddleware(staticFiles, req, res, next); // 625 }).run(); // 626 }); // 627 // 628 // Packages and apps can add handlers to this via WebApp.connectHandlers. // 629 // They are inserted before our default handler. // 630 var packageAndAppHandlers = connect(); // 631 app.use(packageAndAppHandlers); // 632 // 633 var suppressConnectErrors = false; // 634 // connect knows it is an error handler because it has 4 arguments instead of // 635 // 3. go figure. (It is not smart enough to find such a thing if it's hidden // 636 // inside packageAndAppHandlers.) // 637 app.use(function (err, req, res, next) { // 638 if (!err || !suppressConnectErrors || !req.headers['x-suppress-error']) { // 639 next(err); // 640 return; // 641 } // 642 res.writeHead(err.status, { 'Content-Type': 'text/plain' }); // 643 res.end("An error message"); // 644 }); // 645 // 646 app.use(function (req, res, next) { // 647 if (! appUrl(req.url)) // 648 return next(); // 649 // 650 var headers = { // 651 'Content-Type': 'text/html; charset=utf-8' // 652 }; // 653 if (shuttingDown) // 654 headers['Connection'] = 'Close'; // 655 // 656 var request = WebApp.categorizeRequest(req); // 657 // 658 if (request.url.query && request.url.query['meteor_css_resource']) { // 659 // In this case, we're requesting a CSS resource in the meteor-specific // 660 // way, but we don't have it. Serve a static css file that indicates that // 661 // we didn't have it, so we can detect that and refresh. // 662 headers['Content-Type'] = 'text/css; charset=utf-8'; // 663 res.writeHead(200, headers); // 664 res.write(".meteor-css-not-found-error { width: 0px;}"); // 665 res.end(); // 666 return undefined; // 667 } // 668 // 669 // /packages/asdfsad ... /__cordova/dafsdf.js // 670 var pathname = connect.utils.parseUrl(req).pathname; // 671 var archKey = pathname.split('/')[1]; // 672 var archKeyCleaned = 'web.' + archKey.replace(/^__/, ''); // 673 // 674 if (! /^__/.test(archKey) || ! _.has(archPath, archKeyCleaned)) { // 675 archKey = WebApp.defaultArch; // 676 } else { // 677 archKey = archKeyCleaned; // 678 } // 679 // 680 var boilerplate; // 681 try { // 682 boilerplate = getBoilerplate(request, archKey); // 683 } catch (e) { // 684 Log.error("Error running template: " + e); // 685 res.writeHead(500, headers); // 686 res.end(); // 687 return undefined; // 688 } // 689 // 690 res.writeHead(200, headers); // 691 res.write(boilerplate); // 692 res.end(); // 693 return undefined; // 694 }); // 695 // 696 // Return 404 by default, if no other handlers serve this URL. // 697 app.use(function (req, res) { // 698 res.writeHead(404); // 699 res.end(); // 700 }); // 701 // 702 // 703 var httpServer = http.createServer(app); // 704 var onListeningCallbacks = []; // 705 // 706 // After 5 seconds w/o data on a socket, kill it. On the other hand, if // 707 // there's an outstanding request, give it a higher timeout instead (to avoid // 708 // killing long-polling requests) // 709 httpServer.setTimeout(SHORT_SOCKET_TIMEOUT); // 710 // 711 // Do this here, and then also in livedata/stream_server.js, because // 712 // stream_server.js kills all the current request handlers when installing its // 713 // own. // 714 httpServer.on('request', WebApp._timeoutAdjustmentRequestCallback); // 715 // 716 // 717 // start up app // 718 _.extend(WebApp, { // 719 connectHandlers: packageAndAppHandlers, // 720 rawConnectHandlers: rawConnectHandlers, // 721 httpServer: httpServer, // 722 // For testing. // 723 suppressConnectErrors: function () { // 724 suppressConnectErrors = true; // 725 }, // 726 onListening: function (f) { // 727 if (onListeningCallbacks) // 728 onListeningCallbacks.push(f); // 729 else // 730 f(); // 731 } // 732 }); // 733 // 734 // Let the rest of the packages (and Meteor.startup hooks) insert connect // 735 // middlewares and update __meteor_runtime_config__, then keep going to set up // 736 // actually serving HTML. // 737 main = function (argv) { // 738 WebAppInternals.generateBoilerplate(); // 739 // 740 // only start listening after all the startup code has run. // 741 var localPort = parseInt(process.env.PORT) || 0; // 742 var host = process.env.BIND_IP; // 743 var localIp = host || '0.0.0.0'; // 744 httpServer.listen(localPort, localIp, Meteor.bindEnvironment(function() { // 745 if (process.env.METEOR_PRINT_ON_LISTEN) // 746 console.log("LISTENING"); // must match run-app.js // 747 // 748 var callbacks = onListeningCallbacks; // 749 onListeningCallbacks = null; // 750 _.each(callbacks, function (x) { x(); }); // 751 // 752 }, function (e) { // 753 console.error("Error listening:", e); // 754 console.error(e && e.stack); // 755 })); // 756 // 757 return 'DAEMON'; // 758 }; // 759 }; // 760 // 761 // 762 runWebAppServer(); // 763 // 764 // 765 var inlineScriptsAllowed = true; // 766 // 767 WebAppInternals.inlineScriptsAllowed = function () { // 768 return inlineScriptsAllowed; // 769 }; // 770 // 771 WebAppInternals.setInlineScriptsAllowed = function (value) { // 772 inlineScriptsAllowed = value; // 773 WebAppInternals.generateBoilerplate(); // 774 }; // 775 // 776 WebAppInternals.setBundledJsCssPrefix = function (prefix) { // 777 bundledJsCssPrefix = prefix; // 778 WebAppInternals.generateBoilerplate(); // 779 }; // 780 // 781 // Packages can call `WebAppInternals.addStaticJs` to specify static // 782 // JavaScript to be included in the app. This static JS will be inlined, // 783 // unless inline scripts have been disabled, in which case it will be // 784 // served under `/`. // 785 var additionalStaticJs = {}; // 786 WebAppInternals.addStaticJs = function (contents) { // 787 additionalStaticJs["/" + sha1(contents) + ".js"] = contents; // 788 }; // 789 // 790 // Exported for tests // 791 WebAppInternals.getBoilerplate = getBoilerplate; // 792 WebAppInternals.additionalStaticJs = additionalStaticJs; // 793 // 794 ////////////////////////////////////////////////////////////////////////////////////// }).call(this); /* Exports */ if (typeof Package === 'undefined') Package = {}; Package.webapp = { WebApp: WebApp, main: main, WebAppInternals: WebAppInternals }; })(); //# sourceMappingURL=webapp.js.map