Allow injection of arbitrary data to initial Meteor HTML page
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
gadicohen:inject-initial v1.0.2; deprecate gadicohen namespace (no code change) Sep 20, 2014
lib Change injection strategy to work with Meteor 1.3 / gzip (#18) Mar 7, 2016
test Change injection strategy to work with Meteor 1.3 / gzip (#18) Mar 7, 2016
.gitignore add .gitignore Mar 22, 2014
History.md
LICENSE add MIT license Jun 6, 2014
README.md Merge branch 'master' of github.com:gadicc/inject-initial Jun 6, 2014
package.js v1.0.4; Injection now works on both Meteor 1.3 and below (closes #18) Mar 24, 2016
smart.json v0.0.10 (fixes and tests, thanks arunoda) #9 Jul 23, 2014

README.md

inject-initial

API to modify the initial HTML sent by Meteor to the client. An abstraction of the approach first used by Arunoda Susiripala in fast-render.

Preview release but is already being used in a number of projects.

Released under the MIT license (see the LICENSE file).

How, when, why, etc?

This script is targetted at advanced smart package developers. Sometimes we want to pass data with the original HTML request to make the site available quicker. Note, if you're just wanting to accelerate data that is made available via publish/subscribe functions, use fast-render.

Put api.use('inject-initial', ['client', 'server']); in package.js. The injected data is (in the higher level methods) inserted at the top of the HEAD element, and BEFORE any other scripts are called. Thus, you can access this data when your script is loaded, without needing to wait for anything or use any callbacks (like a return from a Meteor.call() to get initial data).

API (from highest to lowest level)

Common arguments:

  • id: Each method expects a unique id. This is used both to give you useful output in error handling, and to allow you to override/replace functions in time. E.g. instead of having a function executed on every connect that retrieves data, simply call the same method with the same id every time the data changes (using observeChanges on a server-only collection, or whatever).

  • data: The data parameter can either be a value, or a function. If a function, it will be called when serving the page to generate appropriate data. It doesn't make sense to use a function in conjunction with a res argument (see below), but we won't stop you for now.

  • res is the optional final argument. If ommitted, the data will be injected on every HTTP request. If present, the data will be specific for (and used only on) that http connection resource (set it up with a regular connectHandler).

Methods:

  • Inject.obj(id, objOrFunc, [res]) on server, accessible via Injected.obj(id) on the client. Obj of course may contain data only, and no references (to functions, other objects, etc). Stored in EJSON in a &lt;script id="id" type="application/ejson">&lt;</script> tag in the HEAD; this allows it to work even if browserPolicy.content.disallowInlineScripts() has been called. Note that id must be unique in the entire DOM! e.g.
if (Meteor.isServer)
  Inject.obj('myData', myData);

// always available immediately
if (Meteor.isClient)
  var myData = Injected.obj('myData');
  • Inject.meta(id, textOrFunc, [res]), accessible via Injected.meta(id) on client. This is plain text that will be stored in a META tag in the HEAD.

  • Inject.rawHead(id, textOrFunc, [res]). text will be inserted in the HTML HEAD.

  • Inject.rawBody(id, textOrFunc, [res]). text will be inserted in the HTML BODY.

  • Inject.rawModHtml(id, func). At injection time, calls func(html, res) with the full page HTML which it expects to be returned, in full, after modification. res is the current http connection response request. e.g.

Inject.rawModHtml('doSomething', function(html) {
	return html.replace(/blah/, 'something');
});
  • Inject.appUrl(url). A copy of Meteor's internal appUrl() method to see if a resource is should be served the initial HTML page.

Example of a "per-request" handler:

if (Meteor.isServer) {
  if (!Package.appcache)
  WebApp.connectHandlers.use(function(req, res, next) {
    if(Inject.appUrl(req.url)) {
      Inject.obj('myData', makeDataFor(req), res);
    }
    next();
  });
}
	
if (Meteor.isClient) {
  // available immediately
  var myData = Injected.obj('myData');
}

App Cache

In general, you probably don't want to pass any information if appcache is enabled, since you'll never be able to refresh it until you update your app again and force a HCP. However, we'll leave these in your hands to decide... maybe that behvaviour is ok. But consider including appcache as a weak dependency, checking for Package.appcache, and avoiding injecting when present (instead use whatever method you used previously to pass data to the client).

Security

This script is very useful for passing general info on the initial request, and fast-render is very useful for passing authenticated publication info, with the additional security checks in place.

To that end, if you are attempting to use this script to pass priviledged information to the client, be aware of the kinds of issues pointed out by Emily Stark here and take the necessary precautions in the callbacks you pass to inject-initial.

Roadmap

  • Should we have functions like isInjectable() to check if appcache is loaded, and what else?

  • Should we have an Inject.script(), which is inserted inline if possible (i.e. if appcache disabled and disallowInlineScripts not set), otherwise, automatically calls (as a 1st script in the head) another page served by the package, which includes the script (great for appcache but slows down page load otherwise?).