forked from RocketChat/Rocket.Chat
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: internalize meteor inject initial (from meteor 3) (RocketChat#…
- Loading branch information
1 parent
ebbad29
commit f57e215
Showing
3 changed files
with
222 additions
and
0 deletions.
There are no files selected for viewing
50 changes: 50 additions & 0 deletions
50
apps/meteor/packages/meteor-inject-initial/lib/inject-core.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Hijack core node API and attach data to the response dynamically | ||
// We are simply using this hack because, there is no way to alter | ||
// Meteor's html content on the server side | ||
|
||
Inject._hijackWrite = function (res) { | ||
const originalWrite = res.write; | ||
res.write = function (chunk, encoding) { | ||
// prevent hijacking other http requests | ||
if (!res.iInjected && encoding === undefined && /^<!DOCTYPE html>/.test(chunk)) { | ||
chunk = chunk.toString(); | ||
|
||
for (id in Inject.rawModHtmlFuncs) { | ||
chunk = Inject.rawModHtmlFuncs[id](chunk, res); | ||
if (!_.isString(chunk)) { | ||
throw new Error(`Inject func id "${id}" must return HTML, not ${typeof chunk}\n${JSON.stringify(chunk, null, 2)}`); | ||
} | ||
} | ||
|
||
res.iInjected = true; | ||
} | ||
|
||
originalWrite.call(res, chunk, encoding); | ||
}; | ||
}; | ||
|
||
WebApp.connectHandlers.use(function (req, res, next) { | ||
// We only separate this to make testing easier | ||
Inject._hijackWrite(res); | ||
|
||
next(); | ||
}); | ||
|
||
// meteor algorithm to check if this is a meteor serving http request or not | ||
Inject.appUrl = function (url) { | ||
if (url === '/favicon.ico' || url === '/robots.txt') return false; | ||
|
||
// NOTE: app.manifest is not a web standard like favicon.ico and | ||
// robots.txt. It is a file id we have chosen to use for HTML5 | ||
// appcache URLs. It is included here to prevent using an appcache | ||
// then removing it from poisoning an app permanently. Eventually, | ||
// once we have server side routing, this won't be needed as | ||
// unknown URLs with return a 404 automatically. | ||
if (url === '/app.manifest') return false; | ||
|
||
// Avoid serving app HTML for declared routes such as /sockjs/. | ||
if (typeof RoutePolicy !== 'undefined' && RoutePolicy.classify(url)) return false; | ||
|
||
// we currently return app HTML on all URLs by default | ||
return true; | ||
}; |
156 changes: 156 additions & 0 deletions
156
apps/meteor/packages/meteor-inject-initial/lib/inject-server.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
function escapeReplaceString(str) { | ||
/* | ||
* When using string.replace(str, newSubStr), the dollar sign ("$") is | ||
* considered a special character in newSubStr, and needs to be escaped | ||
* as "$$". We have to do this twice, for escaping the newSubStr in | ||
* this function, and for the resulting string which is passed back. | ||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace | ||
*/ | ||
return str.replace(/\$/g, '$$$$'); | ||
} | ||
|
||
Inject = { | ||
// stores in a script type=application/ejson tag, accessed with Injected.obj('id') | ||
obj(id, data, res) { | ||
this._checkForObjOrFunction(data, 'Inject.obj(id, data [,res]) expects `data` to be an Object or Function'); | ||
|
||
if (res) { | ||
this._resAssign(res, 'objList', id, data); | ||
} else { | ||
this.objList[id] = data; | ||
} | ||
}, | ||
objList: {}, | ||
|
||
// Inserts a META called `id`, whose `content` can be accessed with Injected.meta() | ||
meta(id, data, res) { | ||
this._checkForTextOrFunction(data, 'Inject.meta(id, data [,res]) expects `data` to be an String or Function'); | ||
|
||
if (res) { | ||
this._resAssign(res, 'metaList', id, data); | ||
} else { | ||
this.metaList[id] = data; | ||
} | ||
}, | ||
metaList: {}, | ||
|
||
rawHead(id, textOrFunc, res) { | ||
this._checkForTextOrFunction(textOrFunc, 'Inject.rawHead(id, content [,res]) expects `content` to be an String or Function'); | ||
|
||
if (res) { | ||
this._resAssign(res, 'rawHeads', id, textOrFunc); | ||
} else { | ||
this.rawHeads[id] = textOrFunc; | ||
} | ||
}, | ||
rawHeads: {}, | ||
|
||
rawBody(id, textOrFunc, res) { | ||
this._checkForTextOrFunction(textOrFunc, 'Inject.rawBody(id, content [,res]) expects `content` to be an String or Function'); | ||
|
||
if (res) { | ||
this._resAssign(res, 'rawBodies', id, textOrFunc); | ||
} else { | ||
this.rawBodies[id] = textOrFunc; | ||
} | ||
}, | ||
rawBodies: {}, | ||
|
||
// The callback receives the entire HTML page and must return a modified version | ||
rawModHtml(id, func) { | ||
if (!_.isFunction(func)) { | ||
const message = `Inject func id "${id}" should be a function, not ${typeof func}`; | ||
throw new Error(message); | ||
} | ||
|
||
this.rawModHtmlFuncs[id] = func; | ||
}, | ||
rawModHtmlFuncs: {}, | ||
|
||
_injectObjects(html, res) { | ||
const objs = _.extend({}, Inject.objList, res.Inject && res.Inject.objList); | ||
if (_.isEmpty(objs)) { | ||
return html; | ||
} | ||
|
||
let obj; | ||
let injectHtml = ''; | ||
for (id in objs) { | ||
obj = _.isFunction(objs[id]) ? objs[id](res) : objs[id]; | ||
injectHtml += ` <script id='${id.replace("'", ''')}' type='application/ejson'>${EJSON.stringify(obj)}</script>\n`; | ||
} | ||
|
||
return html.replace('<head>', `<head>\n${escapeReplaceString(injectHtml)}`); | ||
}, | ||
|
||
_injectMeta(html, res) { | ||
const metas = _.extend({}, Inject.metaList, res.Inject && res.Inject.metaList); | ||
if (_.isEmpty(metas)) return html; | ||
|
||
let injectHtml = ''; | ||
for (id in metas) { | ||
const meta = this._evalToText(metas[id], res, html); | ||
(injectHtml += ` <meta id='${id.replace("'", ''')}' content='${meta.replace("'", ''')}'>\n`), res; | ||
} | ||
|
||
return html.replace('<head>', `<head>\n${escapeReplaceString(injectHtml)}`); | ||
}, | ||
|
||
_injectHeads(html, res) { | ||
const heads = _.extend({}, Inject.rawHeads, res.Inject && res.Inject.rawHeads); | ||
if (_.isEmpty(heads)) return html; | ||
|
||
let injectHtml = ''; | ||
for (id in heads) { | ||
const head = this._evalToText(heads[id], res, html); | ||
injectHtml += `${head}\n`; | ||
} | ||
|
||
return html.replace('<head>', `<head>\n${escapeReplaceString(injectHtml)}`); | ||
}, | ||
|
||
_injectBodies(html, res) { | ||
const bodies = _.extend({}, Inject.rawBodies, res.Inject && res.Inject.rawBodies); | ||
if (_.isEmpty(bodies)) return html; | ||
|
||
let injectHtml = ''; | ||
for (id in bodies) { | ||
const body = this._evalToText(bodies[id], res, html); | ||
injectHtml += `${body}\n`; | ||
} | ||
|
||
return html.replace('<body>', `<body>\n${escapeReplaceString(injectHtml)}`); | ||
}, | ||
|
||
// ensure object exists and store there | ||
_resAssign(res, key, id, value) { | ||
if (!res.Inject) res.Inject = {}; | ||
if (!res.Inject[key]) res.Inject[key] = {}; | ||
res.Inject[key][id] = value; | ||
}, | ||
|
||
_checkForTextOrFunction(arg, message) { | ||
if (!(_.isString(arg) || _.isFunction(arg))) { | ||
throw new Error(message); | ||
} | ||
}, | ||
|
||
_checkForObjOrFunction(arg, message) { | ||
if (!(_.isObject(arg) || _.isFunction(arg))) { | ||
throw new Error(message); | ||
} | ||
}, | ||
|
||
// we don't handle errors here. Let them to handle in a higher level | ||
_evalToText(textOrFunc, res, html) { | ||
if (_.isFunction(textOrFunc)) { | ||
return textOrFunc(res, html); | ||
} | ||
return textOrFunc; | ||
}, | ||
}; | ||
|
||
Inject.rawModHtml('injectHeads', Inject._injectHeads.bind(Inject)); | ||
Inject.rawModHtml('injectMeta', Inject._injectMeta.bind(Inject)); | ||
Inject.rawModHtml('injectBodies', Inject._injectBodies.bind(Inject)); | ||
Inject.rawModHtml('injectObjects', Inject._injectObjects.bind(Inject)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
Package.describe({ | ||
summary: 'Allow injection of arbitrary data to initial Meteor HTML page', | ||
version: '1.0.5', | ||
git: 'https://github.com/meteorhacks/meteor-inject-initial.git', | ||
name: 'meteorhacks:inject-initial', | ||
}); | ||
|
||
Package.onUse(function (api) { | ||
api.use(['routepolicy', 'webapp'], 'server'); | ||
api.use(['ejson', 'underscore'], ['server']); | ||
|
||
api.addFiles('lib/inject-server.js', 'server'); | ||
api.addFiles('lib/inject-core.js', 'server'); | ||
|
||
api.export('Inject', 'server'); | ||
}); |