Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Spike] Cookie Parsing #53

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 171 additions & 12 deletions lib/application.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
(function(){
var bluebird, cursor, dom, routes, serverRendering, domUtils, span, appComponent, initAppState, observePageChange;
var bluebird, cursor, dom, routes, serverRendering, cookie, domUtils, ref$, keys, each, Obj, span, appComponent, initAppState, observePageChange, parseReqCookies;
bluebird = require('bluebird');
cursor = require('./cursor');
dom = require('./dom');
routes = require('./routes');
serverRendering = require('./server-rendering');
cookie = require('cookie');
domUtils = require('./virtual-dom-utils');
ref$ = require('prelude-ls'), keys = ref$.keys, each = ref$.each, Obj = ref$.Obj;
span = dom.span;
appComponent = React.createFactory(React.createClass({
displayName: 'arch-application',
Expand All @@ -31,10 +33,11 @@
}
}
}));
initAppState = function(initialState, routeContext){
initAppState = function(initialState, routeContext, cookies){
return cursor({
state: initialState,
route: routeContext
route: routeContext,
cookies: cookies
});
};
observePageChange = function(rootTree, appState){
Expand All @@ -51,25 +54,53 @@
}, 0);
});
};
parseReqCookies = function(cookies){
return Obj.map(function(it){
return {
value: it
};
})(
cookies);
};
module.exports = {
create: function(app){
return {
start: function(){
var routeSet, path, rootDomNode, stateNode, serverState, appState, rootElement, root;
var routeSet, path, clientCookies, rootDomNode, stateNode, serverState, appState, rootElement, root;
routeSet = app.routes();
path = location.pathname + location.search + location.hash;
clientCookies = parseReqCookies(cookie.parse(document.cookie));
rootDomNode = document.getElementById("application");
stateNode = document.getElementById("arch-state");
serverState = JSON.parse(stateNode.text);
appState = serverState
? cursor(serverState)
: initAppState(app.getInitialState(), routes.resolve(routeSet, path));
: initAppState(app.getInitialState(), routes.resolve(routeSet, pathname), clientCookies);
app.start(appState);
rootElement = appComponent({
appState: appState,
routes: routeSet
});
root = React.render(rootElement, rootDomNode);
appState.get('cookies').onChange(function(cookies){
return each(function(k){
var c;
c = cookies[k]
? cookies[k].value
: cookies[k];
if (!(clientCookies[k] && deepEq$(clientCookies[k].value, JSON.stringify(c), '==='))) {
if (c === null || c === undefined) {
return document.cookie = cookie.serialize(k, null, {
expires: new Date()
});
} else {
return document.cookie = cookie.serialize(k, c, cookies[k].options);
}
}
})(
keys(
cookies));
});
appState.onChange(function(){
return root.setState({
appState: appState
Expand All @@ -78,12 +109,34 @@
observePageChange(root, appState);
return routes.start(app.routes(), appState);
},
render: function(path){
var routeSet, appState, transaction, rootElement;
render: function(req, res){
var path, routeSet, clientCookies, appState, transaction, rootElement;
path = req.originalUrl;
routeSet = app.routes();
appState = initAppState(app.getInitialState(), routes.resolve(routeSet, path));
clientCookies = parseReqCookies(req.cookies);
appState = initAppState(app.getInitialState(), null, clientCookies);
transaction = appState.startTransaction();
appState.get('cookies').onChange(function(cookies){
return each(function(k){
var c;
c = cookies[k]
? cookies[k].value
: cookies[k];
if (!(clientCookies[k] && deepEq$(clientCookies[k].value, JSON.stringify(c), '==='))) {
if (c === null || c === undefined) {
return res.clearCookie(k);
} else {
return res.cookie(k, c, cookies[k].options);
}
}
})(
keys(
cookies));
});
app.start(appState);
appState.get('route').update(function(){
return routes.resolve(routeSet, path);
});
rootElement = appComponent({
appState: appState,
routes: routeSet
Expand All @@ -94,17 +147,39 @@
return [meta, appState.deref(), React.renderToString(rootElement)];
});
},
processForm: function(path, postData){
var routeSet, appState, transaction, rootElement, location;
processForm: function(req, res){
var path, clientCookies, routeSet, appState, transaction, rootElement, location;
path = req.originalUrl;
clientCookies = parseReqCookies(req.cookies);
routeSet = app.routes();
appState = initAppState(app.getInitialState(), routes.resolve(routeSet, path));
appState = initAppState(app.getInitialState(), null, clientCookies);
appState.get('cookies').onChange(function(cookies){
return each(function(k){
var c;
c = cookies[k]
? cookies[k].value
: cookies[k];
if (!(clientCookies[k] && deepEq$(clientCookies[k].value, JSON.stringify(c), '==='))) {
if (c === null || c === undefined) {
return res.clearCookie(k);
} else {
return res.cookie(k, c, cookies[k].options);
}
}
})(
keys(
cookies));
});
transaction = appState.startTransaction();
app.start(appState);
appState.get('route').update(function(){
return routes.resolve(routeSet, path);
});
rootElement = appComponent({
appState: appState,
routes: routeSet
});
location = serverRendering.processForm(rootElement, appState, postData, path);
location = serverRendering.processForm(rootElement, appState, req.body, path);
return appState.endTransaction(transaction).then(function(){
var meta, body;
meta = serverRendering.routeMetadata(rootElement, appState);
Expand All @@ -115,4 +190,88 @@
};
}
};
function deepEq$(x, y, type){
var toString = {}.toString, hasOwnProperty = {}.hasOwnProperty,
has = function (obj, key) { return hasOwnProperty.call(obj, key); };
var first = true;
return eq(x, y, []);
function eq(a, b, stack) {
var className, length, size, result, alength, blength, r, key, ref, sizeB;
if (a == null || b == null) { return a === b; }
if (a.__placeholder__ || b.__placeholder__) { return true; }
if (a === b) { return a !== 0 || 1 / a == 1 / b; }
className = toString.call(a);
if (toString.call(b) != className) { return false; }
switch (className) {
case '[object String]': return a == String(b);
case '[object Number]':
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
return +a == +b;
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') { return false; }
length = stack.length;
while (length--) { if (stack[length] == a) { return true; } }
stack.push(a);
size = 0;
result = true;
if (className == '[object Array]') {
alength = a.length;
blength = b.length;
if (first) {
switch (type) {
case '===': result = alength === blength; break;
case '<==': result = alength <= blength; break;
case '<<=': result = alength < blength; break;
}
size = alength;
first = false;
} else {
result = alength === blength;
size = alength;
}
if (result) {
while (size--) {
if (!(result = size in a == size in b && eq(a[size], b[size], stack))){ break; }
}
}
} else {
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) {
return false;
}
for (key in a) {
if (has(a, key)) {
size++;
if (!(result = has(b, key) && eq(a[key], b[key], stack))) { break; }
}
}
if (result) {
sizeB = 0;
for (key in b) {
if (has(b, key)) { ++sizeB; }
}
if (first) {
if (type === '<<=') {
result = size < sizeB;
} else if (type === '<==') {
result = size <= sizeB
} else {
result = size === sizeB;
}
} else {
first = false;
result = size === sizeB;
}
}
}
stack.pop();
return result;
}
}
}).call(this);
12 changes: 7 additions & 5 deletions lib/server-rendering.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(function(){
var domUtils, ref$, difference, filter, first, keys, Obj, ReactServerRenderingTransaction, ReactDefaultBatchingStrategy, instantiateReactComponent, ReactUpdates, redirectLocation, configureReact, renderTree, fakeEvent, changeInputs, submitForm, processForm, routeMetadata, resetRedirect, redirect;
var domUtils, ref$, difference, filter, first, keys, Obj, each, ReactServerRenderingTransaction, ReactDefaultBatchingStrategy, instantiateReactComponent, ReactUpdates, redirectLocation, configureReact, renderTree, fakeEvent, changeInputs, submitForm, processForm, routeMetadata, resetRedirect, redirect;
domUtils = require('./virtual-dom-utils');
ref$ = require('prelude-ls'), difference = ref$.difference, filter = ref$.filter, first = ref$.first, keys = ref$.keys, Obj = ref$.Obj;
ref$ = require('prelude-ls'), difference = ref$.difference, filter = ref$.filter, first = ref$.first, keys = ref$.keys, Obj = ref$.Obj, each = ref$.each;
ReactServerRenderingTransaction = require('react/lib/ReactServerRenderingTransaction');
ReactDefaultBatchingStrategy = require('react/lib/ReactDefaultBatchingStrategy');
instantiateReactComponent = require('react/lib/instantiateReactComponent');
Expand Down Expand Up @@ -43,9 +43,11 @@
};
changeInputs = function(inputs, postData){
return each(function(it){
it.props.onChange(fakeEvent(it, {
value: postData[it.props.name]
}));
if (it.props.onChange) {
it.props.onChange(fakeEvent(it, {
value: postData[it.props.name]
}));
}
return ReactUpdates.flushBatchedUpdates();
})(
inputs);
Expand Down
17 changes: 9 additions & 8 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(function(){
var express, fs, path, jade, bluebird, bodyParser, bundler, LiveScript, register, ref$, each, values, filter, find, flatten, map, first, defaults, archGet, archPost, __template, layoutRender;
var express, fs, path, jade, bluebird, bodyParser, bundler, LiveScript, register, cookieParser, ref$, each, values, filter, find, flatten, map, first, defaults, archGet, archPost, __template, layoutRender;
express = require('express');
fs = require('fs');
path = require('path');
Expand All @@ -9,6 +9,7 @@
bundler = require('./bundler');
LiveScript = require('LiveScript');
register = require('babel/register');
cookieParser = require('cookie-parser');
ref$ = require('prelude-ls'), each = ref$.each, values = ref$.values, filter = ref$.filter, find = ref$.find, flatten = ref$.flatten, map = ref$.map, first = ref$.first;
defaults = {
environment: process.env.NODE_ENV || 'development',
Expand All @@ -31,13 +32,13 @@
app = options.app || require(options.paths.app.rel);
get = function(req, res){
console.log("GET", req.originalUrl);
return archGet(app, req.originalUrl, options).spread(function(status, headers, body){
return archGet(app, req, res, options).spread(function(status, headers, body){
return res.status(status).set(headers).send(body);
});
};
post = function(req, res){
console.log("POST", req.originalUrl, req.body);
return archPost(app, req.originalUrl, req.body, options).spread(function(status, headers, body){
return archPost(app, req, res, options).spread(function(status, headers, body){
return res.status(status).set(headers).send(body);
});
};
Expand All @@ -46,7 +47,7 @@
var server, listener;
server = express().use("/" + options.paths['public'], express['static'](path.join(options.paths.app.abs, options.paths['public']))).use(bodyParser.urlencoded({
extended: false
})).get('*', get).post('*', post);
})).use(cookieParser()).get('*', get).post('*', post);
bundler.bundle(options.paths, options.environment === 'development', function(ids){
var done, id, parents, e;
done = [];
Expand Down Expand Up @@ -108,15 +109,15 @@
}
};
};
archGet = function(app, url, options){
return app.render(url).spread(function(meta, appState, body){
archGet = function(app, req, res, options){
return app.render(req, res).spread(function(meta, appState, body){
var html;
html = layoutRender(meta, body, appState, options);
return [200, {}, html];
});
};
archPost = function(app, url, postData, options){
return app.processForm(url, postData).spread(function(meta, appState, body, location){
archPost = function(app, req, res, options){
return app.processForm(req, res).spread(function(meta, appState, body, location){
var html;
if (!body) {
return [
Expand Down
3 changes: 2 additions & 1 deletion lib/virtual-dom-utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(function(){
var testUtils, extractRoute, formElements, routeMetadata, toString$ = {}.toString;
var testUtils, ref$, filter, find, any, extractRoute, formElements, routeMetadata, toString$ = {}.toString;
testUtils = React.addons.TestUtils;
ref$ = require('prelude-ls'), filter = ref$.filter, find = ref$.find, any = ref$.any;
extractRoute = function(tree){
var routes;
routes = testUtils.findAllInRenderedTree(tree, function(it){
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"babel-loader": "^4.1.0",
"bluebird": "^2.3.11",
"body-parser": "^1.10.1",
"cookie": "^0.1.2",
"cookie-parser": "^1.3.4",
"envify-loader": "^0.1.0",
"express": "^4.10.6",
"immutable": "^3.4.1",
Expand Down
6 changes: 3 additions & 3 deletions spec/server_spec.ls
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ describe "server" (_) ->
inst := server app: app

it "passes through to application's server rendering" !->
inst.get app, 'url', paths: { public: 'dist' }
inst.get app, {original-url: 'url', cookies:{}}, {}, paths: { public: 'dist' }
# Test that the method that renders a route to a string has been called
# with a URL an anonymous function.
expect app.render .to-have-been-called-with 'url'
expect app.render .to-have-been-called-with { original-url: 'url', cookies:{} }, {}

it "renders the into a provided layout" (done) !->
inst.get app, 'app-state', paths: { layouts: support-templates, public: 'dist' }
inst.get app, {original-url: 'app-state', cookies:{}}, {}, paths: { layouts: support-templates, public: 'dist' }
.spread (status, headers, body) ->
expect body .to-match /^Test /
expect body .to-match /\ test$/
Expand Down
Loading