221V 3 лет назад
Родитель
Сommit
704b96d8e2
135 измененных файлов с 6853 добавлено и 2 удалено
  1. 18 0
      .gitignore
  2. 10 0
      .travis.yml
  3. 18 0
      LICENSE
  4. 181 2
      README.md
  5. 123 0
      doc/actions.tex
  6. 446 0
      doc/api.tex
  7. 31 0
      doc/book.tex
  8. 53 0
      doc/copy.tex
  9. 262 0
      doc/elements.tex
  10. 129 0
      doc/endpoints.tex
  11. 69 0
      doc/handlers.tex
  12. 90 0
      doc/hevea.sty
  13. BIN
      doc/images/connections.png
  14. BIN
      doc/images/n2o-book.png
  15. BIN
      doc/images/n2o-proto.png
  16. BIN
      doc/images/n2o_benchmark.png
  17. BIN
      doc/images/n2o_protocols.png
  18. BIN
      doc/images/page-lifetime.png
  19. 11 0
      doc/images/page-lifetime.uml
  20. BIN
      doc/images/static-serving.png
  21. BIN
      doc/images/wheel.png
  22. 201 0
      doc/index.tex
  23. 168 0
      doc/last.tex
  24. 125 0
      doc/macros.tex
  25. 167 0
      doc/packages.tex
  26. 240 0
      doc/persistence.tex
  27. 134 0
      doc/processes.tex
  28. 451 0
      doc/protocols.tex
  29. 207 0
      doc/setup.tex
  30. 137 0
      doc/synrc.tex
  31. 52 0
      doc/utf8.tex
  32. 17 0
      doc/web/actions.tex
  33. 16 0
      doc/web/api.tex
  34. 17 0
      doc/web/elements.tex
  35. 18 0
      doc/web/endpoints.tex
  36. 18 0
      doc/web/handlers.tex
  37. 18 0
      doc/web/index.tex
  38. 19 0
      doc/web/last.tex
  39. 16 0
      doc/web/macros.tex
  40. 16 0
      doc/web/packages.tex
  41. 16 0
      doc/web/persistence.tex
  42. 17 0
      doc/web/processes.tex
  43. 17 0
      doc/web/protocols.tex
  44. 19 0
      doc/web/setup.tex
  45. 19 0
      doc/web/toc.tex
  46. 19 0
      doc/web/utf8.tex
  47. 55 0
      include/api.hrl
  48. 45 0
      include/wf.hrl
  49. 27 0
      package.exs
  50. 34 0
      priv/bullet.js
  51. 79 0
      priv/ftp.js
  52. 79 0
      priv/http.js
  53. 52 0
      priv/n2o.js
  54. 53 0
      priv/protocols/bert.js
  55. 12 0
      priv/protocols/client.js
  56. 30 0
      priv/protocols/nitrogen.js
  57. 23 0
      priv/template.js
  58. 21 0
      priv/utf8.js
  59. 22 0
      priv/validation.js
  60. 23 0
      priv/xhr.js
  61. 8 0
      rebar.config
  62. 82 0
      samples/README.md
  63. 3 0
      samples/apps/rebar.config
  64. 11 0
      samples/apps/review/README.md
  65. 13 0
      samples/apps/review/include/users.hrl
  66. 11 0
      samples/apps/review/priv/snippets/lobby/bpmn.flow.htm
  67. 15 0
      samples/apps/review/priv/snippets/lobby/bpmn.htm
  68. 1 0
      samples/apps/review/priv/snippets/lobby/erlang.processing.htm
  69. 18 0
      samples/apps/review/priv/snippets/lobby/erlang.setup.htm
  70. 30 0
      samples/apps/review/priv/snippets/lobby/rebol.htm
  71. 18 0
      samples/apps/review/priv/snippets/n2o/template.htm
  72. 29 0
      samples/apps/review/priv/snippets/n2o/utf8.htm
  73. 24 0
      samples/apps/review/priv/static/N2O.svg
  74. 14 0
      samples/apps/review/priv/static/S.svg
  75. 22 0
      samples/apps/review/priv/static/feed.css
  76. 45 0
      samples/apps/review/priv/static/spa/index.htm
  77. 50 0
      samples/apps/review/priv/static/spa/login.htm
  78. 62 0
      samples/apps/review/priv/static/synrc.css
  79. 26 0
      samples/apps/review/priv/templates/dev.html
  80. 47 0
      samples/apps/review/priv/templates/doc.html
  81. 37 0
      samples/apps/review/priv/templates/index.html
  82. 47 0
      samples/apps/review/priv/templates/login.html
  83. 3 0
      samples/apps/review/priv/templates/message.html
  84. 10 0
      samples/apps/review/rebar.config
  85. 22 0
      samples/apps/review/src/config.erl
  86. 40 0
      samples/apps/review/src/doc.erl
  87. 73 0
      samples/apps/review/src/index.erl
  88. 83 0
      samples/apps/review/src/interlogin.erl
  89. 25 0
      samples/apps/review/src/login.erl
  90. 9 0
      samples/apps/review/src/review.app.src
  91. 35 0
      samples/apps/review/src/review.erl
  92. 31 0
      samples/apps/review/src/routes.erl
  93. 15 0
      samples/apps/review/src/users.erl
  94. BIN
      samples/mad
  95. 18 0
      samples/rebar.config
  96. 46 0
      samples/sys.config
  97. 7 0
      samples/vm.args
  98. 5 0
      samples/xen.config
  99. 33 0
      src/endpoints/cowboy/n2o_cowboy.erl
  100. 79 0
      src/endpoints/cowboy/n2o_multipart.erl
  101. 71 0
      src/endpoints/cowboy/n2o_static.erl
  102. 45 0
      src/endpoints/cowboy/n2o_stream.erl
  103. 35 0
      src/endpoints/n2o_document.erl
  104. 47 0
      src/endpoints/n2o_proto.erl
  105. 20 0
      src/endpoints/n2o_relay.erl
  106. 173 0
      src/formatters/wf_convert.erl
  107. 33 0
      src/formatters/wf_utils.erl
  108. 75 0
      src/handlers/n2o_async.erl
  109. 29 0
      src/handlers/n2o_error.erl
  110. 16 0
      src/handlers/n2o_io.erl
  111. 17 0
      src/handlers/n2o_log.erl
  112. 14 0
      src/handlers/n2o_mq.erl
  113. 8 0
      src/handlers/n2o_pickle.erl
  114. 11 0
      src/handlers/n2o_query.erl
  115. 24 0
      src/handlers/n2o_secret.erl
  116. 128 0
      src/handlers/n2o_session.erl
  117. 16 0
      src/handlers/n2o_syn.erl
  118. 9 0
      src/n2o.app.src
  119. 31 0
      src/n2o.erl
  120. 40 0
      src/n2o_cx.erl
  121. 20 0
      src/protocols/n2o_client.erl
  122. 82 0
      src/protocols/n2o_file.erl
  123. 43 0
      src/protocols/n2o_heart.erl
  124. 15 0
      src/protocols/n2o_http.erl
  125. 90 0
      src/protocols/n2o_nitrogen.erl
  126. 14 0
      src/protocols/n2o_text.erl
  127. 278 0
      src/wf.erl
  128. 2 0
      test/bert.sh
  129. 32 0
      test/bert_gen.erl
  130. 23 0
      test/bert_test.js
  131. 19 0
      test/casper/casper.js
  132. 16 0
      test/elements.erl
  133. 12 0
      test/index.html
  134. 76 0
      test/n2o_SUITE.erl
  135. 2 0
      test/test.hrl

+ 18 - 0
.gitignore

@@ -0,0 +1,18 @@
+.DS_Store
+.eunit
+logs
+/ebin/*
+*~
+.#*
+*.beam
+/deps/*
+samples/.applist
+samples/deps
+*.aux
+*.log
+*.pdf
+*.toc
+**/ebin/**
+**/Mnesia*/**
+test/bert.data
+.idea/

+ 10 - 0
.travis.yml

@@ -0,0 +1,10 @@
+language: erlang
+otp_release:
+  - 17.0
+notifications:
+  email: 
+    - maxim@synrc.com
+    - doxtop@synrc.com
+  irc: "chat.freenode.net#n2o"
+script: "(cd samples; ./mad dep com pla)"
+install: "samples/mad"

+ 18 - 0
LICENSE

@@ -0,0 +1,18 @@
+Copyright (c) 2013—2017 Maxim Sokhatsky, Synrc Research Center
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+Software may only be used for the great good and the true happiness of all sentient beings.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 181 - 2
README.md

@@ -1,3 +1,182 @@
-# n4u
+N2O: Erlang Application Server
+==============================
 
-n2o v4.4 fork
+[![Build Status](https://travis-ci.org/synrc/n2o.svg?branch=master)](https://travis-ci.org/synrc/n2o)
+
+Features
+--------
+
+* Formatters: **BERT**, JSON — changeable on the fly
+* Protocols: [N2O](http://5ht.co/n2o.htm), [NITRO](http://5ht.co/n2o.htm), [SPA](http://5ht.co/n2o.htm), [FTP](http://5ht.co/ftp.htm)
+* Endpoints: **WebSocket**, HTTP, [REST](http://synrc.github.io/rest)
+* High Performance Protocol Relay
+* Smallest possible codebase — **1K** **LOC**
+* BEAM/LING support on posix, arm, mips and xen platforms
+* Single-file atomic packaging with [MAD](http://synrc.github.io/mad)
+* Handlers
+  * PubSub: MQS, GPROC, SYN
+  * Templates: DTL, [NITRO](http://synrc.github.io/nitro)
+  * Sessions: server driven
+  * DOM Language: SHEN JavaScript Compiler
+  * Error Logging: IO, LOGGER
+  * Security: PLAIN, AES CBC 128
+* Speed: **30K** **conn/s** at notebook easily
+* Samples: Skyline (DSL), Games (SPA), Review (KVS), Sample (MAD)
+
+Optional Dependencies
+---------------------
+
+N2O comes with BERT message formatter support out of the box, and you only need
+one N2O dependency in this case. Should you need DTL templates, JSON message formatter, 
+SHEN JavaScript Compiler or NITRO Nitrogen DSL you can plug all of them in separately:
+
+```erlang
+{n2o,    ".*",{git,"git://github.com/synrc/n2o",         {tag, "2.8"}}},
+{nitro,  ".*",{git,"git://github.com/synrc/nitro",       {tag, "2.8"}}},
+{shen,   ".*",{git,"git://github.com/synrc/shen",        {tag, "1.5"}}},
+{jsone,  ".*",{git,"git://github.com/sile/jsone.git",    {tag,"v0.3.3"}}},
+{erlydtl,".*",{git,"git://github.com/evanmiller/erlydtl",{tag,"0.8.0"}}},
+```
+
+Message Formatters
+------------------
+
+You can use any message formmatter at the bottom of N2O protocol.
+IO messages supported by the N2O protocol are as follows:
+
+```
+1. BERT : {io,"console.log('hello')",1}
+2. WAMP : [io,"console.log('hello')",1]
+3. JSON : {name:io,eval:"console.log('hello')",data:1}
+4. TEXT : IO console.log('hello') 1\n
+5. XML  : <io><eval>console.log('hello')</eval><data>1</data></io>
+```
+
+Besides, you can even switch a channel termination formatter on the fly
+within one WebSocket session.
+
+All Features in One snippet
+---------------------------
+
+```erlang
+-module(index).
+-compile(export_all).
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("n2o/include/wf.hrl").
+
+peer()    -> io_lib:format("~p",[wf:peer(?REQ)]).
+message() -> wf:js_escape(wf:html_encode(wf:q(message))).
+main()    -> #dtl{file="index",app=n2o_sample,bindings=[{body,body()}]}.
+body() ->
+    {Pid,_} = wf:async(fun(X) -> chat_loop(X) end),
+    [ #panel{id=history}, #textbox{id=message},
+      #button{id=send,body="Chat",postback={chat,Pid},source=[message]} ].
+
+event(init) -> wf:reg(room);
+event({chat,Pid}) -> Pid ! {peer(), message()};
+event(Event) -> skip.
+
+chat_loop({Peer, Message} ) ->
+       wf:insert_bottom(history,#panel{body=[Peer,": ",Message,#br{}]}),
+       wf:flush(room) end.
+```
+
+Performance
+-----------
+
+ab, httperf, wrk and siege are all used for measuring performance. 
+The most valuable request hell is created by wrk and even though it 
+is not achievable in real apps, it can demonstrate internal throughput 
+of certain individual components. 
+
+The nearest to real life apps is siege which also performs a DNS lookup
+for each request. The data below shows internal data throughput by wrk:
+
+| Framework | Enabled Components | Speed | Timeouts |
+|-----------|--------------------|-------|----------|
+| PHP5 FCGI | Simple script with two <?php print "OK"; ?> | 5K | timeouts |
+| ChicagoBoss| No sessions, No DSL, Simple DTL | 500 | no |
+| Nitrogen  | No sessions, No DSL, Simple DTL | 1K | no |
+| N2O       | All enabled, sessions, Template, heavy DSL | 7K | no |
+| N2O       | Sessions enabled, template with two variables, no DSL | 10K | no |
+| N2O       | No sessions, No DSL, only template with two vars | 15K | no |
+
+Kickstart Bootstrap
+-------------------
+
+To try N2O you  need to clone a N2O repo from Github and build it.
+We use a very small and powerful tool called mad designed specially for our Web Stack.
+
+    $ git clone git://github.com/synrc/n2o
+    $ cd n2o/samples
+    $ ./mad deps compile plan repl
+
+Now you can try it out: [http://localhost:8000](http://localhost:8000)
+
+LINUX NOTE: if you want to have online recompilation you should install `inotify-tools` first:
+
+    $ sudo apt-get install inotify-tools
+
+Tests
+-----
+
+    $ cd tests
+    $ npm install -g casperjs
+    $ casperjs test casper
+
+Erlang version
+--------------
+
+We don't accept any reports of problems related to ESL or Ubuntu packaging.
+We only support Erlang built from sources, official Windows package,
+built with kerl or installed on Mac with homebrew. If you have any problems
+with your favourite Erlang package for your OS, please report issues
+to package maintainer.
+
+Posting Issues on Github
+-------
+
+Thank you for using N2O (you've made a wise choice) and your contributions
+to help make it better. Before posting an issue on Github, please contact
+us via the options listed below in the support section. Doing so will
+help us determine whether your issue is a suggested feature, refactor
+of existing code, bug, etc, that needs to be posted to GitHub for the
+contributing development community of N2O to incorporate. DO NOT post
+issues to GitHub related to misuses of N2O, all such issues will be closed.
+
+Support
+-------
+* [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/synrc/n2o?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+* IRC Channel #n2o on FreeNode 24/7
+
+Documentation
+-------
+
+If you are new or you need to decide whether the N2O architecture
+and philosophy is a fit for your project
+
+* Official N2O Book [PDF](http://synrc.com/apps/n2o/doc/book.pdf)
+
+Windows Users
+-------------
+
+For windows you should install http://msys2.github.io and
+appropriative packages to use Synrc Stack:
+
+* pacman -S git
+
+Credits
+-------
+
+* Maxim Sokhatsky — core, shen, windows
+* Dmitry Bushmelev — endpoints, yaws, cowboy
+* Andrii Zadorozhnii — elements, actions, handlers
+* Vladimir Kirillov — mac, bsd, xen, linux support
+* Andrey Martemyanov — binary protocols
+* Oleksandr Nikitin — security
+* Anton Logvinenko — doc
+* Roman Shestakov — advanced elements, ct
+* Jesse Gumm — nitrogen, help
+* Rusty Klophaus — original author
+
+OM A HUM

+ 123 - 0
doc/actions.tex

@@ -0,0 +1,123 @@
+\section{Actions}
+
+\paragraph{}
+{\bf \#action} is the basic record for all actions. It means that each action
+has {\bf \#action} as its ancestor.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    #action { ancestor,
+              target,
+              module,
+              actions,
+              source=[] }.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+{\bf target} specifies an element where this action will arise.
+
+\subsection{JavaScript DSL {\bf \#jq}}
+JavaScript query selector action mimics JavaScript calls and assignments.
+Specific action may be performed depending on filling{\bf property} or {\bf method} fields.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -record(jq, {?ACTION_BASE(action_jq),
+            property,
+            method,
+            args=[],
+            right }).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Here is an example of method calls:
+\begin{lstlisting}
+    wf:wire(#jq{target=n2ostatus,method=[show,select]}).
+\end{lstlisting}
+unfolded to calls:
+\begin{lstlisting}
+    document.querySelector('#n2ostatus').show();
+    document.querySelector('#n2ostatus').select();
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+And here is example of property chained assignments:
+\begin{lstlisting}
+    wf:wire(#jq{target=history,property=scrollTop,
+        right=#jq{target=history,property=scrollHeight}}).
+\end{lstlisting}
+which transforms to:
+\begin{lstlisting}
+    document.querySelector('#history').scrollTop =
+        document.querySelector('#history').scrollHeight;
+\end{lstlisting}
+\vspace{1\baselineskip}
+Part of N2O API is implemented using \#jq actions (updates and redirect).
+This action is introduced as transitional in order to move
+from Nitrogen DSL to using pure JavaScript transformations.
+
+\subsection*{Event Actions}
+Objects passed over WebSockets channel from server to client are called {\bf actions}.
+Objects passed over the same channel from client to server are called {\bf events}. However
+events themselves are bound to HTML elements with {\bf addEventListener} and in order to perform these bindings,
+actions should be sent first. Such actions are called {\bf event actions}. There are three types of event actions.
+
+\subsection{Page Events {\bf \#event}}
+Page events are regular events routed to the calling module. Postback field is used as the main
+routing argument for {\bf event} module function. By providing {\bf source} elements list you specify
+HTML controls values sent to the server and accessed with {\bf wf:q} accessor from the page context.
+Page events are normally generated by active elements like {\bf \#button}, {\bf \#link},
+{\bf \#textbox}, {\bf \#dropdown}, {\bf \#select}, {\bf \#radio} and others elements
+contain postback field.
+
+\paragraph{}
+Control events are used to solve the need of element writers. When you develop your
+own control elements, you usually want events to be routed not to page but to element module.
+Control events were introduced for this purpose.
+
+\subsection{API Events {\bf \#api}}
+When you need to call Erlang function from JavaScript directly you should use API events.
+API events are routed to page module with {\bf api\_event/3} function. API events were
+used in {\bf AVZ} authorization library. Here is an example of how JSON login could be
+implemented using {\bf api\_event}:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    api_event(appLogin, Args, Term) ->
+        Struct = n2o_json:decode(Args),
+        wf:info(?MODULE, "Granted Access"),
+        wf:redirect("/account").
+\end{lstlisting}
+\vspace{1\baselineskip}
+And from JavaScript you call it like this:
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    document.appLogin(JSON.stringify(response));
+\end{lstlisting}
+\vspace{1\baselineskip}
+All API events are bound to root of the HTML document.
+
+
+\subsection{Message Box {\bf \#alert}}
+Message box {\bf alert} is a very simple dialog that could be used for client debugging.
+You can use {\bf console.log} along with alerts.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    event({debug,Var}) ->
+        wf:wire(#alert{text="Debug: " ++ wf:to_list(Var)}),
+\end{lstlisting}
+
+\subsection{Confirmation Box {\bf \#confirm}}
+You can use confirmation boxes for simple approval with JavaScript {\bf confirm} dialogs.
+You should extend this action in order to build custom dialogs. Confirmation box is just an example of how to
+organize this type of logic.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    event(confirm) ->
+        wf:wire(#confirm{text="Are you happy?",postback=continue}),
+
+    event(continue) -> wf:info(?MODULE, "Yes, you're right!", []);
+\end{lstlisting}
+\vspace{1\baselineskip}

+ 446 - 0
doc/api.tex

@@ -0,0 +1,446 @@
+\section{API}
+
+\subsection{Update DOM \bf{wf:update}}
+You can update part of the page or DOM element with a given
+element or even raw HTML. N2O comes with NITRO template engine
+based on Erlang records syntax and optimized to be as fast as DTL or EEX template engines.
+You may use them with {\bf \#dtl} and {\bf \#eex} template NITRO elements.
+N2O Review application provides a sample how to use DTL templates.
+For using Nitrogen like DSL first you should include {\bf nitro} application to your
+rebar.config
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    {nitro,".*",{git,"git://github.com/synrc/nitro",{tag,"2.9"}}},
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+And also plug it in headers to your erlang page module:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -include("nitro/include/nitro.hrl").
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Here is an example of simple {\bf \#span} NITRO element with an HTML counterpart.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    wf:update(history,[#span{body="Hello"}]).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+It generates DOM update script and sends it to
+WebSocket channel for evaluation:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    document.querySelector('#history')
+            .outerHTML = '<span>Hello</span>';
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Companions are also provided for updating head and tail
+of the elements list: {\bf wf:insert\_top/2} and
+{\bf wf:insert\_bottom/2}. These are translated to appropriate
+JavaScript methods {\bf insertBefore} and {\bf appendChild} during rendering.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    wf:insert_top(history,
+        #panel{id=banner, body= [
+            #span{ id=text,
+                   body = wf:f("User ~s logged in.",[wf:user()]) },
+            #button{id=logout, body="Logout", postback=logout },
+            #br{} ]}),
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Remember to envelop all elements in common root element before inserts.
+
+\paragraph{}
+For relative updates use {\bf wf:insert\_before/2} and {\bf wf:insert\_after/2}.
+To remove an element use {\bf wf:remove/2}.
+
+\paragraph{\bf Element Naming}
+You can specify element's id with Erlang atoms,
+lists or binaries. During rendering the value will be converted
+with {\bf wf:to\_list}. Conversion will be consistent only if you use atoms.
+Otherwise you need to care about illegal symbols for element accessors.
+
+\paragraph{}
+During page updates you can create additional elements with
+runtime generated event handlers, perform HTML rendering for
+template elements or even use distributed map/reduce to calculate view.
+You have to be aware that heavy operations will consume
+more power in the browser, but you can save it by rendering
+HTML on server-side. All DOM updates API works both using
+JavaScript/OTP and server pages.
+
+\paragraph{}
+List of elements you can use is given in {\bf Chapter 9}. You can also create
+your own elements with a custom render function.
+If you want to see how custom element are being implemented you may refer
+to {\bf synrc/extra} packages where some useful controls may be found like
+file uploader, calendar, autocompletion textboxlist and HTML editor.
+
+\newpage
+\subsection{Wire JavaScript \bf{wf:wire}}
+Just like HTML is generated from Elements, Actions are rendered into
+JavaScript to handle events raised in the browser. Actions are always
+transformed into JavaScript and sent through WebSockets pipe.
+
+\subsection*{Direct Wiring}
+There are two types of actions. First class are direct JavaScript
+strings provided directly as Erlang lists or via JavaScript/OTP
+transformations.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    wf:wire("window.location='http://synrc.com'").
+\end{lstlisting}
+
+\subsection*{Actions Render}
+Second class actions are in fact Erlang records
+rendered during page load, server events or client events.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    wf:wire(#alert{text="Hello!"}).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+However basic N2O actions that are part of N2O API, {\bf wf:update} and {\bf wf:redirect},
+are implemented as Erlang records as given in the example. If you need deferred
+rendering of JavaScript, you can use Erlang records instead of direct wiring with
+Erlang lists or JavaScript/OTP.
+
+\paragraph{}
+Any action, wired with {\bf wf:wire}, is enveloped in {\bf \#wire\{actions=[]\}},
+which is also an action capable of polymorphic rendering of custom or built-in actions, specified in the list.
+Following nested action embedding is also valid:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    wf:wire(#wire{actions=[#alert{text="N2O"}]}).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+You may try to see how internally wiring is working:
+
+\begin{lstlisting}
+    > wf:actions().
+      []
+
+    > wf:wire(#alert{text="N2O"}).
+      [#wire{ancestor = action,trigger = undefined,
+             target = undefined,module = action_wire,
+             actions = #alert{ancestor = action,
+                              trigger = undefined,
+                              target = undefined,
+                              module = action_alert,
+                              actions = undefined,
+                              source = [], text = "N2O"},
+              source = []}]
+
+    > iolist_to_binary(wf:render(wf:actions())).
+      <<"alert(\"N2O\");">>
+\end{lstlisting}
+
+Consider wiring {\bf \#event} if you want to add listener to
+existed element on page:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+
+    > wf:wire(#event{target=btn,postback=evt,type=click}),
+      []
+
+    > rp(iolist_to_binary(wf:render(wf:actions()))).
+      <<"{var x=qi('element_id'); x && x.addEventListener('cl
+      ick',function (event){{ if (validateSources([])) ws.sen
+      d(enc(tuple(atom('pickle'),bin('element_id'),bin('g2gCa
+      AVkAAJldmQABWluZGV4ZAADZXZ0awAKZWxlbWVudF9pZGQABWV2ZW50
+      aANiAAAFoWIAB8kuYgAOvJA='),[tuple(tuple(utf8_toByteArra
+      y('element_id'),bin('detail')),event.detail)])));else c
+      onsole.log('Validation Error'); }});};">>
+\end{lstlisting}
+
+
+\newpage
+\subsection{Message Bus {\bf wf:reg} and {\bf wf:send}}
+N2O uses {\bf gproc} process registry for managing async processes pools.
+It is used as a PubSub message bus for N2O communications.
+You can associate a process with the pool with {\bf wf:reg}
+and send a message to the pool with {\bf wf:send}.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    loop() ->
+        receive M ->
+            wf:info(?MODULE, "P: ~p, M: ~p",[self(),M]) end, loop().
+\end{lstlisting}
+
+Now you can test it
+
+\begin{lstlisting}
+    > spawn(fun() -> wf:reg(topic), loop() end).
+    > spawn(fun() -> wf:reg(topic), loop() end).
+    > wf:send(topic,"Hello").
+\end{lstlisting}
+
+It should print in REPL something like:
+
+\begin{lstlisting}
+    > [info] P: <0.2012.0>, M: "Hello"
+    > [info] P: <0.2015.0>, M: "Hello"
+\end{lstlisting}
+
+\paragraph{\bf Custom Registrator}
+
+You may want to replace built-in {\bf gproc} based PubSub registrator
+with something more robust like MQTT and AMQP or something more
+internal like {\bf pg2}. All you need is to implement following API:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -module(mqtt_mq).
+    -compile(export_all).
+
+    send(Topic, Message) -> mqtt:publish(Topic, Message).
+    reg(Topic)           -> mqtt:subscribe(Topic, Message).
+    reg(Topic,Tag)       -> mqtt:subscribe(Topic, Tag, Message).
+    unreg(Topic)         -> mqtt:unsubscribe(Topic).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+And set it in runtime:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > application:set_env(n2o,mq,mqtt_mq).
+\end{lstlisting}
+
+\subsection{Async Processes {\bf wf:async} and {\bf wf:flush}}
+Function {\bf wf:async/2} creates Erlang process, which communicate with the primary page
+process by sending messages. {\bf wf:flush/0} should be called to redirect all updates and
+wire actions back to the page process from its async counterpart. But function {\bf wf:flush/1}
+has completly another meaning, it uses pubsub to deliver a rendered actions in async worker to
+any process, previously registered with {\bf wf:reg/1}, by its topic.
+Usually you send messages to async processes over N2O
+message bus {\bf wf:send/2} which is similar to how {\bf wf:flush/1} works.
+But you can use also {\bf n2o\_async:send/2} selectively to async worker what reminds
+{\bf wf:flush/0}. In following
+example different variants are gives, both incrementing counter by 2. Also notice
+the async process initialization through {\bf init} message. It is not nessesary
+to include init clause to async looper.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    body()      -> [ #span   { id=display, body="0"},
+                     #button { id=send, body="Inc",
+                               postback=inc} ].
+
+    event(init) -> wf:async("counter",fun loop/1);
+    event(inc)  -> wf:send(counter,up),
+                   n2o_async:send("counter",up).
+
+    loop(init)  -> wf:reg(counter), put(counter,0);
+    loop(up)    -> C = get(counter) + 1,
+                   put(counter,C),
+                   wf:update(display,
+                      #span{id=display,body=wf:to_binary(C)}),
+                   wf:flush().
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\paragraph{\bf Process Naming}
+The name of async process is globally unique. There are two
+versions, {\bf wf:async/1} and {\bf wf:async/2}. In the given example
+the name of async process is specified as ``counter'', otherwise,
+if the first parameter was not specified, the default name ``looper''
+will be used. Internally each async process includes custom key which
+is settled by default to session id.
+
+\newpage
+So let's mimic {\bf session\_id} and {\bf \#cx} in the shell:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > put(session_id,<<"d43adcc79dd64393a1eb559227a2d3fd">>).
+      undefined
+
+    > wf:context(wf:init_context(undefined)).
+      {cx,[{query,n2o_query},
+           {session,n2o_session},
+           {route,routes}],
+           [],[],index,undefined,[],
+           undefined,[],undefined,[]}
+
+    > wf:async("ho!",
+         fun(X) -> io:format("Received: ~p~n",[X]) end).
+      index:Received: init
+      {<0.507.0>,{async,
+        {"ho!",<<"d43adcc79dd64393a1eb559227a2d3fd">>}}}
+
+    > supervisor:which_children(n2o_sup).
+      [{{async,
+          {"counter",<<"d43adcc79dd64393a1eb559227a2d3fd">>}},
+        <0.11564.0>,worker,
+        [n2o_async]}]
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Async workers suppors both sync and async messages, you may use {\bf gen\_server}
+for calling by pid, {\bf n2o\_async} for named or even built-in erlang way of
+sending messages. All types of handlilng like info, cast and call are supported.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > pid(0,507,0) ! "hey".
+      Received: "hey"
+      ok
+
+    > n2o_async:send("ho!","hola").
+      Received: "hola"
+      ok
+
+    > gen_server:call(pid(0,507,0),"sync").
+      Received: "sync"
+      ok
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Parse URL and Context parameters {\bf wf:q} and {\bf wf:qp}}
+These are used to extract URL parameters or read from the process context.
+{\bf wf:q} extracts variables from the context stored by controls postbacks.
+{\bf wf:qp} extracts variables from URL params provieded by cowboy bridge.
+{\bf wf:qc} extracts variables from {\bf \#cx.params} context parsed with
+custom query handler during endpoint initialization usually performed
+inside N2O with something like.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    Ctx = wf:init_context(Req),
+    NewCtx = wf:fold(init,Ctx#cx.handlers,Ctx),
+    wf:context(NewCtx),
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection{Render {\bf wf:render} or {\bf nitro:render}}
+Render elements or actions with common render. Rendering is usually
+done automatically inside N2O, when you use DOM or Wiring API, but sometime you may
+need manual render, e.g. in static site generators and other NITRO applications
+which couldn't be even dependent from N2O. For that purposes you may use NITRO API
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > nitro:render(#button{id=id,postback=signal}).
+      <<"<button id=\"id\" type=\"button\"></button>">>
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\paragraph{}
+This is simple sample you may use in static site generators, but in N2O context
+you also may need to manual render JavaScript actions produced during HTML rendering.
+First of all you should know that process in which you want to render should be
+initialized with N2O {\bf \#cx} context. Here is example of JavaScript
+produced during previous {\bf \#button} rendering:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > wf:context(wf:init_context([])).
+      undefined
+
+    > rp(iolist_to_binary(nitro:render(wf:actions()))).
+      <<"{var x=qi('id'); x && x.addEventListener('click',
+      function (event){{ if (validateSources([])) ws.send(
+      enc(tuple(atom('pickle'),bin('id'),bin('g2gCaAVkAAJl
+      dmQABWluZGV4ZAAGc2lnbmFsawACaWRkAAVldmVudGgDYgAABaFi
+      AAbo0GIACnB4'),[tuple(tuple(utf8_toByteArray('id'),b
+      in('detail')),event.detail)])));else console.log('Va
+      lidation Error'); }});};">>
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\paragraph{}
+Here is another more complex example of menu rendering using NITRO DSL:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+   menu(Files,Author) ->
+      #panel{id=navcontainer,body=[#ul{id=nav,body=[
+
+      #li{body=[#link{href="#",body="Navigation"},#ul{body=[
+                #li{body=#link{href="/1.htm",body="Root"}},
+                #li{body=#link{href="../1.htm",body="Parent"}},
+                #li{body=#link{href="1.htm",body="This"}}]}]},
+
+      #li{body=[#link{href="#",body="Download"},#ul{body=[
+                #li{body=#link{href=F,body=F}}|| F <- Files ] }]},
+
+      #li{body=[#link{href="#",body="Translations"},#ul{body=[
+                #li{body=#link{href="#",body=Author}}]}]}]}]}.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > rp(iolist_to_binary(wf:render(menu(["1","2"],"5HT")))).
+      <<"<div id=\"navcontainer\"><ul id=\"nav\"><li>
+      <a href=\"#\">Navigation</a><ul><li><a href=\"/
+      1.htm\">Root</a></li><li><a href=\"../1.htm\">P
+      arent</a></li><li><a href=\"1.htm\">This</a></l
+      i></ul></li><li><a href=\"#\">Download</a><ul><
+      li><a href=\"1\">1</a></li><li><a href=\"2\">2<
+      /a></li></ul></li><li><a href=\"#\">Translation
+      s</a><ul><li><a href=\"#\">5HT</a></li></ul></l
+      i></ul></div>">>
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\paragraph{}
+Also notice some helpful functions to preprocess HTML and JavaScript
+escaping to avois XSS attacks:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > wf:html_encode(wf:js_escape("alert('N2O');")).
+    "alert(\\&#39;N2O\\&#39;);"
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Redirects {\bf wf:redirect}}
+Redirects are implemented not with HTTP headers, but with JavaScript action modifying {\bf window.location}.
+This saves login context information which is sent in the first packet upon establishing a WebSocket connection.
+
+\subsection{Session Information {\bf wf:session}}
+Store any session information in ETS tables. Use {\bf wf:user}, {\bf wf:role} for
+login and authorization. Consult {\bf AVZ} library documentation.
+
+\newpage
+\subsection{Bridge information {\bf wf:header} and {\bf wf:cookie}}
+You can read and issue cookie and headers information using internal Web-Server routines.
+You can also read peer IP with {\bf wf:peer}. Usually you do Bridge operations
+inside handlers or endpoints.
+
+\begin{lstlisting}
+    wf:cookies_req(?REQ),
+    wf:cookie_req(Name,Value,Path,TTL,Req)
+\end{lstlisting}
+
+You can set cookies for the page using public cookies API during initial page rendering.
+
+\begin{lstlisting}
+    body() -> wf:cookie("user","Joe"), [].
+\end{lstlisting}
+
+You should use wiring inside WebSocket events:
+
+\begin{lstlisting}
+    event(_) ->
+        wf:wire(wf:f("document.cookie='~s=~s'",["user","Joe"])).
+\end{lstlisting}

+ 31 - 0
doc/book.tex

@@ -0,0 +1,31 @@
+% copyright (c) 2013-2014 Synrc Research Center
+
+\documentclass[8pt,twoside]{article}
+\input{synrc.tex}
+\begin{document}
+\titleWF
+\include{copy}
+
+\tableofcontents
+   \thispagestyle{empty}
+\begin{dedication}
+To Mary and all sentient beings.
+\end{dedication}
+\blankpage
+
+\include{index}
+\include{setup}
+\include{processes}
+\include{endpoints}
+\include{handlers}
+\include{protocols}
+\include{api}
+\include{elements}
+\include{actions}
+\include{macros}
+\include{utf8}
+\include{packages}
+\include{persistence}
+\include{last}
+\end{document}
+

+ 53 - 0
doc/copy.tex

@@ -0,0 +1,53 @@
+%% copyrightpage
+\begingroup
+%\footnotesize
+\topskip 20pt
+\parindent 0pt
+\parskip
+\baselineskip
+
+\begin{tabular}{ll}
+{\bf N2O}: & No Bullshit \\
+    & Sane Framework \\
+    & For Wild Web \\
+\end{tabular}
+\\
+
+SECOND EDITION \\
+
+Book Design and Illustrations by Maxim Sokhatsky \\
+Author Maxim Sokhatsky \\
+
+\begin{tabular}{ll}
+Editors: & Anton Logvinenko \\
+         & Vladimir Kirillov \\
+         & Viktor Sovietov \\
+         & Dmitriy Sukhomlynov \\
+\end{tabular}
+
+Publisher imprint: \\
+Toliman LLC \\
+251 Harvard st. suite 11, Brookline, MA 02446 \\
+1.617.274.0635 \\
+\\
+\\
+\\
+
+
+
+
+Printed in Ukraine \\
+
+Order a copy with worldwide delivery: \\
+https://balovstvo.me/n2o \\
+
+{\bf  ISBN — 978-1-62540-038-3\hspace{2em}}
+
+\begin{tabular}{ll}
+\textcopyright{} 2014 & Toliman \\
+\textcopyright{} 2013-2014 & Synrc Research Center
+\end{tabular}
+
+\endgroup
+
+   \thispagestyle{empty}

+ 262 - 0
doc/elements.tex

@@ -0,0 +1,262 @@
+\section{Elements}
+
+\paragraph{}
+With N2O you don't need to use HTML at all. Instead you define your page
+in the form of Erlang records so that the page is type checked at the compile time.
+This is a classic CGI approach for compiled pages and it gives us all the benefits of
+compile time error checking and provides DSL for client and server-side rendering.
+
+\paragraph{}
+Nitrogen elements, by their nature, are UI control primitives
+that can be used to construct Nitrogen pages with Erlang internal DSL.
+They are compiled into HTML and JavaScript.
+Behavior of all elements is controlled on server-side and all the communication
+between browser and server-side is performed over WebSocket channels.
+Hence there is no need to use POST requests or HTML forms.
+
+\subsection{Static Elements: HTML}
+The core set of HTML elements includes br, headings, links, tables, lists and image tags.
+Static elements are transformed into HTML during rendering.
+
+\paragraph{}
+Static elements could also be used as placeholders for other HTML elements.
+Usually ``static'' means elements that don't use postback parameter:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    #textbox { id=userName, body= <<"Anonymous">> },
+    #panel { id=chatHistory, class=chat_history }
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+This will produce the following HTML code:
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    <input value="Anonymous" id="userName" type="text"/>
+    <div id="chatHistory" class="chat_history"></div>
+\end{lstlisting}
+
+\newpage
+\subsection{Active Elements: HTML and JavaScript}
+There are form elements that provide information for the server
+and gather user input: button, radio and check buttons, text box area and password box.
+Form elements usually allow to assign an Erlang postback handler to specify action behavior.
+These elements are compiled into HTML and JavaScript. For example, during rendering, some
+Actions are converted to JavaScript and sent to be executed in the browser.
+Element definition specifies the list of {\bf source} elements that provide data for event's callback.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    {ok,Pid} = wf:async(fun() -> chat_loop() end),
+    #button { id=sendButton, body= <<"Send">>,
+              postback={chat,Pid}, source=[userName,message] }.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+This will produce the following HTML:
+\begin{lstlisting}
+    <input value="Chat" id="sendButton" type="button"/>
+\end{lstlisting}
+and JavaScript code:
+\begin{lstlisting}
+    $('#sendButton').bind('click',function anonymous(event) {
+        ws.send(Bert.encodebuf({
+            source: Bert.binary('sendButton'),
+            pickle: Bert.binary('g1AAAINQAAAAdX...'),
+            linked: [
+                Bert.tuple(Bert.atom('userName'),
+                utf8.toByteArray($('#userName').val())),
+                Bert.tuple(Bert.atom('message'),
+                utf8.toByteArray($('#message').val()))] })); });
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+If postback action is specified then the page module must include a callback to handle postback info:
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    event({chat,Pid}) ->
+        wf:info(?MODULE, "User ~p Msg ~p",
+                [wf:q(userName),wf:q(message)]).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection{Base Element}
+Each HTML element in N2O DSL has record compatibility with the base element.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    #element { ancestor=element,
+               module,
+               id,
+               actions,
+               class=[],
+               style=[],
+               source=[],
+               data_fields=[],
+               aria_states=[],
+               body,
+               role,
+               tabindex,
+               show_if=true,
+               html_tag=Tag,
+               title }.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Here {\bf module} is an Erlang module that contains a render function.
+Data and Aria HTML custom fields are common attributes for all elements.
+In case element name doesn't correspond to HTML tag, {\bf html\_tag} field provided.
+{\bf body} field is used as element contents for all elements.
+
+\paragraph{}
+Most HTML elements are defined as basic elements. You can even choose element's
+name different from its original HTML tag name:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -record(h6,    ?DEFAULT_BASE).
+    -record(tbody, ?DEFAULT_BASE).
+    -record(panel, ?DEFAULT_BASE_TAG(<<"div">>)).
+    -record('div', ?DEFAULT_BASE_TAG(<<"div">>)).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection{DTL Template {\bf \#dtl}}
+DTL stands for Django Template Language. A DTL element lets to construct HTML
+snippet from template with given placeholders for further substitution.
+Fields contain substitution bindings proplist, filename and templates folder.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -record(dtl, {?ELEMENT_BASE(element_dtl),
+            file="index",
+            bindings=[],
+            app=web,
+            folder="priv/templates",
+            ext="html",
+            bind_script=true }).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Consider we have {\bf prod.dtl} file in {\bf priv/templates} folder with two
+placeholders \{\{title\}\}, \{\{body\}\} and default placeholder for JavaScript \{\{script\}\}.
+All placeholders except \{\{script\}\} should be specified in \#dtl element.
+Here is an example of how to use it:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    body() -> "HTML Body".
+    main() ->
+      [ #dtl { file="prod", ext="dtl",
+               bindings=[{title,<<"Title">>},{body,body()}]} ].
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+You can use templates not only for pages, but for controls as well. Let's say we want
+to use DTL iterators for constructing list elements:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=table.html]
+    {% for i in items %} <a href="{{i.url}}">{{i.name}}</a><br>
+             {% empty %} <span>No items available :-(</span>
+            {% endfor %}
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Here is an example of how to pass variables to the DTL template we've just defined:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    #dtl{file="table", bind_script=false, bindings=[{items,
+      [ {[{name, "Apple"},     {url, "http://apple.com"}]},
+        {[{name, "Google"},    {url, "http://google.com"}]},
+        {[{name, "Microsoft"}, {url, "http://microsoft.com"}]} ]}]}.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+bind\_script should be set to true for page templates. When control elements are rendered from DTL,
+bind\_script should be set to false.
+
+\subsection{Button {\bf \#button}}
+
+\paragraph{}
+\begin{lstlisting}
+    -record(button, {?ELEMENT_BASE(element_button),
+            type= <<"button">>,
+            name,
+            value,
+            postback,
+            delegate,
+            disabled}).
+\end{lstlisting}
+
+\paragraph{}
+Sample:
+
+\begin{lstlisting}
+#button { id=sendButton, body= <<"Send">>,
+          postback={chat,Pid}, source=[userName,message] }.
+\end{lstlisting}
+
+\subsection{Link {\bf \#dropdown}}
+
+\begin{lstlisting}
+    -record(dropdown, {?ELEMENT_BASE(element_dropdown),
+            options,
+            postback,
+            delegate,
+            value,
+            multiple=false,
+            disabled=false,
+            name}).
+
+    -record(option, {?ELEMENT_BASE(element_select),
+            label,
+            value,
+            selected=false,
+            disabled}).
+\end{lstlisting}
+
+\paragraph{}
+Sample:
+
+\begin{lstlisting}
+    #dropdown { id=drop,
+                value="2",
+                postback=combo,
+                source=[drop], options=[
+        #option { label= <<"Microsoft">>, value= <<"Windows">> },
+        #option { label= <<"Google">>,    value= <<"Android">> },
+        #option { label= <<"Apple">>,     value= <<"Mac">> }
+    ]},
+\end{lstlisting}
+
+\newpage
+\subsection{Link {\bf \#link}}
+
+\paragraph{}
+\begin{lstlisting}
+    -record(link, {?ELEMENT_BASE(element_link),
+            target,
+            url="javascript:void(0);",
+            postback,
+            delegate,
+            name}).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Text Editor {\bf \#textarea}}
+
+\paragraph{}
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -record(textarea, {?ELEMENT_BASE(element_textarea),
+            placeholder,
+            name,
+            cols,
+            rows,
+            value}).
+\end{lstlisting}
+\vspace{1\baselineskip}

+ 129 - 0
doc/endpoints.tex

@@ -0,0 +1,129 @@
+\section{Endpoints}
+N2O Erlang Processes are instantiated and run by Web Server.
+Depending on Web Server endpoint bindings you can specify
+module for HTTP requests handling.
+
+\paragraph{}
+N2O comes with three endpoint handlers for each Web Server supported.
+However you are not required to use any of these.
+You can implement your own endpoint handlers, e.g. for using with
+Meteor.js or Angular.js and providing Erlang back-end event streaming
+from server-side. Here is an example of using HTTP, WebSocket and
+REST endpoint handlers with Cowboy Web Server.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    {"/rest/:resource",     rest_cowboy, []},
+    {"/rest/:resource/:id", rest_cowboy, []},
+    {"/ws/[...]",           n2o_stream,  []},
+    {'_',                   n2o_cowboy,  []}
+\end{lstlisting}
+
+\subsection{HTML Pages over HTTP}
+This handler is used for serving initial dynamic HTML page.
+In case you are serving static HTML content this handler is
+not included into the running stack. {\bf {n2o}\_{cowboy}} is
+a default HTML page handler.
+
+\paragraph{}
+On initial page load {\bf {n2o}\_{document}:run} of page document endpoint is started.
+During its execution {\bf {wf}\_{render}:render} proceeds
+by calling {\bf Module:main} selected by the routing handler.
+
+\newpage
+\subsection{JavaScript Events over WebSocket}
+JavaScript handler shares the same router information as the
+HTML handler because during its initial phase the same chain
+of N2O handlers is called.
+
+\paragraph{}
+This handler knows how to deal with XHR and WebSocket requests.
+{\bf {n2o}\_{stream}} is a default JavaScript event handler
+based on Bullet library created by Loïc Hoguin, optimized and refined.
+
+\paragraph{}
+You can send several types of events directly from JavaScript
+using various protocols. E.g. you may need to use client protocol:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+  JavaScript> ws.send(enc(tuple(atom('client'),
+                   tuple(atom('phone_auth'),bin("+380..")))));
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+And catch this event at Erlang side:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+  event({client,{phone_auth,Phone}}) ->
+      io:format("Phone: ~p~n",[Phone]).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+You can also send direct messages to event/1, but use it carefully
+because it may violate security rules.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > ws.send(enc(tuple(atom('direct'),atom('init'))));
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+With catching at Erlang side:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    event(init) -> io:format("Init called~n").
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection{HTTP API over REST}
+REST handler's request context initialization differs for the one
+used by HTML and JavaScript handlers. N2O handler chains are not
+applied to REST requests. {\bf rest\_cowboy} is a default REST
+handler.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    {"/rest/:resource", rest_cowboy, []},
+    {"/rest/:resource/:id", rest_cowboy, []},
+\end{lstlisting}
+
+\lstset{captionpos=b}
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=users.erl]
+    -module(users).
+    -behaviour(rest).
+    -compile({parse_transform, rest}).
+    -include("users.hrl").
+    -export(?REST_API).
+    -rest_record(user).
+
+    init() -> ets:new(users,
+                    [public, named_table, {keypos, #user.id}]).
+
+    populate(Users) -> ets:insert(users, Users).
+    exists(Id) -> ets:member(users, wf:to_list(Id)).
+    get() -> ets:tab2list(users).
+    get(Id) -> [User] = ets:lookup(users, wf:to_list(Id)), User.
+    delete(Id) -> ets:delete(users, wf:to_list(Id)).
+    post(#user{} = User) -> ets:insert(users, User);
+    post(Data) -> post(from_json(Data, #user{})).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+To add users to in-memory storage perform POST requests:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    curl -i -X POST -d "id=vlad" localhost:8000/rest/users
+    curl -i -X POST -d "id=doxtop" localhost:8000/rest/users
+    curl -i -X GET localhost:8000/rest/users
+    curl -i -X PUT -d "id=5HT" localhost:8000/rest/users/vlad
+    curl -i -X GET localhost:8000/rest/users/5HT
+    curl -i -X DELETE localhost:8000/rest/users/5HT
+\end{lstlisting}
+\vspace{1\baselineskip}
+

+ 69 - 0
doc/handlers.tex

@@ -0,0 +1,69 @@
+
+\section{Handlers}
+HTML and JavaScript Web Server HTTP handlers share the same system
+of context initialization.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    init_context(Req) -> #cx{
+        actions=[], module=index, path=[],
+        req=Req, params=[], session=undefined,
+        handlers= [ {'query', wf:config('query', n2o_query)},
+                    {session, wf:config(session, n2o_session)},
+                    {route,   wf:config(route,   n2o_route)} ]}.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Chain of three N2O handlers that are always called
+on each HTTP request. You can redefine any of them or plug your own
+additional handler in the chain to transform web server requests.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=wf:fold/3]
+    fold(Fun,Handlers,Ctx) ->
+        lists:foldl(fun({_,Module},Ctx1) ->
+            {ok,_,NewCtx} = Module:Fun([],Ctx1),
+            NewCtx end,Ctx,Handlers).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Query}
+Query Handler parses URL query and HTTP form information from HTTP request.
+
+\subsection{Session}
+Session Handler manages key-value in-memory database ETS table.
+
+\newpage
+\subsection{Router}
+You can specify routing table with application config:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    {n2o, [{route,n2o_route}]}
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Remember that routing handler should be kept very simple because it
+influences overall initial page load latency and HTTP capacity.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -module(n2o_route).
+    -include_lib("n2o/include/wf.hrl").
+    -export(?ROUTING_API).
+
+    finish(S, Cx) -> {ok, S, Cx}.
+    init(S, Cx)   -> P = wf:path(Cx#context.req),
+                     M = prefix(Path),
+                     {ok, S, Cx#cx{path=P,module=M}}.
+
+    prefix(<<"/ws/",P/binary>>) -> route(P);
+    prefix(<<"/",P/binary>>)    -> route(P);
+    prefix(P)                   -> route(P).
+
+    route(<<>>)                 -> index;
+    route(<<"index">>)          -> index;
+    route(<<"login">>)          -> login;
+    route(<<"favicon.ico">>)    -> index;
+    route(_)                    -> index.
+\end{lstlisting}

+ 90 - 0
doc/hevea.sty

@@ -0,0 +1,90 @@
+% hevea  : hevea.sty
+% This is a very basic style file for latex document to be processed
+% with hevea. It contains definitions of LaTeX environment which are
+% processed in a special way by the translator. 
+%  Mostly :
+%     - latexonly, not processed by hevea, processed by latex.
+%     - htmlonly , the reverse.
+%     - rawhtml,  to include raw HTML in hevea output.
+%     - toimage, to send text to the image file.
+% The package also provides hevea logos, html related commands (ahref
+% etc.), void cutting and image commands.
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{hevea}[2002/01/11]
+\RequirePackage{comment}
+\newif\ifhevea\heveafalse
+\@ifundefined{ifimagen}{\newif\ifimagen\imagenfalse}
+\makeatletter%
+\newcommand{\heveasmup}[2]{%
+\raise #1\hbox{$\m@th$%
+  \csname S@\f@size\endcsname
+  \fontsize\sf@size 0%
+  \math@fontsfalse\selectfont
+#2%
+}}%
+\DeclareRobustCommand{\hevea}{H\kern-.15em\heveasmup{.2ex}{E}\kern-.15emV\kern-.15em\heveasmup{.2ex}{E}\kern-.15emA}%
+\DeclareRobustCommand{\hacha}{H\kern-.15em\heveasmup{.2ex}{A}\kern-.15emC\kern-.1em\heveasmup{.2ex}{H}\kern-.15emA}%
+\DeclareRobustCommand{\html}{\protect\heveasmup{0.ex}{HTML}}
+%%%%%%%%% Hyperlinks hevea style
+\newcommand{\ahref}[2]{{#2}}
+\newcommand{\ahrefloc}[2]{{#2}}
+\newcommand{\aname}[2]{{#2}}
+\newcommand{\ahrefurl}[1]{\texttt{#1}}
+\newcommand{\footahref}[2]{#2\footnote{\texttt{#1}}}
+\newcommand{\mailto}[1]{\texttt{#1}}
+\newcommand{\imgsrc}[2][]{}
+\newcommand{\home}[1]{\protect\raisebox{-.75ex}{\char126}#1}
+\AtBeginDocument
+{\@ifundefined{url}
+{%url package is not loaded
+\let\url\ahref\let\oneurl\ahrefurl\let\footurl\footahref}
+{}}
+%% Void cutting instructions
+\newcounter{cuttingdepth}
+\newcommand{\tocnumber}{}
+\newcommand{\notocnumber}{}
+\newcommand{\cuttingunit}{}
+\newcommand{\cutdef}[2][]{}
+\newcommand{\cuthere}[2]{}
+\newcommand{\cutend}{}
+\newcommand{\htmlhead}[1]{}
+\newcommand{\htmlfoot}[1]{}
+\newcommand{\htmlprefix}[1]{}
+\newenvironment{cutflow}[1]{}{}
+\newcommand{\cutname}[1]{}
+\newcommand{\toplinks}[3]{}
+\newcommand{\setlinkstext}[3]{}
+\newcommand{\flushdef}[1]{}
+\newcommand{\footnoteflush}[1]{}
+%%%% Html only
+\excludecomment{rawhtml}
+\newcommand{\rawhtmlinput}[1]{}
+\excludecomment{htmlonly}
+%%%% Latex only
+\newenvironment{latexonly}{}{}
+\newenvironment{verblatex}{}{}
+%%%% Image file stuff
+\def\toimage{\endgroup}
+\def\endtoimage{\begingroup\def\@currenvir{toimage}}
+\def\verbimage{\endgroup}
+\def\endverbimage{\begingroup\def\@currenvir{verbimage}}
+\newcommand{\imageflush}[1][]{}
+%%% Bgcolor definition
+\newsavebox{\@bgcolorbin}
+\newenvironment{bgcolor}[2][]
+  {\newcommand{\@mycolor}{#2}\begin{lrbox}{\@bgcolorbin}\vbox\bgroup}
+  {\egroup\end{lrbox}%
+   \begin{flushleft}%
+   \colorbox{\@mycolor}{\usebox{\@bgcolorbin}}%
+   \end{flushleft}}
+%%% Style sheets macros, defined as no-ops
+\newcommand{\newstyle}[2]{}
+\newcommand{\addstyle}[1]{}
+\newcommand{\setenvclass}[2]{}
+\newcommand{\getenvclass}[1]{}
+\newcommand{\loadcssfile}[1]{}
+\newenvironment{divstyle}[1]{}{}
+\newenvironment{cellstyle}[2]{}{}
+\newif\ifexternalcss
+%%% Postlude
+\makeatother

BIN
doc/images/connections.png


BIN
doc/images/n2o-book.png


BIN
doc/images/n2o-proto.png


BIN
doc/images/n2o_benchmark.png


BIN
doc/images/n2o_protocols.png


BIN
doc/images/page-lifetime.png


+ 11 - 0
doc/images/page-lifetime.uml

@@ -0,0 +1,11 @@
+title N2O Page Lifetime
+
+Browser->+HTTP: HTTP GET
+HTTP->+Transition Proc: Store Actions
+HTTP->-Browser: Rendered Elements (HTML)
+Browser->+WebSocket: WS INIT
+WebSocket->Transition Proc: Transition ACK, Get Actions
+Transition Proc->-WebSocket: Actions
+WebSocket->-Browser: Rendered Actions (JavaScript), WS INIT DONE
+Browser->+WebSocket: Click Event
+WebSocket->-Browser: Rendered Elements (HTML) + Actions (JavaScript)

BIN
doc/images/static-serving.png


BIN
doc/images/wheel.png


+ 201 - 0
doc/index.tex

@@ -0,0 +1,201 @@
+\section{N2O: Application Server}
+
+N2O was started as the first Erlang Web Framework
+that uses WebSocket protocol only. We saved great compatibility with Nitrogen
+and added many improvements, such as binary page construction,
+binary data transfer, minimized process spawns, transmission of all events over the WebSocket
+and work within Cowboy processes. N2O renders pages several times faster than Nitrogen.
+
+\subsection{Wide Coverage}
+N2O is unusual in that it solves problems in different web development domains
+and stays small and concise at the same time. Started as a Nitrogen concept
+of server-side framework it can also build offline client-side applications
+using the same source code. This became possible with powerful Erlang JavaScript Parse
+Transform which enables running Erlang on JavaScript platform and brings in Erlang and JavaScript
+interoperability. You can use Elixir, LFE and Joxa languages for backend development as well.
+
+\paragraph{}
+N2O supports DSL and HTML templates. It lets you build JavaScript
+control elements in Erlang and perform inline rendering with DSL using
+the same code base for both client and server-side.
+How to use N2O is up to you. You can build mobile applications using server-side rendering
+for both HTML and JavaScript thus reducing CPU cycles and saving the battery of a mobile device.
+Or you can create rich offline desktop applications using Erlang JavaScript compiler.
+
+\newpage
+\subsection*{Why Erlang in Web?}
+We have benchmarked all the existing modern web frameworks that were built using functional
+languages and Cowboy was still the winner. The chart below shows raw HTTP
+performance of functional and C-based languages with concurrent
+primitives (Go, D and Rust) on a VAIO Z notebook with i7640M processor.
+
+\includeimage{n2o_benchmark.png}{Web-Servers raw performance grand congregation}
+
+\paragraph{}
+Erlang was built for low latency streaming of binary data in telecom systems.
+It's fundamental design goal included high manageability, scalability
+and extreme concurrency. Thinking of WebSocket channels as binary
+telecom streams and web pages as user binary sessions
+helps to get an understanding reasons behind choosing Erlang
+over other alternatives for web development.
+
+\paragraph{}
+Using Erlang for web allows you to unleash the full power of telecom systems for
+building web-scale, event-driven, message-passing, NoSQL, asynchronous, non-blocking,
+reliable, highly-available, performant, secure, real-time, distributed applications.
+See Erlang: The Movie II.
+
+\paragraph{}
+N2O outperforms full Nitrogen stack with only 2X raw HTTP Cowboy
+performance downgrade thus upgrading rendering performance several
+times compared to any other functional web framework. And
+sure it's faster than raw HTTP performance of Node.js.
+
+\subsection{Rich and Lightweight Applications}
+There are two approaches for designing client/server communication.
+The first one is called 'data-on-wire'. With this approach only JSON, XML or binary
+data are transferred over RPC and REST channels. All HTML rendering
+is performed on the client-side. This is the most suitable approach for building desktop
+applications. Examples include React, Meteor and ClojureScript.
+This approach can also be used for building mobile clients.
+
+\paragraph{}
+Another approach is sending pre-rendered parts of pages and JS
+and then replacing HTML and executing JavaScript on the client-side. This approach
+is better suited for mobile web development since the
+client doesn't have much resources.
+
+\paragraph{}
+With N2O you can create both types of applications. You can use N2O REST framework
+for desktop applications based on Cowboy REST API along with DTL
+templates for initial HTML rendering for mobile applications.
+You can also use Nitrogen DSL-based approach for modeling parts of pages
+as widgets and control elements, thanks to Nitrogen
+rich collection of elements provided by Nitrogen community.
+
+\paragraph{}
+In cases when your system is built around Erlang infrastructure, N2O
+is the best choice for fast web prototyping, bringing simplicity
+of use and clean codebase. Despite HTML being transfered over the wire,
+you still have access to all your Erlang services directly.
+
+\paragraph{}
+You can also create offline applications using Erlang JavaScript compiler
+just the way you would use ClojureScript, Scala.js, Elm, WebSharper
+or any other similar tool. N2O includes: REST micro frameworks,
+server-side and client-side rendering engines,
+WebSocket events streaming, JavaScript generation
+and JavaScript macro system along with {\bf AVZ} authorization
+library (Facebook, Google, Twitter, Github, Microsoft), key-value storages
+access library {\bf KVS} and {\bf MQS} Message Bus client library (gproc, emqttd).
+
+\subsection{JSON and BERT}
+N2O uses JSON and BERT. All messages passed over
+WebSockets are encoded in native Erlang External Term Format.
+It is easy to parse it in JavaScript with {\bf dec(msg)}
+and it helps to avoid complexity on the server-side. Please refer
+to \footahref{http://bert-rpc.org}{http://bert-rpc.org} for detailed information.
+
+\subsection{DSL and Templates}
+We like Nitrogen for the simple and elegant way it constructs typed
+HTML with internal DSL. This is analogous to Scala Lift,
+OCaml Ocsigen and Haskell Blaze approach. It lets you develop reusable control
+elements and components in the host language.
+
+\paragraph{}
+Template-based approach (Yesod, ASP, PHP, JSP, Rails, Yaws and ChicagoBoss)
+requires developers to deal with raw HTML. It allows
+defining pages in terms of top-level controls, placeholders
+and panels. N2O also support this approach by proving bindings
+to DTL and ET template engines.
+
+\paragraph{}
+The main N2O advantage is its suitability for large-scale projects
+without sacrificing simplicity and comfort of prototyping solutions
+in fast and dynamic manner. Below is an example of complete Web Chat
+implementation using WebSockets that shows how  Templates, DSL and
+asynchronous inter-process communication work in N2O.
+
+\newpage
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=chat.erl]
+    -module(chat).
+    -include_lib("nitro/include/nitro.hrl").
+    -compile(export_all).
+
+    main() ->
+       #dtl { file     = "login",
+              app      = review,
+              bindings = [ { body, body() } ] }.
+
+    body() ->
+      [ #span    { id=title,       body="Your nickname: " },
+        #textbox { id=user,        body="Anonymous" },
+        #panel   { id=history },
+        #textbox { id=message },
+        #button  { id=send,        source=[user,message],
+                                   body="Send",
+                                   postback=chat } ].
+
+    event(init) -> wf:reg(room), wf:async("looper",fun loop/1);
+    event(chat) -> User    = wf:q(user),
+                   Message = wf:q(message),
+                   n2o_async:send("looper",{chat,User,Message}).
+
+    loop({chat,User,Message}) ->
+        Terms = #panel { body = [
+                #span  { body = User }, ": ",
+                #span  { body = Message } ]},
+        wf:insert_bottom(history, Terms),
+        wf:flush(room).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Just try to build the similar functionality with your favorite
+language/framework and feel the difference! Here are one message bus,
+one async {\bf gen\_server} worker under supervision, NITRO DSL, DTL template,
+WebSockets, HTML and JavaScript generation in a simple file that you can
+put in your N2O application directory tree without restart and
+manual compilation. Also you can create single-file bundle
+which is able to run in Windows, Linux and Mac. Moreover this
+application is ready to run under multiplatform LING Erlang virtual machine.
+
+\newpage
+\subsection*{Changes from Nitrogen}
+We took a liberty to break some compatibility with the original
+Nitrogen framework, mostly because we wanted to have a clean codebase
+and achieve better performance. However, it's still possible to port
+Nitrogen web sites to N2O quite easily. E.g., N2O returns id and
+class semantics of HTML and not {\bf html\_id}.
+We simplified HTML rendering without using
+{\bf html\_encode} which should be handled by application layer.
+
+\paragraph{}
+Nitrogen.js, originally created by Rusty Klophaus, was removed
+because of the pure WebSocket nature of N2O which doesn't
+require jQuery on the client-side anymore. In terms of lines of code
+we have impressive showing. New {\bf xhr.js} 25 LOC and {\bf bullet.js} 18 LOC
+was added as the replacement, also {\bf nitrogen.js} takes only 45 LOC.
+UTF-8 {\bf utf8.js} 38 LOC could be plugged separately only when you're
+using {\bf bert.js} 200 LOC formatter. {\bf n2o.js} protocol handler is about 20 LOC.
+
+\paragraph{}
+We also removed {\bf simple\_bridge} and optimized N2O on each level to
+unlock maximum performance and simplicity. We hope you will enjoy
+using N2O. We are fully convinced it is the most efficient way to
+build Web applications in Erlang.
+
+\paragraph{}
+Original Nitrogen was already tested in production under high load and we
+decided to remove {\bf nprocreg} process registry along
+with {\bf action\_comet} heavy process creation. N2O creates a single
+process for an async WebSocket handler, all operations
+ are handled within Cowboy processes.
+
+\paragraph{}
+Also, we introduced new levels of abstraction. You can extend
+the set of available protocols (Nitrogen, Heartbeat, Binary),
+change protocol formatters to BERT, JSON or MessagePack, inject
+your code on almost any level. The code structure
+is clean and Nitrogen compatibility layer NITRO is fully detachable
+from N2O and lives in a separate {\bf synrc/nitro} application.

+ 168 - 0
doc/last.tex

@@ -0,0 +1,168 @@
+\begingroup
+
+\section{History}
+
+\paragraph{}
+The N2O was born in 2013 in spring. It's started as a process of
+reimplementation of Nitrogen Web Framework behavior for better
+performance and code reducing. The initial version had its own
+render core along with pure websocket nature of the IO protocol.
+
+\paragraph{}
+First official release of N2O was made in October 2013 when
+N2O was presented as one having AES/CBC pickling,
+REST transformations, own TeX handbook and
+JavaScript compiler. It is also known as version 0.10.
+
+\paragraph{}
+In this minor release BERT transformations mainly were improved using parse\_transform.
+Was introduced ETS caching. Tiny 50 LOC Makefile called {\bf otp.mk} was
+suggested for easy production deployment. It's still best option to deploy
+ applications. It was release 0.11.
+
+\paragraph{}
+In January 2014 release xen hosting in LING VM
+was initially introduced. Dependencies
+start to freezing; asset and deploy options were improved.
+
+\paragraph{}
+April 2014 release was a giving hand to pure
+JavaScript SPA applications. Now pages could be served
+in nginx behind Erlang cowboy web server. Initial version of
+N2O protocol was introduced in this release. New twitter-format
+stack trace messages was added for error reporting. {\bf bert.js}
+was rewritten and jQuery was removed in this release. In 1.4 release
+was also introduced unified logging schema for KVS and N2O. And main
+WebSocket endpoint was totally rewritten to support N2O protocol.
+
+\paragraph{}
+The release of May 2014 is still supported. In this release
+new build tool {\bf mad} was initially introduced. Version 1.5.
+
+\newpage
+\paragraph{}
+August 2014 version 1.8 received new cookie session manager
+synchronized with ETS table where all entries zipped with session keys.
+Client binary requests was made to exists in {\bf bin} sub-protocol.
+KVS was first added to sample application in this release.
+Full HTML elements and attributes were added, which caused the growth of
+the nitrogen DSL size to the size of N2O.
+
+\paragraph{}
+September 2014 release was numbered 1.9.
+New client side protocol pipeline along with new {\bf n2o.js}.
+For mad was issued new dynamic loader
+which is able to host working directory inside ETS table and readable
+from erlang executable script on Windows, Linux and Mac. UTF-8 support
+was optimized in {\bf utf8.js}. New experimental {\bf rails} protocol and {\bf crashdump.io}
+logging backend module were added in version 1.9.
+
+\paragraph{}
+October 2014 version 1.10 was minor again. The only message in changelog
+were added: "nothing special". The first malfunction bug which was fixed is the
+racing which happened during async DOM bulding. Yes, N2O is faster than browser.
+
+\paragraph{}
+January 2015 version 2.1. Major Release. N2O book from now
+on can be purchased in a hardcover print. For business
+applications validations were introduced. {\bf n2o.js}, {\bf binary.js}, {\bf nitrogen.js},
+{\bf template.js} were slightly optimized. KJELL color support from now on enabled in
+new {\bf review} sample application.
+Log level support and several field and racing fixes in HTML elements.
+
+\paragraph{}
+March 2015 version 2.3. Initial Haskell implentation of N2O server is introduced.
+New N2O WebSocket protocol specification for all stack of {\bf synrc} and {\bf spawnproc} applications.
+New auto-expiring cookie-based session and cache managers. Revised and more sane XHR fallback.
+Automatic language detection from routes in context.
+Several element fixes and latest Cowboy 1.0.1.
+
+\newpage
+\begin{lstlisting}[caption=Bootstraping in a minute]
+
+  $ ./mad app sample
+  Create File: "sample/sys.config"
+  Create File: "sample/apps/sample/priv/static/synrc.css"
+  Create File: "sample/apps/sample/src/web_app.erl"
+  Create File: "sample/apps/rebar.config"
+  Create File: "sample/apps/sample/rebar.config"
+  Create File: "sample/apps/sample/src/sample.app.src"
+  Create File: "sample/apps/sample/src/index.erl"
+  Create File: "sample/apps/sample/src/web_sup.erl"
+  Create File: "sample/apps/sample/priv/static/spa/index.htm"
+  Create File: "sample/rebar.config"
+  Create File: "sample/apps/sample/priv/templates/index.html"
+  Create File: "sample/apps/sample/src/routes.erl"
+  $ cd sample
+  $ time ./mad dep com pla
+  ...
+  Ordered: [kernel,stdlib,fs,cowlib,crypto,
+            compiler,syntax_tools,ranch,gproc,
+            cowboy,erlydtl,n2o,sample,active,mad,sh]
+
+  real    0m41.901s
+  user    0m17.785s
+  sys 0m5.108s
+  $ ./mad rep
+  Configuration: [{n2o,[{port,8000},
+                        {route,routes},
+                        {log_modules,web_app}]},
+                  {kvs,[{dba,store_mnesia},
+                        {schema,[kvs_user,
+                                 kvs_acl,kvs_feed,
+                                 kvs_subscription]}]}]
+  Applications: [kernel,stdlib,fs,cowlib,crypto,
+                 compiler,syntax_tools,ranch,
+                 gproc,cowboy,erlydtl,n2o,
+                 sample,active,mad,sh]
+
+  Erlang/OTP 17 [erts-6.2] [source] [64-bit] [smp:4:4]
+         [async-threads:10] [hipe] [kernel-poll:false]
+
+  Eshell V6.2  (abort with ^G)
+  1>
+\end{lstlisting}
+
+\paragraph{}
+N2O is fast energy efficient binary protocol for IoT and WebSocket applications.
+I hope you will find this retrospective useful in your discovering of N2O.
+
+\newpage
+\section{Afterword}
+
+Hope you find \footahref{http://synrc.com/apps/n2o}{N2O},
+\footahref{http://synrc.com/apps/kvs}{KVS}, and
+\footahref{http://synrc.com/apps/mad}{MAD} stack small and concise,
+because it was the main goal during development.
+We stay with minimal viable functionality criteria.
+
+\paragraph{}
+N2O is free from unnecessary layers and code calls as much as possible.
+At the same time it covers all your needs to build
+flexible web messaging relays using rich stack of protocols.
+
+\paragraph{}
+Minimalistic criteria allows you to see the system's
+most general representation, which drives you to describe efficiently.
+You could be more productive by focusing on core.
+Erlang N2O and companion libraries altogether make
+your life managing web applications easy without
+efforts due to its naturally compact and simple design, and absence of code bloat.
+
+\paragraph{}
+You can see that parse\_transform is very useful, especially in JavaScript
+protocol generation (SHEN) and REST record-to-proplist generators. So having
+quote/unquote in language would be very useful. Fast and small
+Erlang Lisp (LOL) is expecting compiler is this field as universal
+Lisp-based macro system.
+
+\paragraph{}
+All apps in stack operate on its own DSL records-based language:
+N2O --- \#action/\#element; KVS --- \#iterator/\#container.
+This language is accessible directly from Erlang-based languages: Joxa, Elixir, Erlang, Lol.
+
+\paragraph{}
+We hope that this book will guide you in the wild world of Erlang web development
+and you will be enlightened by its minimalistic power.
+
+\endgroup

+ 125 - 0
doc/macros.tex

@@ -0,0 +1,125 @@
+\section{JavaScript Compiler}
+
+\subsection{Compilation and Macros}
+Erlang JavaScript/OTP Parse Transform has two modes defined
+by {\bf \-jsmacro} and {\bf \-js} Erlang module attributes.
+The first mode precompiles Erlang module functions
+into JavaScript strings. The second one exports Erlang functions
+into a separate JavaScript file ready to run in the browser or Node.js.
+
+\paragraph{}
+Sample usage of {\bf \-jsmacro} and {\bf \-js}:
+
+\begin{lstlisting}
+    -module(sample).
+    -compile({parse_transform, shen}).
+    -jsmacro([tabshow/0,doc_ready/1,event/3]).
+    -js(doc_ready/1).
+\end{lstlisting}
+
+\subsection{Erlang Macro Functions}
+Macro functions are useful for using N2O as a server-side framework.
+Functions get rewritten during Erlang compilation into a JavaScript format
+string ready for embedding. Here is an example from N2O pages:
+
+\begin{lstlisting}
+    tabshow() ->
+        X = jq("a[data-toggle=tab]"),
+        X:on("show",
+            fun(E) -> T = jq(E:at("target")),
+            tabshow(T:attr("href")) end).
+
+    doc_ready(E) ->
+        D = jq(document),
+        D:ready(fun() ->
+            T = jq("a[href=\"#" ++ E ++ "\"]"),
+            T:tab("show") end).
+
+    event(A,B,C) ->
+        ws:send('Bert':encodebuf(
+            [{source,'Bert':binary(A)}, {x,C},
+             {pickle,'Bert':binary(B)}, {linked,C}])).
+
+    main() ->
+        Script1 = tabshow(),
+        Script2 = event(1, 2, 3),
+        Script3 = doc_ready(wf:js_list("tab")),
+        io:format("tabshow/0:~n~s~nevent/3:~n~s~ndoc_ready/1:~n~s~n",
+            [Script1,Script2,Script3]).
+\end{lstlisting}
+
+Perform compilation and run tests:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+        $ erlc sample.erl
+        $ erl
+        > sample:main().
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+You'll get the following output:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    tabshow/0:
+        var x = $('a[data-toggle=tab]');
+        x.on('show',function(e) {
+            var t = $(e['target']);
+            return tabshow(t.attr('href'));
+        });
+
+    event/3:
+        ws.send(Bert.encodebuf({source:Bert.binary(1),
+                                x:3,
+                                pickle:Bert.binary(2),
+                                linked:3}));
+
+    doc_ready/1:
+    var d = $(document);
+    d.ready(function() {
+        var t = $('a[href="#' + 'tab' + '"]');
+        return t.tab('show');
+    });
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+As you see, no source-map needed.
+
+\subsection{JavaScript File Compilation}
+Export Erlang function to JavaScript file with {\bf -js([sample/0,fun\_{args}/2])}.
+You could include functions for both {\bf macro} and {\bf js} definitions.
+
+\newpage
+\subsection{Mapping Erlang/OTP to JavaScript/OTP}
+Following OTP libraries are partially supported in Erlang JavaScript Parse Transform:
+{\bf lists}, {\bf proplists}, {\bf queue}, {\bf string}.
+
+\paragraph{\bf Example 1}\
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    S = lists:map(fun(X) -> X * X end,[1,2,3,4]),
+\end{lstlisting}
+
+transforms to:
+
+\begin{lstlisting}
+    s = [1,2,3,4].map(function(x) {
+        return x * x;
+    });
+\end{lstlisting}
+
+\paragraph{\bf Example 2}\
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    M = lists:foldl(fun(X, Acc) -> Acc + X end,0,[1,2,3,4]),
+\end{lstlisting}
+
+transforms to:
+
+\begin{lstlisting}
+    m = [1,2,3,4].reduce(function(x,acc) {
+        return acc + x;
+    },0);
+\end{lstlisting}

+ 167 - 0
doc/packages.tex

@@ -0,0 +1,167 @@
+\section{MAD: Build and Packaging Tool}
+
+\subsection{History}
+
+We came to conclusion that no matter how perfect your libraries are,
+the comfort and ease come mostly from development tools.
+Everything got started when \footahref{https://github.com/proger}{Vladimir~Kirillov} decided to
+replace Rusty's sync beam reloader. As you know sync uses
+filesystem polling which is neither energy-efficient nor elegant. Also
+sync is only able to recompile separate modules, while
+common use-case in N2O is to recompile DTL templates
+and LESS/SCSS stylesheets. That is why we need to recompile
+the whole project. That's the story how \footahref{https://github.com/synrc/active}{active} emerged.
+Under the hood active is a client subscriber
+of \footahref{https://github.com/synrc/fs}{fs} library, native filesystem listener for Linux, Windows and Mac.
+
+\paragraph{}
+De-facto standard in Erlang world is rebar.
+We love rebar interface despite its implementation.
+First we plugged rebar into active and then decided to drop its support,
+it was slow, especially in cold recompilation.
+Rebar was designed to be a stand-alone tool, so it has some
+glitches while using as embedded library.
+Later we switched to Makefile-based build tool \footahref{https://github.com/synrc/otp.mk}{otp.mk}.
+
+\paragraph{}
+The idea to build rebar replacement was up in the air for a long time.
+The best minimal approach was picked up by \footahref{https://github.com/s1n4}{Sina~Samavati},
+who implemented the first prototype called 'mad'. Initially mad
+was able to compile DTL templates, YECC files, escript (like
+bundled in gproc), and it also had support for caching with side-effects.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=Example of building N2O sample]
+                                   Cold       Hot
+    rebar get-deps compile         53.156s    4.714s
+    mad deps compile               54.097s    0.899s
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=Example of building Cowboy]
+                                   Hot
+    make (erlang.mk)               2.588s
+    mad compile                    2.521s
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Introduction}
+
+We were trying to make something minimalistic that fits out \footahref{https://github.com/synrc}{Web Stack}.
+Besides we wanted to use our knowledge of other build tools like lein, sbt etc.
+Also for sure we tried sinan, ebt, Makefile-based scripts.
+
+Synrc mad has a simple interface as follows:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+  BNF:
+      invoke := mad params
+      params := [] | run params
+         run := command [ options ]
+     command := app | lib | deps | compile | bundle
+                start | stop | repl
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+It seems to us more natural, you can specify random
+command sets with different specifiers (options).
+
+\subsection{Single-File Bundling}
+
+The key feature of mad is ability to create single-file bundled web sites.
+Thus making dream to boot simpler than Node.js come true.
+This target escript is ready for run on Windows, Linux and Mac.
+
+\paragraph{}
+To make this possible we implemented a zip filesytem inside escript.
+mad packages priv directories along with ebin and configs.
+You can redefine each file in zip fs inside target
+escript by creating the copy with the same path locally near escript.
+After launch all files are copied to ETS.
+N2O also comes with custom cowboy static handler that is able to
+read static files from this cached ETS filesystem.
+Also bundles are compatible with active online realoading and recompilation.
+
+\subsection{Templates}
+
+mad also comes with N2O templates. So you can bootstrap an N2O-based site
+just having a single copy of mad binary.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    # mad app sample
+    # cd sample
+    # mad deps compile plan bundle sample
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+After that you can just run escript web\_app under Windows, Linux and
+Mac and open \footahref{http://localhost:8000}{http://localhost:8000}.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+C:\> escript sample
+Applications: [kernel,stdlib,crypto,cowlib,ranch,cowboy,compiler,
+               syntax_tools,erlydtl,gproc,xmerl,n2o,sample,
+               fs,active,mad,sh]
+Configuration: [{n2o,[{port,8000},{route,routes}]},
+                {kvs,[{dba,store_mnesia},
+                      {schema,[kvs_user,kvs_acl,kvs_feed,
+                               kvs_subscription]}]}]
+Erlang/OTP 17 [erts-6.0] [64-bit] [smp:4:4] [async-threads:10]
+
+Eshell V6.0  (abort with ^G)
+1>
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Deploy}
+
+mad is also supposed to be a deploy tool with ability to
+deploy not only to our resources like Erlang on Xen, Voxoz (LXC/Xen) but
+also to Heroku and others.
+
+\subsection{OTP Compliant}
+
+mad supports rebar umbrella project structure.
+Specifically two kinds of directory layouts:
+
+\begin{lstlisting}[caption=Solution]
+    apps
+    deps
+    rebar.config
+    sys.config
+\end{lstlisting}
+
+\begin{lstlisting}[caption=OTP Application]
+    deps
+    ebin
+    include
+    priv
+    src
+    rebar.config
+\end{lstlisting}
+
+\subsection{Apps Ordering}
+
+As you may know, you can create OTP releases with
+reltool (rebar generate) or systools (relx). mad currently
+creates releases with relx but is going to do it independently soon.
+Now it can only order applications.
+
+\begin{lstlisting}
+    # mad plan
+    Ordered: [kernel,stdlib,mnesia,kvs,crypto,cowlib,ranch,
+              cowboy,compiler,syntax_tools,erlydtl,gproc,
+              xmerl,n2o,n2o_sample,fs,active,mad,rest,sh]
+\end{lstlisting}
+
+And the good part about mad is it's size:
+
+\begin{lstlisting}
+                      Sources        Binary
+    mad               567 LOC        39 KB
+    rebar             7717 LOC       181 KB
+\end{lstlisting}

+ 240 - 0
doc/persistence.tex

@@ -0,0 +1,240 @@
+\section{KVS: Abstract Erlang Database}
+
+KVS is an Erlang abstraction over various native Erlang key-value databases,
+like Mnesia. Its meta-schema includes only concept
+of iterators (persisted linked lists) that are locked or guarded by
+containers (list head pointers). All write operations to the list are
+serialized using a single Erlang process to provide sequential consistency. The application
+which starts Erlang processes per container called \footahref{https://github.com/synrc/feeds}{feeds}.
+
+\paragraph{}
+The best use-case for KVS and key-value storages is to store operational data.
+This data should be later fed to SQL data warehouses for analysis. Operational data
+stores should be scalable, secure, fault-tolerant and available. That is why
+we store work-in-progress data in key-value storages.
+
+\paragraph{}
+KVS also supports queries that require secondary indexes, which
+are not supported by all backends.
+Currently KVS includes following storage backends: Mnesia, Riak and \footahref{https://github.com/synrc/kai}{KAI}.
+
+\subsection{Polymorphic Records}
+
+Any data in KVS is represented by regular Erlang records.
+The first element of the tuple as usual indicates the name of bucket.
+The second element usually corresponds to the index key field.
+
+\begin{lstlisting}
+            Rec = {user,"maxim@synrc.com",[]}.
+
+     RecordName = element(1, Rec).
+             Id = element(2, Rec).
+\end{lstlisting}
+
+\newpage
+\subsection{Iterators}
+
+Iterator is a sequence of fields used as interface for all tables
+represented as doubly-linked lists. It defines id, next, prev,
+feed\_id fields. This fields should be at the beginning of user's record,
+because KVS core is accessing relative position of the
+field (like \#iterator.next) with setelement/element BIF, e.g.
+
+\begin{lstlisting}
+    setelement(#iterator.next, Record, NewValue).
+\end{lstlisting}
+
+All records could be chained into the double-linked lists in the database.
+So you can inherit from the ITERATOR record just like that:
+
+\begin{lstlisting}
+    -record(access, {?ITERATOR(acl),
+        entry_id,
+        acl_id,
+        accessor,
+        action}).
+\end{lstlisting}
+
+\begin{lstlisting}
+    #iterator { record_name,
+                id,
+                version,
+                container,
+                feed_id,
+                prev,
+                next,
+                feeds,
+                guard }
+\end{lstlisting}
+
+This means your table will support add/remove linked list operations to lists.
+
+\begin{lstlisting}
+    1> kvs:add(#user{id="mes@ua.fm"}).
+    2> kvs:add(#user{id="dox@ua.fm"}).
+\end{lstlisting}
+
+Read the chain (undefined means all)
+
+\begin{lstlisting}
+    3> kvs:entries(kvs:get(feed, user), user, undefined).
+    [#user{id="mes@ua.fm"},#user{id="dox@ua.fm"}]
+\end{lstlisting}
+
+or just
+
+\begin{lstlisting}
+    4> kvs:entries(user).
+    [#user{id="mes@ua.fm"},#user{id="dox@ua.fm"}]
+\end{lstlisting}
+
+Read flat values by all keys from table:
+
+\begin{lstlisting}
+    4> kvs:all(user).
+    [#user{id="mes@ua.fm"},#user{id="dox@ua.fm"}]
+\end{lstlisting}
+
+\subsection{Containers}
+
+If you are using iterators records this automatically
+means you are using containers. Containers are just boxes
+for storing top/heads of the linked lists. Here is layout
+of containers:
+
+\begin{lstlisting}
+    #container { record_name,
+                 id,
+                 top,
+                 entries_count }
+\end{lstlisting}
+
+\subsection{Extending Schema}
+
+Usually you only need to specify custom Mnesia indexes and tables tuning.
+Riak and KAI backends don't need it. Group your table into table packages
+represented as modules with handle\_notice API.
+
+\begin{lstlisting}
+    -module(kvs_feed).
+    -inclue_lib("kvs/include/kvs.hrl").
+
+    metainfo() ->
+        #schema{name=kvs,tables=[
+
+            #table{ name = feed, container = true,
+                    fields = record_info(fields,feed)},
+
+            #table{ name = entry, container = feed,
+                    fields = record_info(fields,entry),
+                    keys = [feed_id,entry_id,from] },
+
+            #table{ name = comment, container = feed,
+                    fields = record_info(fields,comment),
+                    keys = [entry_id,author_id] } ]}.
+\end{lstlisting}
+
+And plug it into schema sys.config:
+
+\begin{lstlisting}
+    {kvs, {schema,[kvs_user,kvs_acl,kvs_feed,kvs_subscription]}},
+\end{lstlisting}
+
+After run you can create schema on local node with:
+
+\begin{lstlisting}
+    1> kvs:join().
+\end{lstlisting}
+
+It will create your custom schema.
+
+\subsection{KVS API}
+
+\subsection{Service}
+System functions for start and stop service:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -spec start() -> ok | {error,any()}.
+    -spec stop() -> stopped.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Schema Change}
+This API allows you to create, initialize and destroy the database schema.
+Depending on database the format and/or feature set may differ. {\bf join/1} function
+is used to initialize database, replicated from remote node along with its schema.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -spec destroy() -> ok.
+    -spec join() -> ok | {error,any()}.
+    -spec join(string()) -> [{atom(),any()}].
+    -spec init(atom(), atom()) -> list(#table{}).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Meta Info}
+This API allows you to build forms from table metainfo.
+You can also use this API for metainfo introspection.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -spec modules() -> list(atom()).
+    -spec containers() -> list(tuple(atom(),list(atom()))).
+    -spec tables() -> list(#table{}).
+    -spec table(atom()) -> #table{}.
+    -spec version() -> {version,string()}.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Chain Ops}
+This API allows you to modify the data, chained lists.
+You can use {\bf create/1} to create the container.
+You can add and remove nodes from lists.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -spec create(atom()) -> integer().
+    -spec remove(tuple()) -> ok | {error,any()}.
+    -spec remove(atom(), any()) -> ok | {error,any()}.
+    -spec add(tuple()) -> {ok,tuple()} |
+                          {error,exist} |
+                          {error,no_container}.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Raw Ops}
+These functions will patch the Erlang record inside database.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -spec put(tuple()) -> ok | {error,any()}.
+    -spec delete(atom(), any()) -> ok | {error,any()}.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Read Ops}
+Allows you to read the Value by Key and list records with given secondary indexes.
+{\bf get/3} API is used to specify default value.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -spec index(atom(), any(), any()) -> list(tuple()).
+    -spec get(atom(),any(), any()) -> {ok,any()}.
+    -spec get(atom(), any()) -> {ok,any()} |
+                                {error,duplicated} |
+                                {error,not_found}.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{Import/Export}
+You can use this API to store all database
+in a single file when it is possible. It's ok for development but not very good for production API.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -spec load_db(string()) -> list(ok | {error,any()}).
+    -spec save_db(string()) -> ok | {error,any()}.
+\end{lstlisting}
+\vspace{1\baselineskip}

+ 134 - 0
doc/processes.tex

@@ -0,0 +1,134 @@
+\section{Erlang Processes}
+
+\subsection{Reduced Latency}
+The secret to reducing latency is simple. We try to deliver rendered HTML
+as soon as possible and render JavaScript only when WebSocket initialization is complete.
+It takes three steps and three Erlang processes for doing that.
+
+\includeimage{images/page-lifetime.png}{Page Lifetime}
+
+\paragraph{}
+N2O request lifetime begins with the start of HTTP process serving the first HTML page.
+After that it dies and spawns Transition process.
+Then the browser initiates WebSocket connections to the similar URL endpoint.
+N2O creates persistent WebSocket process and the Transition process dies.
+
+\paragraph{}
+Your page could also spawn processes with {\bf wf:async}.
+These are persistent processes that act like regular Erlang processes.
+This is a usual approach to organize non-blocking UI for file uploads
+and other time consuming operations.
+
+\newpage
+\subsection{Page Serving Process}
+This processes are applicable only to the case when you serving not static HTML,
+but dynamically rendered pages with NITRO, DTL or ET template engines.
+The very first HTTP handler only renders HTML. During page initialization
+function Module:{\bf main/0} is called. This function should return raw HTML or
+NITRO elements that could be rendered into raw HTML. All created on the way
+JavaScript actions are stored in the transition process.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    main() -> #dtl { file     = "login",
+                     app      = review,
+                     bindings = [ { body,
+                          #button { id       = send,
+                                    postback = chat } } ] }.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\paragraph{}
+HTTP handler will die immediately after returning HTML. Transition process
+stores actions and waits for a request from a WebSocket handler.
+
+\subsection{Transition Process}
+Right after receiving HTML the browser initiates WebSocket connection
+thus starting WebSocket handler on the server. After responding with
+JavaScript actions the Transition process dies and the only process left
+running is WebSocket handler. At this point initialization phase is complete.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    transition(Actions) ->
+        receive {'N2O',Pid} -> Pid ! Actions end.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Transition process is only applicable to dynamically rendered pages
+served by {\bf n2o\_document} endpoint. You never deal with it manually.
+
+\subsection{Events Process}
+After that all client/server communication is performed over
+WebSocket channel. All events coming from the browser are
+handled by N2O, which renders elements to HTML and actions to
+JavaScript. Each user at any time has only one WebSocket process
+per connection.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    event(init) -> wf:reg(room);
+    event(chat) -> #insert_top(history,#span{body="message"}),
+                   wf:flush(room).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+This code will register all WebSocket processes under the same
+topic in pubsub and broadcast history changing to all registered
+process in the system under the same topic using {\bf \#flush}
+NITRO protocol message.
+
+\paragraph{}
+During page initialization before Module:{\bf event(init)},
+Module:{\bf main/0} is called to render initial postbacks for
+elements. So you can share the same code to use SPA or DSL/DTL approach.
+
+\subsection{Async Processes}
+These are user processes that were created with {\bf wf:async} invocation.
+This processes was very useful to organize persistent stateful connection
+for legacy async technology like COMET for XHR channel. If you have problem with
+proxying WebSocket stream you can easily use XHR fallback that is
+provided by {\bf xhr.js} N2O companion library.
+Async processes are optional and only needed when you have a UI event taking too much
+time to be processed, like gigabyte file uploads. You can create
+multiple async processes per user. Starting from N2O 2.9 all async
+processes are being created as {\bf gen\_server} under
+{\bf n2o\_sup} supervision tree.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    event(init) -> wf:reg(room),
+                   wf:async("looper", fun async/1);
+
+    async(init) -> ok;
+    aynsc(Chat) -> io:format("Chat: ~p~n",[Chat]).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection{SPA Mode}
+In SPA mode N2O can serve no HTML at all. N2O elements are
+bound during initialization handshake and thus can be used
+regularly as in DSL mode.
+
+\paragraph{}
+In the example provided in n2o/samples you can find two different
+front end to the same {\bf review} application which consist of
+two page modules {\bf index} and {\bf login}. You can access this application
+involving no HTML rendering by using static file serving that could be
+switched to direct nginx serving in production.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    open http://localhost:8000/static/app/login.htm
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Or you can see DTL rendered HTML pages which resides at following address:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    open http://localhost:8000/login.htm
+\end{lstlisting}
+\vspace{1\baselineskip}
+

+ 451 - 0
doc/protocols.tex

@@ -0,0 +1,451 @@
+\section{Protocols}
+N2O is more that just web framework or even application server.
+It also has protocol specification that covers broad range of application domains.
+In this chapter we go deep inside network capabilities of N2O communications.
+N2O protocol also has an ASN.1 formal description, however here we will speak on it freely.
+Here is the landscape of N2O protocols stack.
+
+\includeimage{n2o-proto.png}{Protocols Stack}
+
+\paragraph{}
+You may find it similar to XML-based XMPP, binary COM/CORBA,
+JSON-based WAMP, Apache Camel or Microsoft WCF communication foundations.
+We took best from all and put into one protocols stack for web,
+social and enterprise domains providing stable and mature implementation for Erlang
+in a form of N2O application server.
+
+\newpage
+\subsection*{Cross Language Compatibility}
+N2O application server implemented to support N2O protocol definition
+in Erlang which is widely used in enterprise applications.
+Experimental implementation in Haskell {\bf n2o.hs} exists
+which supports only core {\bf heart} protocol along with {\bf bert} formatter.
+We will show you how N2O clients are compatible across
+different server implementations in different languages.
+
+\subsection*{Web Protocols: {\bf nitro}, {\bf spa}, {\bf bin}}
+N2O protocols stack provides definition for several unoverlapped protocol layers.
+N2O application server implementation of N2O protocol specification supports
+four protocol layers from this stack for WebSocket and IoT applications:
+{\bf heart}, {\bf nitro}, {\bf spa} and {\bf bin} protocols.
+HEART protocol is designed for reliable managed connections and stream channel initialization.
+The domain of NITRO protocol is HTML5 client/server interoperability, HTML events and JavaScript delivery.
+SPA protocol dedicated for games and static page applications that involves no HTML,
+such as SVG based games or non-gui IoT applications.
+And finally binary file transfer protocol for images and gigabyte file uploads and downloads.
+All these protocols transfers coexist in the same multi-channel stream.
+
+\subsection*{Social Protocols: {\bf roster}, {\bf muc}, {\bf search}}
+For social connectivity one may need to use {\bf synrc/roster} instant messaging server
+that supports {\bf roster} protocol  with variation
+for enabling public rooms {\bf muc} or full-text {\bf search} facilities.
+
+\subsection*{Enterprise Protocols: {\bf bpe}, {\bf mq}, {\bf rest}}
+There is no single system shipped to support all of N2O protocols but it
+could exist theoretically. For other protocols implementation you may refer
+to other products like {\bf spawnproc/bpe}, {\bf synrc/rest} or {\bf synrc/mq}.
+
+\newpage
+\subsection*{Channel Termination Formatters}
+N2O protocol is formatter agnostic and it doesn't strict you
+to use a particular encoder/decoder.
+Application developers could choose their own formatter per protocol.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    1. BERT : {io,"fire();",1}
+    2. WAMP : [io,"fire();",1]
+    3. JSON : {name:io,eval:"fire();",data:1}
+    4. TEXT : IO \xFF fire(); \xFF 1\n
+    5. XML  : <io><eval>fire();</eval><data>1</data></io>
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+E.g. N2O uses TEXT formatting for ``PING'' and ``N2O,'' protocol messages,
+across versions N2O used to have IO message formatted with JSON and BERT both.
+All other protocol messages were BERT from origin.
+Make sure formatters set for client and server is compatible.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    #cx{formatter=bert}.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Note that you may include to support more that one protocol on the client.
+At server side you can change formatter on the fly without breaking
+the channel stream. Each message during data stream could be formatted
+using only one protocol at a time. If you want to pass each message
+through more that one formatter you should write an echo protocol.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    <script src='/n2o/protocols/bert.js'></script>
+    <script src='/n2o/protocols/client.js'></script>
+    <script>protos = [ $bert, $client ]; N2O_start();</script>
+\end{lstlisting}
+
+
+\newpage
+\subsection*{Protocol Loop}
+After message arrives to endpoint and handlers chain is being initializes,
+message then comes to protocol stack. N2O selects appropriative protocol
+module and handle the message. After than message is being formatted and
+replied back to stream channel. Note that protocol loop is applicable
+only to WebSocket stream channel endpoint.
+
+\includeimage{n2o_protocols.png}{Messaging Pipeline}
+
+\paragraph{}
+Here is pseudocode how message travels for each protocol until some
+of them handle the message. Note tnat this logic is subject to change.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=Top-level protocol loop in {n2o}\_{proto}]
+    reply(M,R,S)              -> {reply,M,R,S}.
+    nop(R,S)                  -> {reply,<<>>,R,S}.
+    push(_,R,S,[],_Acc)       -> nop(R,S);
+    push(M,R,S,[H|T],Acc)     ->
+        case H:info(M,R,S) of
+              {unknown,_,_,_} -> push(M,R,S,T,Acc);
+             {reply,M1,R1,S1} -> reply(M1,R1,S1);
+                            A -> push(M,R,S,T,[A|Acc]) end.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection*{Enabling Protocols}
+You may set up protocol from sys.config file,
+enabling or disabling some of them on the fly.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    protocols() ->
+       wf:config(n2o,protocols,[ n2o_heart,
+                                 n2o_nitrogen,
+                                 n2o_client,
+                                 n2o_file  ]).
+
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+For example in Skyline (DSL) application you use only {\bf nitro} protocol:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > wf:config(n2o,protocols).
+    [n2o_heart,n2o_nitrogen]
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+And in Games (SPA) application you need only {\bf spa} protocol:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > wf:config(n2o,protocols).
+    [n2o_heart,n2o_client]
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection{HEART}
+
+HEART protocol is essential WebSocket application level protocol for PING and N2O initialization.
+It pings every 4-5 seconds from client-side to server thus allowing to
+determine client online presence. On reconnection or initial connect
+client sends N2O init marker telling to server to reinitialize the context.
+
+\paragraph{}
+The {\bf heart} protocol defined client originated messages N2O, PING
+and server originated messages PONG, IO and NOP. IO message contains EVAL
+that contains UTF-8 JavaScript string and DATA reply contains any
+binary string, including BERT encoded data. "PING" and "N2O," are
+defined as text 4-bytes messages and second could be followed by
+any text string. NOP is 0-byte acknowledging packet.
+This is heart essence protocol which is enough for any rpc and code
+transferring interface. Normally heart protocol is not for active
+client usage but for supporting active connection with notifications
+and possibly DOM updates.
+
+\subsection*{Session Initialization}
+
+After page load you should start N2O session in JavaScript with configured
+formatters and starting function that will start message loop on the client:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    var transition = {pid: '', host: 'localhost', port:'8000'};
+    protos = [ $bert, $client ];
+    N2O_start();
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+If {\bf pid} field is not set in {\bf transition} variable then you
+will request new session otherwise you may put here information from
+previously settled cookies for attaching to existing session. This {\bf pid}
+disregarding set or empty will be bypassed as a parameter to N2O init marker.
+
+You can manually invoke session initialization inside existing session:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    ws.send('N2O,');
+\end{lstlisting}
+
+\newpage
+In response on successful WebSocket connection and enabled {\bf heart}
+protocol on the server you will receive the IO message event.
+IO events are containers for function and data which can be used as parameters.
+There is no predefined semantic to IO message. Second element of a tuple
+will be directly evaluated in WebBrowser. Third element can contain data or error
+as for SPA and BIN protocols, and can contain only error for NITRO protocol.
+IO events are not constructed on client. N2O request returns IO messages with
+evaluation string and empty data or empty evaluation string
+with error in data field.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    issue TEXT N2O expect IO
+   N2O is TEXT "N2O," ++ PID
+   PID is TEXT "" or any
+    IO is BERT {io,<<>>,Error}
+            or {io,Eval,<<>>}
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection*{Online Presence}
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    ws.send('PING');
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+You can try manually send this messag in web console to see whats happening,
+also you can enable logging the heartbeat protocol by including its
+module in {\bf log\_modules}:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    log_modules() -> [n2o_heart].
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Heartbeat protocol PING request returns PONG or empty NOP binary response.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    issue TEXT PING expect PONG
+  PONG is TEXT "PONG" or ""
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+
+\newpage
+\subsection{NITRO}
+
+NITRO protocol consist of three protocol messages: {\bf pickle}, {\bf flush} and {\bf direct}.
+Pickled messages are used if you send messages over unencrypted
+channel and want to hide the content of the message,
+that was generated on server. You can use BASE64 pickling mechanisms
+with optional AES/RIPEMD160 encrypting. NITRO messages on success alway
+return empty data field in IO message and
+error otherwise. Here is definition to NITRO protocol in expect language:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+        issue BERT PICKLE expect IO
+        issue BERT DIRECT expect IO
+        issue BERT FLUSH  expect IO
+    PICKLE is BERT {pickle,_,_,_,_}
+    DIRECT is BERT {direct,_}
+     FLUSH is BERT {flush,_}
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Usually {\bf pickle} events are being sent generated from server during
+rendering of {\bf nitro} elements. To see how it looks like you can see
+inside IO messages returned from N2O initialization. There you can find
+something like this:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    ws.send(enc(tuple(atom('pickle'),
+        bin('loginButton'),
+        bin('g2gCaAVkAAJldmQABGF1dGhkAAVsb2dpbmsAC2xvZ2lu'
+             'QnV0dG9uZAAFZXZlbnRoA2IAAAWiYgAA72ViAA8kIQ=='),
+        [ tuple(tuple(utf8_toByteArray('loginButton'),
+                      bin('detail')),[]),
+          tuple(atom('user'),querySource('user')),
+          tuple(atom('pass'),querySource('pass'))])));
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Invocation of {\bf pickle} messages is binded to DOM elements
+using {\bf source} and {\bf postback} information from nitro elements.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+   #button { id=loginButton,
+             body="Login",
+             postback=login,
+             source=[user,pass] } ].
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Only fields listed in {\bf source} will be included in {\bf pickle}
+message on invocation. Information about module and event arguments (postback)
+is sent encrypted or pickled. So it would be hard to know the internal
+structure of server codebase for potential hacker. On the server you will
+recieve following structure:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    {pickle,<<"loginButton">>,
+            <<"g2gCaAVkAAJldmQABGF1dGhkAAVsb2dpbmsAC2xvZ2lu"
+              "QnV0dG9uZAAFZXZlbnRoA2IAAAWiYgAA72ViAA8kIQ==">>,
+           [{{"loginButton",<<"detail">>},[]},
+            {user,[]},
+            {pass,"z"}]}
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+You can depickle {\bf \#ev} event with {\bf wf:depickle} API:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > wf:depickle(<<"g1AAAAA6eJzLYMpgTWFgSi1LYWDNyU/PzIPR2Qh+"
+                    "allqXkkGcxIDA+siIHEvKomB5cBKAN+JEQ4=">>).
+
+    #ev { module  = Module  = auth,
+          msg     = Message = login,
+          name    = event,
+          trigger = "loginButton" }
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Information for {\bf \#ev} event is directly passed to page module
+as {\bf Module:event(Message) }. Information from sources {\bf user}
+and {\bf pass} could be retrieved with {\bf wf:q} API:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -module(auth).
+    -compile(export_all).
+
+    event(login) ->
+        io:format(lists:concat([":user:",wf:q(user),
+                                ":pass:",wf:q(pass)])).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+This is Nitrogen-based messaging model. Nitrogen WebSocket processes receive also
+flush and delivery protocol messages, but originated from server, which is internal
+NITRO protocol messages. All client requests originate IO message as a response.
+
+\newpage
+\subsection{SPA}
+
+If you are creating SVG based game you don't need HTML5 nitro elements at all.
+Instead you need simple and clean JavaScript based protocol for updating DOM SVG elements
+but based on {\bf shen} generated or manual JavaScript code sent from server.
+Thus you need still IO messages as a reply but originating message shouldn't
+rely in {\bf nitro} at all. For that purposes in general and for {\bf synrc/games} sample
+in particular we created SPA protocol layer. SPA protocol consist of CLIENT originated message
+and SERVER message that could be originated both from client and server. All messages expects
+IO as a response. In IO response data field is always set with return value of the event
+while eval field is set with rendered actions as in NITRO protocol.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+        issue BERT CLIENT expect IO
+        issue BERT SERVER expect IO
+    SERVER is BERT {server,_}
+    CLIENT is BERT {client,_}
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Client messages usually originated at client and represent the Client API Requests:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    ws.send(enc(tuple(
+        atom('client'),
+        tuple(atom('join_game'),1000001))));
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Server messages are usually being sent to client originated on the
+server by sending {\bf info} notifications directly to Web Socket process:
+
+\begin{lstlisting}
+    > WebSocketPid ! {server, Message}
+\end{lstlisting}
+
+You can obtain this Pid during page init:
+
+\begin{lstlisting}
+    event(init) -> io:format("Pid: ~p",[self()]);
+\end{lstlisting}
+
+You can also send server messages from client relays and vice versa.
+It is up to your application and client/server handlers how to handle such messages.
+
+\newpage
+\subsection{BIN}
+
+When you need raw binary Blob on client-side,
+for images or other raw data, you can ask server like this:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > ws.send(enc(tuple(atom('bin'),bin('request'))));
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Ensure you have defined {\bf \#bin} handler and page you are
+asking is visible by router:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    event(#bin{data=Data}) ->
+        wf:info(?MODULE,"Binary Delivered ~p~n",[Data]),
+        #bin{data = "SERVER v1"};
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Having enabled all loggin in module {\bf n2o\_file}, {\bf index} and {\bf wf\_convert}
+you will see:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    n2o_file:BIN Message: {bin,<<"request">>}
+    index:Binary Delivered <<"request">>
+    wf_convert:BERT {bin,_}: "SERVER v1"
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+In JavaScript when you enable `debug=true` you can see:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > {"t":104,"v":[{"t":100,"v":"bin"},
+                    {"t":107,"v":"SERVER v1"}]}
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Or by adding handling for BIN protocol:
+
+\begin{lstlisting}
+    > $file.do = function (x)
+        { console.log('BIN received: ' + x.v[1].v); }
+    > ws.send(enc(tuple(atom('bin'),bin('request'))));
+    > BIN received: SERVER v1
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+The formal description of BIN is simple relay:
+
+\begin{lstlisting}
+    issue BERT {bin,_} expect {bin,_}
+\end{lstlisting}

+ 207 - 0
doc/setup.tex

@@ -0,0 +1,207 @@
+\section{Setup}
+
+\subsection{Prerequisites}
+To run N2O websites you need to install Erlang version 18 or higher.
+N2O works on Windows, Mac and Linux.
+
+\subsection{Kickstart Bootstrap}
+To try N2O you only need to fetch it from Github and build. We don't use
+fancy scripts, so building process is OTP compatible: bootstrap site
+is bundled as an Erlang release.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    $ git clone git://github.com/synrc/n2o
+    $ cd n2o/samples
+    $ ./mad deps compile plan repl
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+Now you can try: \footahref{http://localhost:8000}{http://localhost:8000}.
+
+On Linux you should do at first:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    $ sudo apt-get install inotify-tools
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection{Application Template}
+If you want to start using N2O inside your application, you can use Cowboy dispatch parameter
+for passing HTTP, REST, WebSocket and Static N2O endpoints:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=sample.erl]
+
+  -module(sample).
+  -behaviour(supervisor).
+  -behaviour(application).
+  -export([init/1, start/2, stop/1, main/1]).
+
+  main(A)    -> mad:main(A).
+  start(_,_) -> supervisor:start_link({local,review},review,[]).
+  stop(_)    -> ok.
+  init([])   -> { ok, { { one_for_one, 5, 100 }, [spec()] } }.
+
+  spec()   -> ranch:child_spec(http, 100, ranch_tcp, port(), cowboy_protocol, env()).
+  port()   -> [ { port, wf:config(n2o,port,8000)  } ].
+  env()    -> [ { env, [ { dispatch, points() } ] } ].
+  static() ->   { dir, "apps/sample/priv/static", mime() }.
+  n2o()    ->   { dir, "deps/n2o/priv",           mime() }.
+  mime()   -> [ { mimetypes, cow_mimetypes, all   } ].
+  points() -> cowboy_router:compile([{'_', [
+
+              { "/static/[...]",       n2o_static,  static()},
+              { "/n2o/[...]",          n2o_static,  n2o()},
+              { "/ws/[...]",           n2o_stream,  []},
+              { '_',                   n2o_cowboy,  []} ]}]).
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+While Listing 1 is a little bit criptic we want to say that N2O
+intentionally not introduced here any syntax sugar.
+For any Erlang application you need to create application
+and supervisor behavior modules which we combined in the
+same Erlang file for simplicity.
+
+\paragraph{}
+Cowboy routing rules also leaved as is.
+We'd better to leave our efforts for making N2O protocol
+and N2O internals simplier. Here we can't fix a much. Just use this
+as template for bootstrapping N2O based applications.
+
+\subsection{Companion Dependencies}
+For raw N2O use with BERT message formatter you need only
+one N2O dependecy, but if you want to use DTL templates,
+JSON message formatter, SHEN JavaScript Compiler or NITRO
+Nitrogen DSL you can plug all of them separately.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    https://github.com/synrc/n2o        2.9
+    https://github.com/synrc/nitro      2.9
+    https://github.com/synrc/kvs        2.9
+    https://github.com/synrc/active     2.9
+    https://github.com/synrc/shen       1.5
+    https://github.com/synrc/rest       1.5
+    https://github.com/spawnproc/bpe    1.5
+    https://github.com/spawnproc/forms  1.5
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection{Configuration}
+
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=sys.config]
+    [{n2o, [{port,8000},
+            {app,review},
+            {route,routes},
+            {mq,n2o_mq},
+            {json,jsone},
+            {log_modules,config},
+            {log_level,config},
+            {log_backend,n2o_log},
+            {session,n2o_session},
+            {origin,<<"*">>},
+            {bridge,n2o_cowboy},
+            {pickler,n2o_pickle},
+            {erroring,n2o_error}]},
+     {kvs, [{dba,store_mnesia},
+            {schema, [kvs_user,
+                      kvs_acl,
+                      kvs_feed,
+                      kvs_subscription ]} ]} ].
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+
+\subsection*{Ports}
+N2O uses two ports for WebSocket and HTTP connections.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    wf:config(n2o,port,443)
+    wf:config(n2o,websocket_port,443)
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+If you use server pages mode N2O will render HTML with nessesary ports values.
+For single page application mode you should redefine these ports inside the template:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    <script> var transition = { pid: '',
+                                host: 'localhost',
+                                port: '443' }; </script>
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+
+\subsection*{Application}
+In {\bf app} setting you should place the name of your OTP application
+that will be treated by N2O and NITRO as a source for templates and
+other static data with {\bf code:priv\_dir}.
+
+\subsection*{Routes}
+Setting {\bf route} is a place for the name of Erlang module where
+resides mappings from URL to page modules.
+
+\subsection*{Logging}
+N2O supports logging API and you can plug different logging module.
+It ships with {\bf n2o\_io} and {\bf n2o\_log} modules which you can set in the
+{\bf log\_backend} option. This is how logging looks like in N2O:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    wf:info(index,"Message: ~p",[Message]),
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+First argument is a module from which function is being called. By using this
+N2O can filter all log messages with special filter settled with {\bf log\_modules} option.
+It says in which Erlang module function {\bf log\_modules/0} exists that
+returns allowed Erlang modules to log. Option {\bf log\_level} which specified
+in a similar way, it specifies the module with function {\bf log\_level/0} that could
+return one of {\bf none}, {\bf error}, {\bf warning} or {\bf info} atom values which
+means different error log levels.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    -module(config).
+    -compile(export_all).
+
+    log_level() -> info.
+    log_modules() -> [ login, index ].
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\newpage
+\subsection*{Message Queue}
+In {\bf mq} settings you should place the name of Erlang module which supports
+message queue API. By default N2O provides {\bf n2o\_mq} module.
+
+\subsection*{Formatter}
+With {\bf formatter} option you may set the WebSocket channel
+termination formatter such as {\bf bert} or {\bf json}. If you will select json as formatter
+you may want also to chose a json encoder in {\bf json} option. By default in n2o enabled
+{\bf json} formatter and {\bf jsone} encoder. The main reason is that {\bf jsone} is
+written in pure erlang idiomatic code and is ready to run on LING without C NIF linkage.
+But you may want to switch to {\bf jsonx} on BEAM or whatever.
+
+\subsection*{Minimal Page}
+And then add a minimal {\bf index.erl} page:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}[caption=index.erl]
+    -module(index).
+    -compile(export_all).
+    -include_lib("nitro/include/nitro.hrl").
+
+    main() -> #span{body="Hello"}.
+\end{lstlisting}
+\vspace{1\baselineskip}

+ 137 - 0
doc/synrc.tex

@@ -0,0 +1,137 @@
+% Copyright (c) 2010 Synrc Research Center
+
+\usepackage{afterpage}
+
+\usepackage{hevea}
+\usepackage[english]{babel}
+\usepackage{palatino}
+\usepackage{graphicx}
+\usepackage{tocloft}
+\usepackage{cite}
+\usepackage[utf8]{inputenc}
+\usepackage{moreverb}
+\usepackage{listings}
+\usepackage[none]{hyphenat}
+\usepackage{caption}
+\usepackage[usenames,dvipsnames]{color}
+\usepackage[top=18mm, bottom=25.4mm,
+            inner=16mm,outer=18mm,
+            paperwidth=142mm, paperheight=200mm]{geometry}
+
+%left=18mm, right=18mm,
+
+% \usepackage[hmarginratio=3:2]{geometry}
+
+\hyphenation{framework nitrogen javascript facebook streaming JavaScript micro-frameworks}
+
+\setlength{\cftsubsecnumwidth}{2.5em}
+
+% include image for HeVeA and LaTeX
+
+\makeatletter
+\def\@seccntformat#1{\llap{\csname the#1\endcsname\hskip0.7em\relax}}
+\makeatother
+
+\newcommand{\includeimage}[2]
+{\ifhevea
+    {\imgsrc{#1}}
+\else{
+    \begin{figure}[h!]
+    \centering
+    \includegraphics[width=\textwidth]{#1}
+    \caption{#2}
+    \end{figure}}
+\fi}
+
+% HeVeA header
+
+{\ifhevea
+  \let\oldmeta=\@meta
+  \renewcommand{\@meta}{%
+  \oldmeta
+  \begin{rawhtml}
+  <meta name="Author" content="Maxim Sokhatsky">
+  <meta http-equiv="expires" content="Tue, 01 Jan 2020 1:00:00 GMT" />
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=IE10,chrome=1" />
+  \end{rawhtml}}
+
+  \htmlhead{\rawhtmlinput{templates/head-hevea.htx}}
+  \htmlfoot{\rawhtmlinput{templates/foot.htx}}
+  \footerfalse
+\fi}
+
+% title page for N2O
+
+\newcommand*{\titleWF}
+{
+    \begingroup
+   \thispagestyle{empty}
+    \hbox{
+        \hspace*{0.2\textwidth}
+        \rule{1pt}{\textheight}
+        \hspace*{0.05\textwidth}
+        \parbox[b]{0.75\textwidth}
+        {
+            {\noindent\Huge \bfseries N2O}\\[2\baselineskip]
+            {\LARGE \textsc{
+                No Bullshit\\[0.5\baselineskip]
+                Sane Framework\\[0.5\baselineskip]
+                for Wild Web}}\\[4\baselineskip]
+            \vspace{0.5\textheight}
+%            {\Large \textit{Maxim Sokhatsky}}\\[2\baselineskip]
+%            {\large {\bf {\color{Blue}syn}{\color{OrangeRed}rc} research center}
+%            {\copyright} 2013-2014}\\[1\baselineskip]
+        }
+    }
+    \endgroup
+}
+
+% define images store
+
+\graphicspath{{./images/}}
+
+% start each section from new page
+
+\let\stdsection\section
+\renewcommand\section{\newpage\stdsection}
+
+% define style for code listings
+
+\lstset{
+    backgroundcolor=\color{white},
+    keywordstyle=\color{blue},
+    basicstyle=\bf\ttfamily\footnotesize,
+    columns=fixed}
+
+%\headsep = 0cm
+%\voffset = -1.5cm
+%\hoffset = -0.7cm
+%\topmargin = 0cm
+%\textwidth = 12cm
+%\textheight = 17.5cm
+%\footskip = 1cm
+\parindent = 0cm
+
+\hyphenpenalty=5000
+  \tolerance=1000
+
+\newenvironment{dedication}
+{
+   \thispagestyle{empty}
+   \cleardoublepage
+   \thispagestyle{empty}
+   \vspace*{\stretch{1}}
+   \hfill\begin{minipage}[t]{0.66\textwidth}
+   \raggedright
+}%
+{
+   \end{minipage}
+   \vspace*{\stretch{3}}
+   \clearpage
+}
+
+\newcommand\blankpage{
+    \null
+    \thispagestyle{empty}
+    \newpage}

+ 52 - 0
doc/utf8.tex

@@ -0,0 +1,52 @@
+\section{UTF-8}
+
+\subsection{Erlang}
+
+The main thing you should know about Erlang unicode is that
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    unicode:characters_to_binary("Uni") == <<"Uni"/utf8>>.
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+I.e. in N2O DSL you should use:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    #button{body= <<"Unicode Name"/utf8>>}
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+\subsection{JavaScript}
+
+Whenever you want to send to server the value from DOM element
+you should use utf8\_toByteArray.
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > utf8_toByteArray(document.getElementById('phone').value);
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+However we created shortcut for that purposes which knows
+about radio, fieldset and other types of DOM nodes. So you should use just:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > querySource('phone');
+\end{lstlisting}
+\vspace{1\baselineskip}
+
+querySource JavaScript function ships in nitrogen.js which is part
+of N2O JavaScript library.
+
+\paragraph{}
+Whenever you get unicode data from server you should prepare it before place
+in DOM with utf8\_dec:
+
+\vspace{1\baselineskip}
+\begin{lstlisting}
+    > console.log(utf8_dec(receivedMessage));
+\end{lstlisting}
+\vspace{1\baselineskip}

+ 17 - 0
doc/web/actions.tex

@@ -0,0 +1,17 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\begin{document}
+\title{N2O Actions}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\input{../actions}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}
+

+ 16 - 0
doc/web/api.tex

@@ -0,0 +1,16 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\begin{document}
+\title{N2O API}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../api}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}

+ 17 - 0
doc/web/elements.tex

@@ -0,0 +1,17 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\begin{document}
+\title{N2O Elements}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\input{../elements}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}
+

+ 18 - 0
doc/web/endpoints.tex

@@ -0,0 +1,18 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\renewcommand{\images}{http://synrc.com/apps/n2o/doc}
+\begin{document}
+\title{N2O Endpoints}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../endpoints}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}
+

+ 18 - 0
doc/web/handlers.tex

@@ -0,0 +1,18 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\renewcommand{\images}{http://synrc.com/apps/n2o/doc}
+\begin{document}
+\title{N2O Handlers}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../handlers}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}
+

+ 18 - 0
doc/web/index.tex

@@ -0,0 +1,18 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\renewcommand{\images}{http://synrc.com/apps/n2o/doc/images}
+\begin{document}
+\title{N2O}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../index}
+%HEVEA \rawhtmlinput{templates/slides.htx}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}

+ 19 - 0
doc/web/last.tex

@@ -0,0 +1,19 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../synrc.css}
+\begin{document}
+\title{N2O History}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../last}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\begin{rawhtml}
+<script type="text/javascript" src="http://synrc.com/hi.js"></script>
+\end{rawhtml}
+\end{document}

+ 16 - 0
doc/web/macros.tex

@@ -0,0 +1,16 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\begin{document}
+\title{N2O JavaScript}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../macros}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}

+ 16 - 0
doc/web/packages.tex

@@ -0,0 +1,16 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\begin{document}
+\title{N2O Packaging}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../packages}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}

+ 16 - 0
doc/web/persistence.tex

@@ -0,0 +1,16 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\begin{document}
+\title{N2O Persistence}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../persistence}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}

+ 17 - 0
doc/web/processes.tex

@@ -0,0 +1,17 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\renewcommand{\images}{http://synrc.com/apps/n2o/doc}
+\begin{document}
+\title{N2O Processes}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../processes}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}

+ 17 - 0
doc/web/protocols.tex

@@ -0,0 +1,17 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../../../synrc.css}
+\renewcommand{\images}{http://synrc.com/apps/n2o/doc/images}
+\begin{document}
+\title{N2O Protocols}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../protocols}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\end{document}

+ 19 - 0
doc/web/setup.tex

@@ -0,0 +1,19 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../synrc.css}
+\begin{document}
+\title{N2O Setup}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../setup}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\begin{rawhtml}
+<script type="text/javascript" src="http://synrc.com/hi.js"></script>
+\end{rawhtml}
+\end{document}

+ 19 - 0
doc/web/toc.tex

@@ -0,0 +1,19 @@
+%HEVEA \begin{divstyle}{toc}
+\section*{TOC}
+\paragraph{}
+\footahref{http://synrc.com/apps/n2o/doc/web}{1. Framework} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/setup.htm}{2. Setup} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/processes.htm}{3. Processes} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/endpoints.htm}{4. Endpoints} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/handlers.htm}{5. Handlers} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/protocols.htm}{6. Protocols} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/api.htm}{7. API} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/elements.htm}{8. Elements} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/actions.htm}{9. Actions} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/macros.htm}{10. JavaScript} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/utf8.htm}{11. UTF-8} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/packages.htm}{12. Packages} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/persistence.htm}{13. Persistence} \@br
+\footahref{http://synrc.com/apps/n2o/doc/web/last.htm}{14. History} \@br
+\footahref{http://synrc.com/apps/n2o/doc/book.pdf}{Download PDF} \@br
+%HEVEA \end{divstyle}

+ 19 - 0
doc/web/utf8.tex

@@ -0,0 +1,19 @@
+\input{../../../../synrc.hva}
+%HEVEA \loadcssfile{../../synrc.css}
+\begin{document}
+\title{N2O UTF8}
+\author{Maxim Sokhatsky}
+%HEVEA \begin{divstyle}{nonselectedwrapper}
+%HEVEA \begin{divstyle}{article}
+\input{toc}
+%HEVEA \begin{divstyle}{articlecol}
+\include{../utf8}
+%HEVEA \rawhtmlinput{templates/disqus.htx}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \end{divstyle}
+%HEVEA \begin{divstyle}{clear}{~}\end{divstyle}
+\begin{rawhtml}
+<script type="text/javascript" src="http://synrc.com/hi.js"></script>
+\end{rawhtml}
+\end{document}

+ 55 - 0
include/api.hrl

@@ -0,0 +1,55 @@
+-include("wf.hrl").
+
+% n2o types
+
+-type name() :: atom() | binary() | string().
+-type render() :: list() | binary() | tuple() | list(tuple()).
+-type wire_answer() :: undefined | list(tuple()).
+-type wiring() :: string() | tuple().
+
+% update
+
+-spec update(name(), render()) -> wire_answer().
+-spec insert_top(name(), render()) -> wire_answer().
+-spec insert_bottom(name(), render()) -> wire_answer().
+-spec insert_before(name(), render()) -> wire_answer().
+-spec insert_after(name(), render()) -> wire_answer().
+-spec remove(name()) -> wire_answer().
+
+% wire
+
+-spec wire(wiring()) -> wire_answer().
+
+% async
+
+-spec async(name(),fun()) -> {pid(),{async,{name(),any()}}}.
+-spec start(#handler{}) -> {pid(),{name(),any()}}.
+-spec stop(name()) -> any().
+-spec restart(name()) -> any().
+-spec flush() -> any().
+-spec flush(any()) -> any().
+
+% pickle
+
+-spec pickle(any()) -> binary().
+-spec depickle(binary()) -> any().
+
+% session
+
+-spec session(name()) -> any().
+-spec session(name(), any()) -> any().
+-spec user() -> any().
+-spec user(any()) -> any().
+
+% mq
+
+-spec send(name(), any()) -> {ok,pid()}.
+-spec reg(name(), any()) -> defined | undefined | any().
+-spec unreg(name()) -> skip | undefined | any().
+
+% query
+
+-spec q(name()) -> any().
+-spec qc(name()) -> any().
+-spec qp(name()) -> any().
+

+ 45 - 0
include/wf.hrl

@@ -0,0 +1,45 @@
+-ifndef(N2O_HRL).
+-define(N2O_HRL, true).
+
+-record(handler, { name, module, class, group, config, state}).
+-record(cx,      { handlers, actions, req, module, lang, path, session, formatter=false, params, form, state=[] }).
+
+-define(CTX, (get(context))).
+-define(REQ, (get(context))#cx.req).
+
+% API
+
+-define(HANDLER_API, [init/2, finish/2]).
+-define(FAULTER_API, [error_page/2]).
+-define(ROUTING_API, [init/2, finish/2]).
+-define(QUERING_API, [init/2, finish/2]).
+-define(SESSION_API, [init/2, finish/2, get_value/2, set_value/2, clear/0]).
+-define(PICKLES_API, [pickle/1, depickle/1]).
+-define(MESSAGE_API, [send/2, reg/1, reg/2, unreg/1]).
+
+% IO protocol
+
+-record(io,      { eval, data }).
+-record(bin,     { data }).
+
+% Client/Server protocol
+
+-record(client,  { data }).
+-record(server,  { data }).
+
+% Nitrogen Protocol
+
+-record(pickle,  { source, pickled, args }).
+-record(flush,   { data }).
+-record(direct,  { data }).
+-record(ev,      { module, msg, trigger, name }).
+
+% File Transfer Protocol
+
+-record(ftp,     { id, sid, filename, meta, size, offset, block, data, status }).
+
+% HTTP
+
+-record(http,     { url, method, body, headers = [] }).
+
+-endif.

+ 27 - 0
package.exs

@@ -0,0 +1,27 @@
+defmodule N2O.Mixfile do
+  use Mix.Project
+
+  def project do
+    [app: :n2o,
+     version: "4.4.0",
+     description: "N2O Application Server",
+     package: package,
+     deps: deps]
+  end
+
+  def application do
+    [mod: {:n2o, []}]
+  end
+
+  defp package do
+    [files: ["include", "priv", "samples", "src", "LICENSE", "README.md", "rebar.config"],
+     licenses: ["MIT"],
+     maintainers: ["Andy Martemyanov", "Namdak Tonpa"],
+     name: :n2o,
+     links: %{"GitHub" => "https://github.com/synrc/n2o"}]
+  end
+
+  defp deps do
+     [{:ex_doc, ">= 0.0.0", only: :dev}]
+  end
+end

+ 34 - 0
priv/bullet.js

@@ -0,0 +1,34 @@
+
+// WebSocket Transport
+
+$ws = { heart: true, interval: 4000,
+        creator: function(url) { return window.WebSocket ? new window.WebSocket(url) : false; },
+        onheartbeat: function() { this.channel.send('PING'); } };
+
+// N2O Reliable Connection
+
+$conn = { onopen: nop, onmessage: nop, onclose: nop, onconnect: nop,
+          send:  function(data)   { if (this.port.channel) this.port.channel.send(data); },
+          close: function()       { if (this.port.channel) this.port.channel.close(); } };
+
+ct = 0;
+transports = [ $ws ];
+heartbeat = null;
+reconnectDelay = 1000;
+maxReconnects = 100;
+
+function nop() { }
+function bullet(url) { $conn.url = url; return $conn; }
+function xport() { return maxReconnects <= ct ? false : transports[ct++ % transports.length]; }
+function reconnect() { setTimeout(function() { connect(); }, reconnectDelay); }
+function next() { $conn.port = xport(); return $conn.port ? connect() : false; }
+function connect() {
+    $conn.port.channel = $conn.port.creator($conn.url);
+    if (!$conn.port.channel) return next();
+    $conn.port.channel.onmessage = function(e) { $conn.onmessage(e); };
+    $conn.port.channel.onopen = function() {
+        if ($conn.port.heart) heartbeat = setInterval(function(){$conn.port.onheartbeat();}, $conn.port.interval);
+        $conn.onopen();
+        $conn.onconnect(); };
+    $conn.port.channel.onclose = function() { $conn.onclose(); clearInterval(heartbeat); reconnect(); };
+    return $conn; }

+ 79 - 0
priv/ftp.js

@@ -0,0 +1,79 @@
+try { module.exports = {ftp:ftp}; } catch (e) { }
+
+// N2O File Transfer Protocol
+
+var ftp = {
+    queue: [],
+    init:  function(file) {
+        var item = {
+            id:        performance.now().toString(),
+            status:    'init',
+            autostart: ftp.autostart || false,
+            name:      ftp.filename || file.name,
+            sid:       ftp.sid || co(session),
+            meta:      ftp.meta || bin(''),
+            offset:    ftp.offset || 0,
+            block:     1,
+            total:     file.size,
+            file:      file
+        };
+        ftp.queue.push(item);
+        ftp.send(item, '', 1);
+        return item.id;
+    },
+    start: function(id) {
+        if(ftp.active) { id && ( ftp.item(id).autostart = true ); return false; }
+        var item = id ? ftp.item(id) : ftp.next();
+        if(item) { ftp.active = true; ftp.send_slice(item); }
+    },
+    stop:  function(id) {
+        var item = ftp.item(id);
+        var index = ftp.queue.indexOf(item);
+        ftp.queue.splice(index, 1);
+        ftp.active = false;
+        ftp.start();
+    },
+    send:  function(item, data) {
+        ws.send(enc(tuple(atom('ftp'),
+            bin(item.id),
+            bin(item.sid),
+            bin(item.name),
+            item.meta,
+            number(item.total),
+            number(item.offset),
+            number(item.block || data.byteLength),
+            bin(data),
+            bin(item.status || 'send')
+            ))); },
+    send_slice: function(item) {
+        this.reader = new FileReader();
+        this.reader.onloadend = function(e) {
+            var res = e.target, data = e.target.result;
+            if(res.readyState === FileReader.DONE && data.byteLength > 0) ftp.send(item,data);
+         };
+        this.reader.readAsArrayBuffer(item.file.slice(item.offset, item.offset + item.block)); },
+    item: function(id) { return ftp.queue.find(function(item){ return item && item.id === id; }); },
+    next: function() { return ftp.queue.find(function(next){ return next && next.autostart }); }
+};
+
+$file.do = function(rsp) {
+    var offset = rsp.v[6].v, block = rsp.v[7].v, status = utf8_dec(rsp.v[9].v);
+    switch (status) {
+        case 'init':
+            var item = ftp.item(utf8_dec(rsp.v[1].v));
+            item.offset = offset;
+            item.block = block;
+            item.name = utf8_dec(rsp.v[3].v);
+            item.status = undefined;
+            if(item.autostart) ftp.start(item.id);
+            break;
+        case 'send':
+            var x = qi('ftp_status'); if(x) x.innerHTML = offset;
+            var item = ftp.item(utf8_dec(rsp.v[1].v));
+            item.offset = offset;
+            item.block = block;
+            (block > 0 && ftp.active) ? ftp.send_slice(item) : ftp.stop(item.id)
+            break;
+        case 'relay': if (typeof ftp.relay === 'function') ftp.relay(rsp); break;
+    }
+};

+ 79 - 0
priv/http.js

@@ -0,0 +1,79 @@
+try { module.exports = {http:http}; } catch (e) { }
+
+// Template: http.send(url + '?' + 'test1=1&test2=2', 'GET', '', {SomeHeader:'some header'}).done(function(data, status, headers) {console.log(data););
+
+var http = {
+    receiveData:null,
+    doneCallback:function(){},
+    failCallback:function(){},
+    settings:{},
+    send: function(settings) {
+        var $ = this;
+        var defSett = {
+            url:window.location.href,
+            method:'GET',
+            body:'',
+            headers:{},
+            returnType:'html',
+        };
+        $.settings = {};
+        if (typeof settings === 'string') {
+            $.settings = $.merge(defSett, {url:settings});
+        } else {
+            $.settings = $.merge(defSett, settings);
+        }
+        if (/^\/\//.test($.settings.url)) {
+            $.settings.url = window.location.protocol + $.settings.url
+        }
+        if (!/^http[s]*\:\/\//.test($.settings.url)) {
+            $.settings.url = window.location.origin + $.settings.url
+        }
+        var tList = [];
+        if ($.settings.headers) {
+            for (var prop in $.settings.headers) {
+                tList.push(tuple(bin(prop),bin($.settings.headers[prop])));
+            }
+        }
+        ws.send(enc(tuple(atom('http'),
+            bin($.settings.url),
+            bin($.settings.method||'GET'),
+            bin($.settings.body||''),
+            tList)
+        ));
+        return $;
+    },
+    back: function(d, s, h) {
+        if (s >= 400) {
+            this.failCallback(d, s, h);
+        } else {
+            if (this.settings.returnType === 'json') {
+                try {
+                    d = JSON.parse(this.escapeSpecialChars(d));
+                } catch (e) {
+                    if (debug) console.error('Unexpected string for JSON');
+                };
+            }
+            this.doneCallback(d, s, h);
+        }
+    },
+    done: function(callback) {
+        this.doneCallback = callback;
+        return this;
+    },
+    fail: function(callback) {
+        this.failCallback = callback;
+        return this;
+    },
+    merge: function(obj, src) {
+        var newO = {};
+        Object.keys(obj).forEach(function(key){newO[key] = obj[key];});
+        Object.keys(src).forEach(function(key){newO[key] = src[key];});
+        return newO;
+    },
+    escapeSpecialChars: function(jsonString) {
+        return jsonString.replace(/\n/g, "\\n")
+            .replace(/\r/g, "\\r")
+            .replace(/\t/g, "\\t")
+            .replace(/\f/g, "\\f");
+    }
+};

+ 52 - 0
priv/n2o.js

@@ -0,0 +1,52 @@
+
+// N2O CORE
+
+var active      = false,
+    debug       = false,
+    session     = "site-sid",
+    protocol    = window.location.protocol == 'https:' ? "wss://" : "ws://",
+    querystring = window.location.pathname + window.location.search,
+    host        = null == transition.host ? window.location.hostname : transition.host,
+    port        = null == transition.port ? window.location.port : transition.port;
+
+function N2O_start() {
+    ws = new bullet(protocol + host + (port==""?"":":"+port) + "/ws" + querystring);
+    ws.onmessage = function (evt) { // formatters loop
+    for (var i=0;i<protos.length;i++) { p = protos[i]; if (p.on(evt, p.do).status == "ok") return; } };
+    ws.onopen = function() { if (!active) { console.log('Connect'); ws.send('N2O,'+transition.pid); active=true; } };
+    ws.onclose = function() { active = false; console.log('Disconnect'); }; next(); }
+
+function qi(name) { return document.getElementById(name); }
+function qs(name) { return document.querySelector(name);  }
+function qn(name) { return document.createElement(name);  }
+function is(x,num,name) { return x.t==106?false:(x.v.length === num && x.v[0].v === name); }
+function co(name) { match=document.cookie.match(new RegExp(name+'=([^;]+)')); return match?match[1]:undefined; }
+
+/// N2O Protocols
+
+var $io = {}; $io.on = function onio(r, cb) { if (is(r,3,'io')) {
+    try { eval(utf8_dec(r.v[1].v)); if (typeof cb == 'function') cb(r); return { status: "ok" }; }
+    catch (e) { console.log(e); return { status: '' }; } } else return { status: '' }; }
+
+var $file = {}; $file.on = function onfile(r, cb) { if (is(r,10,'ftp')) {
+    if (typeof cb == 'function') cb(r); return { status: "ok" }; } else return { status: ''}; }
+
+var $bin = {}; $bin.on = function onbin(r, cb) { if (is(r,2,'bin')) {
+    if (typeof cb == 'function') cb(r); return { status: "ok" }; } else return { status: '' }; }
+
+// BERT Formatter
+
+var $bert = {}; $bert.protos = [$io,$bin,$file]; $bert.on = function onbert(evt, cb) {
+    if (Blob.prototype.isPrototypeOf(evt.data) && (evt.data.length > 0 || evt.data.size > 0)) {
+        var r = new FileReader();
+        r.addEventListener("loadend", function() {
+            try { erlang = dec(r.result);
+                  if (debug) console.log(JSON.stringify(erlang));
+                  if (typeof cb  == 'function') cb(erlang);
+                  for (var i=0;i<$bert.protos.length;i++) {
+                    p = $bert.protos[i]; if (p.on(erlang, p.do).status == "ok") return; }
+            } catch (e) { console.log(e); } });
+        r.readAsArrayBuffer(evt.data);
+        return { status: "ok" }; } else return { status: "error", desc: "data" }; }
+
+var  protos = [ $bert ];

+ 53 - 0
priv/protocols/bert.js

@@ -0,0 +1,53 @@
+try { module.exports = {dec:dec,enc:enc}; } catch (e) { }
+
+// BERT Encoder
+
+function uc(u1,u2) { if (u1.byteLength == 0) return u2; if (u2.byteLength == 0) return u1;
+                     var a = new Uint8Array(u1.byteLength + u2.byteLength);
+                     a.set(u1, 0); a.set(u2, u1.byteLength); return a; };
+function ar(o)     { return o.v instanceof ArrayBuffer ? new Uint8Array(o.v) : o.v instanceof Uint8Array ? o.v :
+                     Array.isArray(o.v) ? new Uint8Array(o.v) : new Uint8Array(utf8_toByteArray(o.v).v);}
+function fl(a)     { return a.reduce(function(f,t){ return uc(f, t instanceof Uint8Array ? t :
+                     Array.isArray(t) ? fl(t) : new Uint8Array([t]) ); }, new Uint8Array()); }
+function atom(o)   { return {t:100,v:utf8_toByteArray(o).v}; }
+function bin(o)    { return {t:109,v:o instanceof ArrayBuffer ? new Uint8Array(o) : o instanceof Uint8Array ? o : utf8_toByteArray(o).v}; }
+function tuple()   { return {t:104,v:Array.apply(null,arguments)}; }
+function list()    { return {t:108,v:Array.apply(null,arguments)}; }
+function number(o) { return {t:98,v:o}; }
+function enc(o)    { return fl([131,ein(o)]); }
+function ein(o)    { return Array.isArray(o)?en_108({t:108,v:o}):eval('en_'+o.t)(o); }
+function en_undefined(o) { return [106]; }
+function en_98(o)  { return [98,o.v>>>24,(o.v>>>16)&255,(o.v>>>8)&255,o.v&255]; }
+function en_97(o)  { return [97,o.v];}
+function en_106(o) { return [106];}
+function en_100(o) { return [100,o.v.length>>>8,o.v.length&255,ar(o)]; }
+function en_107(o) { return [107,o.v.length>>>8,o.v.length&255,ar(o)];}
+function en_104(o) { var l=o.v.length,r=[]; for(var i=0;i<l;i++)r[i]=ein(o.v[i]); return [104,l,r]; }
+function en_109(o) { var l=o.v instanceof ArrayBuffer ? o.v.byteLength : o.v.length;
+                     return[109,l>>>24,(l>>>16)&255,(l>>>8)&255,l&255,ar(o)]; }
+function en_108(o) { var l=o.v.length,r=[]; for(var i=0;i<l;i++)r.push(ein(o.v[i]));
+                     return o.v.length==0?[106]:[108,l>>>24,(l>>>16)&255,(l>>>8)&255,l&255,r,106]; }
+
+// BERT Decoder
+
+function nop(b) { return []; };
+function big(b) { var sk=b==1?sx.getUint8(ix++):sx.getInt32((a=ix,ix+=4,a));
+                  var ret=0, sig=sx.getUint8(ix++), count=sk;
+                  while (count-->0) {
+                    ret = 256 * ret + sx.getUint8(ix+count)
+                  }
+                  ix += sk;
+                  return ret*(sig==0?1:-1);
+                }
+function int(b) { return b==1?sx.getUint8(ix++):sx.getInt32((a=ix,ix+=4,a)); };
+function dec(d) { sx=new DataView(d);ix=0; if(sx.getUint8(ix++)!==131)throw("BERT?"); return din(); };
+function str(b) { var dv,sz=(b==2?sx.getUint16(ix):sx.getInt32(ix));ix+=b;
+                  var r=sx.buffer.slice(ix,ix+=sz); return b==2?utf8_dec(r):r; };
+function run(b) { var sz=(b==1?sx.getUint8(ix):sx.getUint32(ix)),r=[]; ix+=b;
+                  for(var i=0;i<sz;i++) r.push(din()); if(b==4)ix++; return r; };
+function din()  { var c=sx.getUint8(ix++),x; switch(c) { case 97: x=[int,1];break;
+                  case 98:  x=[int,4]; break; case 100: x=[str,2]; break;
+                  case 110: x=[big,1]; break; case 111: x=[big,4]; break;
+                  case 104: x=[run,1]; break; case 107: x=[str,2]; break;
+                  case 108: x=[run,4]; break; case 109: x=[str,4]; break;
+                  default:  x=[nop,0]; } return {t:c,v:x[0](x[1])};};

+ 12 - 0
priv/protocols/client.js

@@ -0,0 +1,12 @@
+
+// JSON formatter
+
+var $client = {};
+$client.on = function onclient(evt, callback) {
+    try {  msg = JSON.parse(evt.data);
+           if (debug) console.log(JSON.stringify(msg));
+           if (typeof callback == 'function' && msg) callback(msg);
+           for (var i=0;i<$bert.protos.length;i++) {
+                p = $bert.protos[i]; if (p.on(msg, p.do).status == "ok") return { status: "ok"}; }
+    } catch (ex) { return { status: "error" }; }
+    return { status: "ok" }; };

+ 30 - 0
priv/protocols/nitrogen.js

@@ -0,0 +1,30 @@
+// Nitrogen Compatibility Layer
+
+function querySourceRaw(Id) {
+    var val, el = document.getElementById(Id);
+    if (!el) return "";
+    switch (el.tagName) {
+        case 'FIELDSET': val = document.querySelector('[id="' + Id + '"] :checked');
+                         val = val ? val.value : ""; break;
+        case 'INPUT':
+            switch (el.getAttribute("type")) {
+                case 'radio': case 'checkbox': val = el.checked ? el.value : ""; break;
+                case  'date': val = new Date(Date.parse(el.value)) || ""; break;
+                case  'calendar': val = pickers[el.id]._d || ""; break;  //only 4 nitro #calendar{}
+                default:     var edit = el.contentEditable;
+                    if (edit && edit === 'true') val = el.innerHTML;
+                    else val = el.value; }
+            break;
+        default: var edit = el.contentEditable;
+            if (edit && edit === 'true') val = el.innerHTML;
+            else val = el.value; }
+    return val; }
+
+function querySource(Id) {
+    var qs = querySourceRaw(Id);
+    if(qs instanceof Date) { return tuple(number(qs.getFullYear()),number(qs.getMonth()+1),number(qs.getDate())); }
+    else { return utf8_toByteArray(qs); } }
+
+(function() {
+    window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
+        window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; })();

+ 23 - 0
priv/template.js

@@ -0,0 +1,23 @@
+
+// N2O Simple Template
+
+function template(html, data) { // template("{this.name}",{name:"Maxim"})
+    var re = /{([^}]+)?}/g, code = 'var r=[];', cursor = 0;
+    var add = function(line,js) {
+        js? (code += 'r.push(' + line + ');') :
+            (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");' : ''); // "
+        return add; }
+    while(match = re.exec(html)) {
+        add(html.slice(cursor, match.index))(match[1],true);
+        cursor = match.index + match[0].length; }
+    add(html.substr(cursor, html.length - cursor));
+    code += 'return r.join("");';
+    return new Function(code.replace(/[\r\t\n]/g, '')).apply(data); }
+
+function xml(html) { return new DOMParser().parseFromString(html, "application/xhtml+xml").firstChild; }
+function dom(html) {
+    try { return new DOMParser().parseFromString(html, "text/html").firstChild.getElementsByTagName("body")[0].firstChild; } 
+    catch (ex) { var temp = document.createElement("DIV");
+                     temp.innerHTML = html;
+                     return temp.firstChild; }
+}

+ 21 - 0
priv/utf8.js

@@ -0,0 +1,21 @@
+try { module.exports = {dec:utf8_dec,enc:utf8_toByteArray}; } catch (e) { }
+
+// N2O UTF-8 Support
+
+function utf8_toByteArray(str) {
+    var byteArray = [];
+    if (str !== undefined && str !== null)
+    for (var i = 0; i < str.length; i++)
+        if (str.charCodeAt(i) <= 0x7F) byteArray.push(str.charCodeAt(i));
+        else {
+            var h = encodeURIComponent(str.charAt(i)).substr(1).split('%');
+            for (var j = 0; j < h.length; j++) byteArray.push(parseInt(h[j], 16)); }
+    return {t:107,v:byteArray}; };
+
+function utf8_dec(ab) {
+    if (!(ab instanceof ArrayBuffer)) ab = new Uint8Array(utf8_toByteArray(ab).v).buffer;
+    var t=new DataView(ab),i=c=c1=c2=0,itoa=String.fromCharCode,s=[]; while (i<t.byteLength ) {
+    c=t.getUint8(i); if (c<128) { s+=itoa(c); i++; } else
+    if ((c>191) && (c<224)) { c2=t.getUint8(i+1); s+=itoa(((c&31)<<6)|(c2&63)); i+=2; }
+    else { c2=t.getUint8(i+1); c3=t.getUint8(i+2); s+=itoa(((c&15)<<12)|((c2&63)<<6)|(c3&63));
+    i+=3; } } return s; }

+ 22 - 0
priv/validation.js

@@ -0,0 +1,22 @@
+
+// N2O Validation
+
+function validateSources(list) {
+    return list.reduce(function(acc,x) {
+        var event = new CustomEvent('validation');
+            event.initCustomEvent('validation',true,true,querySourceRaw(x));
+        var el = qi(x),
+            listener = el && el.validation,
+            res = !listener || listener && el.dispatchEvent(event);
+        console.log(res);
+        if (el) el.style.background = res ? '' : 'pink';
+        return res && acc; },true); }
+
+(function () {
+   function CustomEvent ( event, params ) {
+       params = params || { bubbles: false, cancelable: false, detail: undefined };
+       var evt = document.createEvent( 'CustomEvent' );
+       evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
+       return evt;  };
+  CustomEvent.prototype = window.Event.prototype;
+  window.CustomEvent = CustomEvent; })();

+ 23 - 0
priv/xhr.js

@@ -0,0 +1,23 @@
+
+// N2O XHR Fallback
+
+// WebSocket = undefined; // to test
+
+$xhr = { heart: false, interval: 100, creator: function(url) { $conn.url = xhr_url(url);
+         $xhr.channel = { send: xhr_send, close: xhr_close }; $conn.onopen();
+         return $xhr.channel; }, onheartbeat: function() { xhr('POST',{});} };
+
+transports = [$ws,$xhr];
+
+function xhr_header(request) { request.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=utf-8'); }
+function xhr_url(url) { return url.replace('ws:', 'http:').replace('wss:', 'https:'); }
+function xhr_close() { $conn.onclose(); clearInterval(heartbeat); }
+function xhr_send(data) { return xhr('POST',data); }
+function xhr_receive(data) { if (data.length != 0) $conn.onmessage({'data':data}); }
+function xhr(method,data) {
+    var request = new XMLHttpRequest();
+    request.open(method,$conn.url,true);
+    xhr_header(request);
+    request.onload = function() { console.log(request.response); xhr_receive(request.response); };
+    request.send(data);
+    return true; }

+ 8 - 0
rebar.config

@@ -0,0 +1,8 @@
+{lib_dirs,[".."]}.
+{deps_dir,["deps"]}.
+{deps,[
+ %  {nitro,  ".*", {git, "git://github.com/synrc/nitro.git",  {tag,"0.9"}}},
+ %  {jsone,  ".*", {git, "git://github.com/sile/jsone.git",   {tag,"v0.3.3"}}},
+ %  {gproc,  ".*", {git, "git://github.com/uwiger/gproc.git", {tag,"0.3"}}},
+    {cowboy, ".*", {git, "git://github.com/extend/cowboy",    {tag,"1.0.1"}}}
+]}.

+ 82 - 0
samples/README.md

@@ -0,0 +1,82 @@
+N2O: Erlang Web Framework on WebSockets
+=======================================
+
+![ScreenShot](http://synrc.com/images/n2o_sample_screen.png)
+
+Samples
+-------
+
+Samples provided as Erlang release packaged
+with *web* Erlang application which contains modules:
+
+* REST samples
+* N2O samples
+* XEN support
+
+Prerequisites
+-------------
+
+* erlang
+* mad
+* inotify-tools (Linux, for filesystem watching)
+* uglify (assets)
+
+Run
+---
+
+To run just perform on Windows, Linux and Mac
+
+    $ ./mad deps compile plan repl
+
+On BSD you should use gmake
+
+And open it in browser [http://localhost:8000](http://localhost:8000)
+If you want to try pure Single Page Application (SPA) wich
+connects to Erlang N2O Application Server you should use
+[http://localhost:8000/static/spa/spa.htm](http://localhost:8000/static/spa/spa.htm)
+
+For full features of make please refer to [https://github.com/synrc/otp.mk](https://github.com/synrc/otp.mk)
+
+Production Assets
+-----------------
+
+    $ ./mad static min
+    > application:set_env(n2o,mode,prod).
+
+Xen
+---
+
+To run on Xen is a bit tricky:
+
+    $ sudo apt-get install xen-hypervisor-amd64
+    $ echo XENTOOLSTACK=xl > /etc/default/xen
+
+Boot into Xen 4.2 Domain-0 and create network bridge:
+
+    $ sudo brctl addbr docker0
+    $ sudo ip addr add 172.16.42.1/24 dev docker0
+
+Compile Image at Erlang on Xen builder:
+
+    $ rebar get-deps compile
+    $ ./nitrogen_static.sh
+    $ rebar ling-build-image
+    $ sudo xl create -c xen.config
+
+Inside Ling start n2o_sample application:
+
+    Ling 0.2.2 is here
+    Started in 49438 us
+    Erlang [ling-0.2.2]
+
+    Eshell V5.10.2  (abort with ^G)
+    1> application:start(n2o_sample).
+
+And open it in browser [http://172.16.42.108:8000](http://172.16.42.108:8000)
+
+Credits
+-------
+
+* Maxim Sokhatsky
+
+OM A HUM

+ 3 - 0
samples/apps/rebar.config

@@ -0,0 +1,3 @@
+{sub_dirs, [ "review" ]}.
+{lib_dirs, ["../apps"]}.
+{deps_dir, ["../deps"]}.

+ 11 - 0
samples/apps/review/README.md

@@ -0,0 +1,11 @@
+Simple Review Application
+=========================
+
+Just leave a comment to a published file 
+or to a realm anonymously or on behalf.
+Create you own realms and files. 
+Discuss and Review with other users realtime.
+
+Credits
+-------
+* Brought to you by 5HT

+ 13 - 0
samples/apps/review/include/users.hrl

@@ -0,0 +1,13 @@
+-record(user, {id, name, email, proplist = [{facebook, udefined},
+                                            {github, "github.com/b0oh"},
+                                            {local, undefined},
+                                            {twitter, udefined}],
+               string = "common",
+               number = 12,
+               list_of_strings = ["one", "two", "three"],
+               list_of_numbers = [34958726345, 12],
+               nested_proplists = [{nested, [{number, 12},
+                                             {string, "common"},
+                                             {list_of_strings, ["one", "two", "three"]},
+                                             {list_of_atoms, [one, two, three]},
+                                             {list_of_numbers, [100000, 2,3 ]}]}]}).

+ 11 - 0
samples/apps/review/priv/snippets/lobby/bpmn.flow.htm

@@ -0,0 +1,11 @@
+<code>
+bpe:start(
+   #process{name="Order11",
+       flows=[ #sequenceFlow{source="begin",target="end2"},
+               #sequenceFlow{source="end2",target="end"}],
+       tasks=[ #userTask{name="begin"},
+               #userTask{name="end2"},
+               #endEvent{name="end"}],
+       task="begin",beginEvent="begin",endEvent="end"},[]).
+       
+</code>       

+ 15 - 0
samples/apps/review/priv/snippets/lobby/bpmn.htm

@@ -0,0 +1,15 @@
+<code>
+-record(task,         { name, id, roles, module }).
+-record(userTask,     { name, id, roles, module }).
+-record(serviceTask,  { name, id, roles, module }).
+-record(messageEvent, { name, id, payload }).
+-record(beginEvent ,   { name, id }).
+-record(endEvent,      { name, id }).
+-record(sequenceFlow, { name, id, source, target }).
+-record(history,      { ?ITERATOR(feed,true), name, task }).
+-record(process,      { ?ITERATOR(feed,true), name,
+                        roles=[], tasks=[], events=[], history=[], flows=[],
+                        rules, docs=[],
+                        task,
+                        beginEvent, endEvent }).
+</code>                        

+ 1 - 0
samples/apps/review/priv/snippets/lobby/erlang.processing.htm

@@ -0,0 +1 @@
+<img src="https://d324imu86q1bqn.cloudfront.net/uploads/asset/attachment/549899/optimized.jpg" width=600>

+ 18 - 0
samples/apps/review/priv/snippets/lobby/erlang.setup.htm

@@ -0,0 +1,18 @@
+<code>
+Erlang 6.2 
+msys2.github.com
+font14
+pacman -S make
+pacman -S git
+git clone https://github.com/synrc/skyline
+git clone https://github.com/synrc/mad
+cp mad/mad web 
+cd web
+./mad deps compile plan repl
+> ^C
+./mad bundle web_app
+cp web_app /install
+. /install/web_app
+>
+
+</code>

+ 30 - 0
samples/apps/review/priv/snippets/lobby/rebol.htm

@@ -0,0 +1,30 @@
+<h1> REBOL/View 2.7</h1>
+
+<code>
+
+REBOL [] view layout [ h2 "Demo" area btn "Click Me" ]
+
+the-form: layout [
+across space 2x8
+style label vtext bold 100x24 middle right
+style bar box 306x3 edge [size: 1x1 color: water effect: 'bevel]
+backcolor water bar return
+
+    label "Full Name"     f1: field 200 return
+    pad 120 c1: check vtext "Programmer" bold return
+    pad 120 c2: check vtext "Operator" bold return
+    label "Email Address" f2: field return
+    label "Phone Number"  f3: field return
+    label "Your Comments" f4: area wrap 200x100 return
+
+    bar return
+    pad 40 space 100
+    btn-enter 60 "Submit" [
+        unview/only the-form
+        show-results
+    ]
+    btn-cancel 60 "Close" [unview/only the-form]
+]
+the-form/effect: [gradient 0x1]
+
+</code>

+ 18 - 0
samples/apps/review/priv/snippets/n2o/template.htm

@@ -0,0 +1,18 @@
+<code>function template(html, data) { // template("{this.name}",{name:"Maxim"})
+    var re = /{([^}]+)?}/g, code = 'var r=[];', cursor = 0;
+    var add = function(line,js) {
+        js? (code += 'r.push(' + line + ');') :
+            (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");' : ''); // "
+        return add; }
+    while(match = re.exec(html)) {
+        add(html.slice(cursor, match.index))(match[1],true);
+        cursor = match.index + match[0].length; }
+    add(html.substr(cursor, html.length - cursor));
+    code += 'return r.join("");';
+    return new Function(code.replace(/[\r\t\n]/g, '')).apply(data); }
+
+function xml(html) { return new DOMParser().parseFromString(html, "application/xhtml+xml").firstChild; }
+function dom(html) {
+    var dom =  new DOMParser().parseFromString(html, "text/html")
+              .firstChild.getElementsByTagName("body")[0].firstChild; return dom; }
+</code>              

+ 29 - 0
samples/apps/review/priv/snippets/n2o/utf8.htm

@@ -0,0 +1,29 @@
+<h1>UTF-8</h1>
+
+<h2>Erlang</h2>
+	
+The main thing you should know about Erlang unicode is that
+<code>
+1> unicode:characters_to_binary("UniText") == <<"UniText"/utf8>>.
+</code>
+I.e. in N2O DSL you should use:
+<code>
+2> io:format("~tp",[ #button{body= <<"Unicode Name"/utf8>>} ]).
+</code>
+
+<h2>JavaScript</h2>
+Whenever you want to send to server the value from DOM element you should use utf8_toByteArray.
+
+<code>    
+> utf_toByteArray(document.getElementById('phone').value);
+</code>
+<p>However we created shortcut for that purposes which knows about radio, fieldset and other types of DOM nodes. So you should use just:</p>
+<code>
+> querySource('phone');
+</code>
+<p>querySource JavaScript function ships in nitrogen.js which is part of N2O JavaScript library.
+Whenever you get unicode data from server you should prepare it before place in DOM with utf8_decode:</p>
+<code>    
+> console.log(utf8_decode(receivedMessage));
+</code>
+    

+ 24 - 0
samples/apps/review/priv/static/N2O.svg

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="451px" height="567px" viewBox="0 0 451 567" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+    <!-- Generator: Sketch 3.0.4 (8054) - http://www.bohemiancoding.com/sketch -->
+    <title>Scene</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="N2O" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+        <g id="Scene" sketch:type="MSLayerGroup">
+            <rect id="Base" fill="#4990E2" sketch:type="MSShapeGroup" x="0" y="0" width="451" height="567"></rect>
+            <text id="N2O-2" sketch:type="MSTextLayer" font-family="Open Sans" font-size="130" font-weight="bold" letter-spacing="-1.10000014" fill="#FFFFFF">
+                <tspan x="85" y="184">N2O</tspan>
+            </text>
+            <text id="Web-Stack" sketch:type="MSTextLayer" font-family="Open Sans" font-size="48" font-weight="bold" fill="#FFFFFF">
+                <tspan x="101" y="477">Web Stack</tspan>
+            </text>
+            <g id="nitroxyde" transform="translate(95.000000, 222.000000)" stroke="#FFFFFF" stroke-width="10" sketch:type="MSShapeGroup">
+                <circle id="O" fill="#D0011B" cx="195" cy="65" r="65"></circle>
+                <circle id="N2" fill="#163D6B" cx="130" cy="65" r="65"></circle>
+                <circle id="N1" fill="#163D6B" cx="65" cy="65" r="65"></circle>
+            </g>
+            <rect id="Rectangle-2" fill="#FFFFFF" sketch:type="MSShapeGroup" x="94" y="393" width="264" height="18"></rect>
+        </g>
+    </g>
+</svg>

+ 14 - 0
samples/apps/review/priv/static/S.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="159px" height="155px" viewBox="0 0 159 155" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+    <!-- Generator: Sketch 3.1.1 (8761) - http://www.bohemiancoding.com/sketch -->
+    <title>g20</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+        <g id="g14" sketch:type="MSLayerGroup" transform="translate(331.250000, 254.093750) scale(1, -1) translate(-331.250000, -254.093750) translate(1.250000, 0.593750)" fill="#4990E2">
+            <g id="g20" transform="translate(-0.328375, 353.000000)" sketch:type="MSShapeGroup">
+                <path d="M55,1.25 L56.21875,9.81625 C56.62375,12.4575 56.87,15.1225 56.95125,17.81 C57.03,20.49625 57.115,26.1475 57.19625,34.765 L58.3275,35.9125 L63.74875,35.9125 L64.88125,34.77 C65.0525,27.645 65.48125,21.54375 66.17875,16.45375 C68.855,12.47 73.475,9.2275 80.04,6.72875 C86.605,4.22375 93.0775,2.97625 99.47,2.97625 C108.625,2.97625 115.945,5.4125 121.43,10.29625 C126.9125,15.17375 129.6575,20.995 129.6575,27.76 C129.6575,31.44125 128.66875,34.6275 126.695,37.32625 C124.71625,40.02 121.73875,42.24375 117.745,44 C113.755,45.755 106.6125,47.83 96.315,50.23375 C87.47375,52.285 81.2925,53.89125 77.77625,55.0475 C74.2525,56.2025 70.86125,58.14625 67.6025,60.88625 C64.34375,63.625 61.85,66.9875 60.13375,70.96625 C58.41875,74.94625 57.5625,79.3325 57.5625,84.13 C57.5625,95.5725 62.11875,104.7325 71.23375,111.60625 C80.34875,118.48 91.71625,121.91625 105.32625,121.91625 C111.05625,121.91625 117.30625,121.12625 124.07125,119.555 C130.83,117.9825 135.97,116.4725 139.48125,115.03125 L140.63625,113.2475 C139.8525,109.845 139.36625,100.91875 139.1725,86.47 L137.9775,85.32 L132.6825,85.32 L131.48625,86.47 C131.14375,91.66125 130.675,95.2975 130.08,97.385 C129.485,99.46625 127.9525,101.725 125.48875,104.14875 C123.01875,106.575 119.53,108.61625 115.025,110.27375 C110.5175,111.9325 105.755,112.7675 100.74125,112.7675 C95.5475,112.7675 91.19,111.995 87.6625,110.4575 C84.12875,108.9125 81.28,106.58 79.11375,103.45875 C76.94125,100.33625 75.86,96.54375 75.86,92.09625 C75.86,88.84125 76.52375,85.93125 77.855,83.36375 C79.1825,80.79125 81.22375,78.715 83.96875,77.13 C86.7125,75.5475 89.58375,74.41375 92.585,73.73375 L106.6125,70.1375 C118.62125,67.22125 127.215,64.69375 132.4075,62.555 C137.6,60.41625 141.6075,57.185 144.43875,52.86375 C146.9375,49.04875 148.2675,44.47375 148.5625,39.225 C154.1025,50.0125 157.24875,62.22875 157.24875,75.1925 C157.24875,118.73375 121.95,154.03 78.41125,154.03 C34.86,154.03 -0.43375,118.73375 -0.43375,75.1925 C-0.43375,39.5725 22.41,9.73 54.8325,-0.05 L55,1.25 L55,1.25 Z" id="path22"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 22 - 0
samples/apps/review/priv/static/feed.css

@@ -0,0 +1,22 @@
+
+// Feed UI styles
+
+.fd-label{}
+
+.fd-table{}
+.fd-tbody{}
+.fd-body{}
+
+.fd-traverse-ctl{}
+.fd-sel-toolbar{}
+
+.fd-alert{}
+.fd-entry-sel{}
+.fd-btn-delete{}
+.fd-trash{}
+.fd-btn-prev{}
+.fd-btn-next{}
+.fd-chevron-right{}
+.fd-checkbox{}
+
+.text-warning{}

+ 45 - 0
samples/apps/review/priv/static/spa/index.htm

@@ -0,0 +1,45 @@
+<html>
+    <head>
+        <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
+        <link href='/static/synrc.css' type='text/css' rel='stylesheet'>
+        <link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
+        <title>Chat</title>
+    </head>
+    <body>
+        <table cellpadding='20' align='center'>
+            <tr>
+                <td valign='top' align='center' height='230' width='300'>
+                    <br><img src='/static/S.svg' border='0' height='200' style='margin-top:0px;'><br>
+                </td>
+                <td colspan='2' width='700' valign='top'>
+                    <h1><b id='heading'>Roster</b></h1>
+                    <h2>Unique Feed<br><button id='logout'></button></h2>
+                </td>
+            </tr>
+            <tr>
+                <td width='300' valign='top'>
+                    <p>Just type what you think about this:</p>
+                    <textarea id='message' style='margin-left:5px; width:490px; font-size:16pt;'
+                      value='' rows='5' autofocus></textarea>
+                    <div id='upload'></div>
+                    <div id='history'></div>
+                    <button id=send>Send</body>
+                </td>
+            </tr>
+        </table>
+        <script>var transition = {pid: '', host: 'localhost', port:'8000'};</script>
+        <script src='/n2o/protocols/bert.js'></script>
+        <script src='/n2o/protocols/binary.js'></script>
+        <script src='/n2o/protocols/client.js'></script>
+        <script src='/n2o/protocols/nitrogen.js'></script>
+        <script src='/n2o/bullet.js'></script>
+        <script src='/n2o/xhr.js'></script>
+        <script src='/n2o/utf8.js'></script>
+        <script src='/n2o/template.js'></script>
+        <script src='/n2o/n2o.js'></script>
+        <script src='/n2o/validation.js'></script>
+        <script src='/n2o/ftp.js'></script>
+        <!--script src='https://synrc.com/hi.js'></script-->
+        <script>protos = [$client,$bert]; N2O_start();</script>
+    </body>
+</html>

+ 50 - 0
samples/apps/review/priv/static/spa/login.htm

@@ -0,0 +1,50 @@
+<html>
+    <head>
+        <meta name='viewport' content='initial-scale=0.5'>
+        <link href='/static/synrc.css' type='text/css' rel='stylesheet'>
+        <link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
+        <title>Login</title>
+    </head>
+    <body>
+
+        <table cellpadding='20' align='center'>
+            <tr>
+                <td valign='top' align='center' height='230' width='300'>
+                    <img src='/static/S.svg' border='0' height='200' style='margin-top:0px;'><br>
+                </td>
+                <td colspan='2' width='700' valign='top'>
+                    <h1><b>N2O</b></h1>
+                    <h2>Simple Review Application</h2>
+                </td>
+            </tr>
+            <tr>
+                <td width='300' valign='top'>
+                    <p>List of spawned feeds:</p>
+                    <div id='history' class='feed'></div><br><br>
+                </td>
+                <td colspan='2' width='700' valign='top' height='550' bgcolor='#eeeeee'>
+                    <h1>Anyname Login</h1>
+                    <span id="display"></span><br>
+                    <span>Login: </span>
+                    <input id="user" type="text" autofocus="true"><br>
+                    <span>Join/Create Feed: </span>
+                    <input id="pass" type="text">
+                    <button id="loginButton" type="button">Login</button>
+                </td>
+                </td>
+            </tr>
+        </table>
+        <script>var transition = {pid: '', host: 'localhost', port:'8000'};</script>
+        <script src='/n2o/protocols/bert.js'></script>
+        <script src='/n2o/protocols/binary.js'></script>
+        <script src='/n2o/protocols/client.js'></script>
+        <script src='/n2o/protocols/nitrogen.js'></script>
+        <script src='/n2o/bullet.js'></script>
+        <script src='/n2o/xhr.js'></script>
+        <script src='/n2o/utf8.js'></script>
+        <script src='/n2o/template.js'></script>
+        <script src='/n2o/n2o.js'></script>
+        <script src='/n2o/validation.js'></script>
+        <script>protos = [$client,$bert]; N2O_start();</script>
+    </body>
+</html>

+ 62 - 0
samples/apps/review/priv/static/synrc.css

@@ -0,0 +1,62 @@
+A { font-size:18pt; }
+A:link { color: blue; }
+A:visited { color: darkblue; }
+A:hover { color: blue; }
+SUP { font-size:10pt; }
+html { zoom: 0.8; }
+body { font-size:14pt; font-family: Lato, sans-serif; font-weight:300;}
+TD { font-size:18pt; margin: 0 auto; padding-right: 20px; }
+textarea { font-family: "Lucida Console", Monaco, monospace; padding: 10px; }
+input { font-size:28pt; width: 50%; margin:15px 15px 5px 5px; }
+button { font-size:20pt; margin-top:15px; padding: 10px; }
+H1 { color: blue; font-weight: bold; font-size:26pt; margin-top:20px; margin-bottom:10px; margin-right:6px; }
+H2 { color: darkblue; font-size:18pt; margin-top:20px; margin-bottom:10px; margin-right:6px; }
+H3 { font-size:14pt; margin-top:20px; margin-bottom:10px; margin-right:6px; } 
+P { font-size:18pt; margin-top: 0px; margin-bottom: 8px; }
+#frame { width: 700px; height: 1000px; border: 1px solid black; }
+.poem { font-size:14pt; margin-top: 0px; margin-bottom: 8px; white-space: pre;}
+LI { font-size:12pt; }
+pre { font-size:10pt; font-family: Calibri, Lucida Console; }
+.small_infoblock { font-family: Calibri, Lucida Sans Unicode; font-size:8pt; }
+.block  { background-color: #EEEEEE; padding-top:6;padding-bottom:10;padding-right:10;padding-left:10;}
+.threecol { float:left; }
+.left { float:left; }
+.hints { width:59px;float:left;margin-right:6px; }
+.main { width:843px;float:left;margin-right:26px; }
+.contents { width:270px;float:left; }
+.numbox { padding:10px 10px 10px 10px;background-color:black;color:white; }
+.feed { margin-top:20px; }
+
+code, div.lstlisting { font-size:14pt; margin: 20px 20px 20px 0px;
+                        padding: 0px 20px 20px 20px; font-family: monospace;
+                        white-space: pre; background-color: white; display: inline-block;
+                         border-left:60px solid #afafaf; }
+code, div.lstlisting .keyword              { font-weight: bold }
+code, div.lstlisting .string, code .regexp { color: green }
+code, div.lstlisting .class { color: darkblue }
+code, div.lstlisting .record { color: purple }
+code, div.lstlisting .special { color: royalblue }
+code, div.lstlisting .number               { color: pink }
+code, div.lstlisting .comment              { color: grey }
+
+
+/* seahawks
+ #03263a - college navy
+ #90918c - wolf gray
+ #4ea701 - action green
+ #ffffff - white
+ #36578c - blue
+*/
+.center{text-align: center;}
+.main{ width:100%;z-index:2;background: #ffffff;}
+.section{ border-bottom: 1px solid lightgray;}
+.section-header{font-weight:300;}
+.content{ margin-right:auto;margin-left:auto; max-width:1024px;padding: 1em 2em;}
+.header{ height:2.2em; }
+.profile-pic{padding:0 24px 0 24px;}
+.profile-pic img{width:2.2em;height: 2.2em;border-radius:2em;}
+.footer{text-align:center;color:90918c}
+.menu{display: flex;}
+.menu .pure-menu-heading{height:2em;line-height:2em;padding:0.1em 24px;display:flex;}
+.pure-button-group .google-button{display:inline-block;}
+.pure-button-group .avz-button{font-size:125%;color:#36578c;margin: 0 5px 0 5px;border-bottom:1px solid #90918c;}

+ 26 - 0
samples/apps/review/priv/templates/dev.html

@@ -0,0 +1,26 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta name='viewport'  content="width=device-width, initial-scale=1.0">
+  <link href='http://fonts.googleapis.com/css?family=Lato:300'    rel='stylesheet' type='text/css'/>
+  <link href="https://unpkg.com/purecss@0.6.2/build/pure-min.css" rel="stylesheet" type='text/css'/>
+  <link href='/static/synrc.css'  rel='stylesheet' type='text/css'/>
+  <link href='/static/feed.css'   rel='stylesheet' type='text/css'/>
+  <title>{{title}}</title>
+</head>
+<body>
+  {{body}}
+  <script>{{script}}</script>
+  <script src='/n2o/protocols/bert.js'></script>
+  <script src='/n2o/protocols/client.js'></script>
+  <script src='/n2o/protocols/nitrogen.js'></script>
+  <script src='/n2o/bullet.js'></script>
+  <script src='/n2o/xhr.js'></script>
+  <script src='/n2o/utf8.js'></script>
+  <script src='/n2o/template.js'></script>
+  <script src='/n2o/n2o.js'></script>
+  <script src='/n2o/validation.js'></script>
+  <script src='/n2o/ftp.js'></script>
+  <script>protos = [$client,$bert]; N2O_start();</script>
+</body>
+</html>

+ 47 - 0
samples/apps/review/priv/templates/doc.html

@@ -0,0 +1,47 @@
+<html>
+    <head>
+        <meta name='viewport' content='initial-scale=0.5'>
+        <link href='/static/synrc.css' type='text/css' rel='stylesheet'>
+        <link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
+        <title>Login</title>
+    </head>
+    <body>
+
+        <table cellpadding='20' align='center'>
+            <tr>
+                <td valign='top' align='center' height='230' width='300'>
+                    <a href="/"><img src='/static/S.svg' border='0' height='200' style='margin-top:0px;'></a><br>
+                </td>
+                <td colspan='2' width='700' valign='top'>
+                    <h1><b>N2O</b></h1>
+                    <h2>Simple Review Application</h2>
+                </td>
+            </tr>
+            <tr>
+                <td width='300' valign='top'>
+                    <p>List of spawned feeds:</p>
+                    <div id='history' class='feed'></div><br><br>
+                    <p>List of inhabited realms:</p>
+                    <div class='feed'>
+                        {{folders}}
+                    </div>
+                </td>
+                <td colspan='2' width='700' valign='top' height='550' bgcolor='#eeeeee'>
+                    <h1>Anyname Login</h1>
+                    {{body}}
+                </td>
+            </tr>
+        </table>
+        <script>{{script}}</script>
+        <script src='/n2o/protocols/bert.js'></script>
+        <script src='/n2o/protocols/client.js'></script>
+        <script src='/n2o/protocols/nitrogen.js'></script>
+        <script src='/n2o/bullet.js'></script>
+        <script src='/n2o/xhr.js'></script>
+        <script src='/n2o/utf8.js'></script>
+        <script src='/n2o/template.js'></script>
+        <script src='/n2o/n2o.js'></script>
+        <script src='/n2o/validation.js'></script>
+        <script>protos = [$client,$bert]; N2O_start();</script>
+    </body>
+</html>

+ 37 - 0
samples/apps/review/priv/templates/index.html

@@ -0,0 +1,37 @@
+<html>
+    <head>
+        <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
+        <link href='/static/synrc.css' type='text/css' rel='stylesheet'>
+        <link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
+        <title>Chat</title>
+    </head>
+    <body>
+        <table cellpadding='20' align='center'>
+            <tr>
+                <td valign='top' align='center' height='230' width='300'>
+                    <br><a href="/doc"><img src='/static/S.svg' border='0' height='200' style='margin-top:0px;'></a><br>
+                </td>
+                <td colspan='2' width='700' valign='top'>
+                    <h1><b id='heading'>Roster</b></h1>
+                    <h2>Unique Feed<br><button id='logout'></button></h2>
+                </td>
+            </tr>
+            <tr>
+                <td width='300' valign='top'>
+                    <p>Just type what you think about this:</p>
+                    <textarea id='message' style='margin-left:5px; width:490px; font-size:16pt;' 
+          value='' rows='5' autofocus></textarea>
+                    {{body}}
+                    <div id='history'></div>
+                </td>
+                <td colspan='2' width='700' valign='top' height='550' bgcolor='#eeeeee'>
+                    {{list}}
+                </td>
+            </tr>
+        </table>
+        <!--script>var transition = {pid: '', host: 'localhost', port:'8000'};</script-->
+        <script>{{script}}</script>
+        {{javascript}}
+        <script>protos = [$bert]; N2O_start();</script>
+    </body>
+</html>

+ 47 - 0
samples/apps/review/priv/templates/login.html

@@ -0,0 +1,47 @@
+<html>
+    <head>
+        <meta name='viewport' content='initial-scale=0.5'>
+        <link href='/static/synrc.css' type='text/css' rel='stylesheet'>
+        <link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
+        <title>Login</title>
+    </head>
+    <body>
+
+        <table cellpadding='20' align='center'>
+            <tr>
+                <td valign='top' align='center' height='230' width='300'>
+                    <a href="/doc"><img src='/static/S.svg' border='0' height='200' style='margin-top:0px;'></a><br>
+                </td>
+                <td colspan='2' width='700' valign='top'>
+                    <h1><b>N2O</b></h1>
+                    <h2>Simple Review Application</h2>
+                </td>
+            </tr>
+            <tr>
+                <td width='300' valign='top'>
+                    <p>List of spawned feeds:</p>
+                    <div id='history' class='feed'></div><br><br>
+                    <p>List of inhabited realms:</p>
+                    <div class='feed'>
+                        {{folders}}
+                    </div>
+                </td>
+                <td colspan='2' width='700' valign='top' height='550' bgcolor='#eeeeee'>
+                    <h1>Anyname Login</h1>
+                    {{body}}
+                </td>
+            </tr>
+        </table>
+        <script>{{script}}</script>
+        <script src='/n2o/protocols/bert.js'></script>
+        <script src='/n2o/protocols/client.js'></script>
+        <script src='/n2o/protocols/nitrogen.js'></script>
+        <script src='/n2o/bullet.js'></script>
+        <script src='/n2o/xhr.js'></script>
+        <script src='/n2o/utf8.js'></script>
+        <script src='/n2o/template.js'></script>
+        <script src='/n2o/n2o.js'></script>
+        <script src='/n2o/validation.js'></script>
+        <script>protos = [$client,$bert]; N2O_start();</script>
+    </body>
+</html>

+ 3 - 0
samples/apps/review/priv/templates/message.html

@@ -0,0 +1,3 @@
+<table width="500" cellpadding="20" cellspacing="3">
+<tr><td bgcolor="{{color}}" style='color:white;'><b>{{user}}</b>: {{message}}</td></tr>
+</table>

+ 10 - 0
samples/apps/review/rebar.config

@@ -0,0 +1,10 @@
+{deps_dir, ["../../deps"]}.
+{lib_dirs, ["../../apps"]}.
+{deps, []}.
+{erlydtl_opts, [
+    {doc_root,   "priv/templates"},
+    {out_dir,    "ebin"},
+   {compiler_options, [report, return, debug_info]},
+    {source_ext, ".html"},
+    {module_ext, "_view"}
+]}.

+ 22 - 0
samples/apps/review/src/config.erl

@@ -0,0 +1,22 @@
+-module(config).
+-compile(export_all).
+
+log_level() -> info.
+log_modules() -> % any
+  [
+    login,
+%    wf_convert,
+%    n2o_file,
+    n2o_async,
+    n2o_proto,
+%    n2o_client,
+%    n2o_static,
+    n2o_stream,
+    n2o_nitrogen,
+    n2o_session,
+    doc,
+    kvs,
+    store_mnesia,
+    index2,
+    index
+  ].

+ 40 - 0
samples/apps/review/src/doc.erl

@@ -0,0 +1,40 @@
+-module(doc).
+-compile(export_all).
+-include_lib("kvs/include/feed.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("n2o/include/wf.hrl").
+
+main() -> #dtl{file="doc",app=review,bindings=[{body,body()}]}.
+
+body() -> case wf:user() of
+               undefined -> wf:user("anonymous");
+                       _ -> skip end,
+  [ #h2      { body = "Docs search" },
+    #textbox { id   = query },
+    #button  { body = "Search", postback=search, source=[query] },
+    #panel   { id   = results } ].
+
+event({client,Panel}) -> wf:insert_top(results,Panel);
+event(search)         -> wf:update(results,#panel{id=results}),
+                         Pid = self(), Query = wf:q(query), spawn(fun() -> search(Pid,Query) end), ok;
+event(_)              -> [].
+
+sections(Path,Match,Pid)  ->
+    Page=filename:basename(Path),
+    App = lists:nth(4,lists:reverse(filename:split(Path))),
+    Forms=#panel{body=[
+          #h5{body=filename:join([App,filename:basename(Page,".htm")])}, [ begin
+              Url=["index.htm?code=",
+                   wf:pickle(iolist_to_binary([wf:to_binary(App),"/doc/web/",Page,$#,Sec]))],
+              #panel{body=#link{body=T,href=Url}}
+          end||[Sec,T] <- Match]]},
+    Pid ! {client,Forms}.
+
+re(Q) -> <<"(?:<h\\d[^>]*?\\ id=[\'\"](sec\\d{1,3})[\'\"][^>]*?>(.*?)<\\/h\\d>.*?)+",Q/binary,"[^>]+<">>.
+search(Pid,Q) ->
+    lists:map(fun(Path) -> spawn(fun() ->
+        {ok,Bin}=file:read_file(Path),
+        case re:run(Bin,re(Q),[unicode,global,{capture,[1,2],binary},dotall,caseless]) of
+            {match,Match} -> sections(Path,Match,Pid);
+            nomatch -> [] end end) end,
+    filelib:wildcard(application:get_env(n2o,search,"/var/www/sites/synrc.com/apps/*/doc/web/*.htm"))).

+ 73 - 0
samples/apps/review/src/index.erl

@@ -0,0 +1,73 @@
+-module(index).
+-compile(export_all).
+-include_lib("kvs/include/entry.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("n2o/include/wf.hrl").
+
+main() ->
+    case wf:user() of
+         undefined -> wf:redirect("login.htm"), redirect_wait();
+         _ -> #dtl{file = "index", app=review,bindings=[{body,body()},{list,list()},{javascript,(?MODULE:(wf:config(n2o,mode,dev)))()}]} end.
+
+prod() ->   [ #script{src="/static/review.min.js"} ].
+dev()  -> [ [ #script{src=lists:concat(["/n2o/protocols/",X,".js"])} || X <- [bert,nitrogen] ],
+            [ #script{src=lists:concat(["/n2o/",Y,".js"])}           || Y <- [bullet,n2o,ftp,utf8,validation] ] ].
+
+redirect_wait() -> #dtl{}.
+list() -> "<iframe src=http://synrc.com/apps/"++code()++" frameborder=0 width=700 height=1250></iframe>".
+code() -> case wf:q(<<"code">>) of undefined  -> "../privacy.htm";
+                                    Code -> wf:to_list(wf:depickle(Code)) end.
+
+body() ->
+    wf:update(heading,#b{id=heading,body="Review: " ++ code()}),
+    wf:update(logout,#button{id=logout, body="Logout " ++ wf:user(), postback=logout}),
+    [ #span{id=upload},#button { id=send, body= <<"Chat">>, postback=chat, source=[message] } ].
+
+event(init) ->
+    Room = code(),
+    wf:update(upload,#upload{id=upload}),
+    wf:reg(n2o_session:session_id()),
+    wf:reg({topic,Room}),
+    Res = wf:async("looper",fun index:loop/1),
+    n2o_async:send("looper","waterline"),
+    wf:info(?MODULE,"Async Process Created: ~p at Page Pid ~p~n",[Res,self()]),
+    [ event({client,{E#entry.from,E#entry.media}}) || E <- kvs:entries(kvs:get(feed,{room,Room}),entry,10) ];
+
+event(logout) ->
+    wf:logout(),
+    wf:redirect("login.htm");
+
+event(chat) ->
+    User = wf:user(),
+    Message = wf:q(message),
+    wf:info(?MODULE,"Chat pressed: ~p~n",[Message]),
+    Room = code(),
+    kvs:add(#entry{id=kvs:next_id("entry",1),from=wf:user(),feed_id={room,Room},media=Message}),
+    wf:send({topic,Room},#client{data={User,Message}});
+
+event(#client{data={User,Message}}) ->
+    wf:wire(#jq{target=message,method=[focus,select]}),
+    HTML = wf:to_list(Message),
+    wf:info(?MODULE,"HTML: ~tp~n",[HTML]),
+    DTL = #dtl{file="message",app=review,bindings=[{user,User},{color,"gray"},{message,HTML}]},
+    wf:insert_top(history, wf:jse(wf:render(DTL)));
+
+event(#bin{data=Data}) ->
+    wf:info(?MODULE,"Binary Delivered ~p~n",[Data]),
+    #bin{data = "SERVER"};
+
+event(#ftp{sid=Sid,filename=Filename,status={event,stop}}=Data) ->
+    wf:info(?MODULE,"FTP Delivered ~p~n",[Data]),
+    Name = hd(lists:reverse(string:tokens(wf:to_list(Filename),"/"))),
+    erlang:put(message,wf:render(#link{href=iolist_to_binary(["/static/",Sid,"/",wf:url_encode(Name)]),body=Name})),
+    wf:info(?MODULE,"Message ~p~n",[wf:q(message)]),
+    event(chat);
+
+event(Event) ->
+    wf:info(?MODULE,"Event: ~p", [Event]),
+    ok.
+
+loop(M) ->
+    DTL = #dtl{file="message",app=review,bindings=[{user,"system"},{message,M},{color,"silver"}]},
+    wf:insert_top(history, wf:jse(wf:render(DTL))),
+    wf:flush().

+ 83 - 0
samples/apps/review/src/interlogin.erl

@@ -0,0 +1,83 @@
+-module(interlogin).
+-compile(export_all).
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("n2o/include/wf.hrl").
+-include_lib("kvs/include/user.hrl").
+-include_lib("avz/include/avz.hrl").
+-include_lib("kvs/include/user.hrl").
+
+-define(LOGIN, [facebook, twitter, google, github, microsoft]).
+
+main() ->
+ avz:callbacks(?LOGIN),
+ #dtl{file="dev",app=review,bindings=[{title,<<"Login">>},{body,body()},{folders,folders()}]}.
+folders() -> string:join([filename:basename(F)||F<-filelib:wildcard(code:priv_dir(review)++"/snippets/*/")],",").
+
+body() ->
+  header() ++
+  [#panel{class=main, body=[
+    #panel{class=section, body=[
+      #panel{class=[content,center], body=[
+        #span{ id=display },
+        #link{href="/", body=[#image{src="/static/S.svg",style="margin-top:0px;"}]},
+        #h1{body= <<"Sign In/Up">>},
+        #h2{body= <<"Select provider to enter through">>},
+        #br{},
+        #panel{class=["pure-button-group"], role=group, body=[
+          avz:buttons(?LOGIN)
+        ]}
+      ]}
+    ]},
+    footer()
+  ]} | avz:sdk(?LOGIN)].
+
+event(init) ->
+[wf:wire(#jq{target=Id, method=["classList.add"], args=["'pure-button','avz-button'"]}) 
+  || Id <- [loginfb,twlogin,github_btn,microsoftbtn]],
+wf:wire(#jq{target=gloginbtn, method=["classList.add"], args=["'google-button'"]});
+
+event({register, U=#user{}}) ->
+  case kvs:add(U#user{id=kvs:next_id("user",1)}) of 
+    {ok, U1} -> avz:login_user(U1);
+    {error,E} -> event({login_failed,E}) end;
+  
+event({login, #user{}=U, N}) -> 
+  Updated = avz:merge(U,N), 
+  ok = kvs:put(Updated), 
+  avz:login_user(Updated);
+
+event({login_failed, E}) -> 
+  wf:update(display, #span{id=display, body=[E] });
+
+event(X) -> wf:info(?MODULE,"Event:~p~n",[X]),avz:event(X).
+api_event(X,Y,Z) -> avz:api_event(X,Y,Z).
+
+header() -> 
+  [#header{class=[header], body=[
+    #panel{class=["menu", "pure-menu", "pure-menu-horizontal","pure-menu-fixed"], body=[
+      #link{class=["pure-menu-heading"], href= <<"/">>, body=[#image{src="/static/S.svg"}, <<"Home">>]},
+
+      #list{class=["pure-menu-list"], body=[
+        case wf:user() of 
+          undefined -> [
+            #li{class=["pure-menu-item", "pure-menu-selected"], 
+              body=[#link{class=["pure-menu-link"], body= <<"Sign In">>, url= <<"/login.html">>}]} ];
+          User ->
+            Email = wf:to_list(User#user.email),
+            Avatar = case User#user.images of [] -> "holder.js/50x50"; [{_,A}|_] -> A end,
+            [
+              #li{class=["pure-menu-item"], body=[#link{class=["pure-menu-link"], body= [<<"Profile">>], url= <<"/profile.html">>}]},
+              #li{class=["pure-menu-item"], body=[
+                #link{class=["pure-menu-link"], body= ["Sign Out"], postback=logout, delegate=index} ]},
+              #li{class=["pure-menu-item"], body=[Email]},
+              #li{class=["pure-menu-item"], body=[
+                #link{class=["pure-menu-link", "profile-pic"], body=[
+                  #image{class=["pure-img"], image = Avatar} ]} ]}
+            ]
+        end
+      ]}
+    ]}
+  ]}].
+
+% 
+footer() -> #footer{class=[footer], body=[ <<"&copy; 2017">> ]}.

+ 25 - 0
samples/apps/review/src/login.erl

@@ -0,0 +1,25 @@
+-module(login).
+-compile(export_all).
+-include_lib("kvs/include/feed.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("n2o/include/wf.hrl").
+
+main() -> #dtl{file="login",app=review,bindings=[{body,body()},{folders,folders()}]}.
+folders() -> string:join([filename:basename(F)||F<-filelib:wildcard(code:priv_dir(review)++"/snippets/*/")],",").
+
+body() ->
+ [ #span   { id=display },                #br{},
+   #span   { body="Login: " },            #textbox{id=user,autofocus=true}, #br{},
+   #span   { body="Join/Create Feed: " }, #textbox{id=pass},
+   #button { id=loginButton, body="Login",postback=login,source=[user,pass]} ].
+
+event(login) ->
+    User = case wf:q(user) of <<>> -> "anonymous";
+                              undefined -> "anonymous";
+                              E -> wf:to_list(E) end,
+    wf:user(User),
+    wf:info(?MODULE,"User: ~p",[wf:user()]),
+    wf:redirect("index.htm?room="++wf:to_list(wf:q(pass))),
+    ok;
+
+event(_) -> [].

+ 9 - 0
samples/apps/review/src/review.app.src

@@ -0,0 +1,9 @@
+{application, review,
+ [
+  {description, "N2O Review Application"},
+  {vsn, "2.9"},
+  {registered, []},
+  {applications, [kernel, stdlib, n2o, kvs]},
+  {mod, { review, []}},
+  {env, []}
+ ]}.

+ 35 - 0
samples/apps/review/src/review.erl

@@ -0,0 +1,35 @@
+-module(review).
+-behaviour(supervisor).
+-behaviour(application).
+-export([init/1, start/2, stop/1, main/1]).
+-include_lib("kvs/include/user.hrl").
+
+main(A)    -> mad:main(A).
+start()    -> start(normal,[]).
+start(_,_) -> supervisor:start_link({local,review},review,[]).
+stop(_)    -> ok.
+
+-define(USERS, [#user{id="maxim",email="maxim@synrc.com"},
+                #user{id="doxtop",email="doxtop@synrc.com"},
+                #user{id="roman",email="roman@github.com"}]).
+
+init([]) -> users:init(),
+            users:populate(?USERS),
+            kvs:join(),
+            {ok, {{one_for_one, 5, 10}, [spec()]}}.
+
+spec()   -> ranch:child_spec(http, 100, ranch_tcp, port(), cowboy_protocol, env()).
+env()    -> [ { env, [ { dispatch, points() } ] } ].
+static() ->   { dir, "apps/review/priv/static", mime() }.
+n2o()    ->   { dir, "deps/n2o/priv",           mime() }.
+mime()   -> [ { mimetypes, cow_mimetypes, all   } ].
+port()   -> [ { port, wf:config(n2o,port,8000)  } ].
+points() -> cowboy_router:compile([{'_', [
+
+    {"/static/[...]",       n2o_static,  static()},
+    {"/n2o/[...]",          n2o_static,  n2o()},
+    {"/multipart/[...]",  n2o_multipart, []},
+    {"/rest/:resource",     rest_cowboy, []},
+    {"/rest/:resource/:id", rest_cowboy, []},
+    {"/ws/[...]",           n2o_stream,  []},
+    {'_',                   n2o_cowboy,  []} ]}]).

+ 31 - 0
samples/apps/review/src/routes.erl

@@ -0,0 +1,31 @@
+-module(routes).
+-author('Maxim Sokhatsky').
+-include_lib("n2o/include/wf.hrl").
+-export([init/2, finish/2]).
+
+%% U can use default dynamic routes or define custom static as this
+%% Just put needed module name to sys.config:
+%% {n2o, [{route,routes}]}
+%% Also with dynamic routes u must load all modules before starting Cowboy
+%% [code:ensure_loaded(M) || M <- [index, login, ... ]]
+
+finish(State, Ctx) -> {ok, State, Ctx}.
+init(State, Ctx) ->
+    Path = wf:path(Ctx#cx.req),
+    wf:info(?MODULE,"Route: ~p~n",[Path]),
+    {ok, State, Ctx#cx{path=Path,module=route_prefix(Path)}}.
+
+route_prefix(<<"/ws/",P/binary>>) -> route(P);
+route_prefix(<<"/",P/binary>>) -> route(P);
+route_prefix(P) -> route(P).
+
+route(<<>>)              -> login;
+route(<<"counter",_/binary>>) -> counter;
+route(<<"chat",_/binary>>) -> chat;
+route(<<"doc",_/binary>>) -> doc;
+route(<<"index",_/binary>>) -> index;
+route(<<"static/spa/index",_/binary>>) -> index;
+route(<<"static/spa/login",_/binary>>) -> login;
+route(<<"login",_/binary>>) -> login;
+route(<<"interlogin",_/binary>>) -> interlogin;
+route(_) -> login.

+ 15 - 0
samples/apps/review/src/users.erl

@@ -0,0 +1,15 @@
+-module(users).
+-behaviour(rest).
+-compile({parse_transform, rest}).
+-include_lib("kvs/include/user.hrl").
+-export([init/0, populate/1, exists/1, get/0, get/1, post/1, delete/1]).
+-rest_record(user).
+
+init()               -> ets:new(users, [public, named_table, {keypos, #user.id}]).
+populate(Users)      -> ets:insert(users, Users).
+exists(Id)           -> ets:member(users, wf:to_list(Id)).
+get()                -> ets:tab2list(users).
+get(Id)              -> #user{id=Id}.
+delete(Id)           -> ets:delete(users, wf:to_list(Id)).
+post(#user{} = User) -> ets:insert(users, User), true;
+post(Data)           -> post(from_json(Data, #user{})), true.


+ 18 - 0
samples/rebar.config

@@ -0,0 +1,18 @@
+{sub_dirs,["apps"]}.
+{lib_dirs,["apps","deps"]}.
+{deps_dir,"deps"}.
+{deps, [
+    {gproc,  ".*", {git, "git://github.com/uwiger/gproc",       {tag, "0.6.1"}}},
+    {rest,   ".*", {git, "git://github.com/synrc/rest",         {tag, "2.9"}    }},
+    {erlydtl,".*", {git, "git://github.com/evanmiller/erlydtl", {tag, "0.8.0"}  }},
+    {nitro,  ".*", {git, "git://github.com/synrc/nitro",        {tag, "2.4"} }},
+    {mad,    ".*", {git, "git://github.com/synrc/mad",          {tag, "1.9"} }},
+    {fs,     ".*", {git, "git://github.com/synrc/fs",           {tag, "1.9"}    }},
+    {sh,     ".*", {git, "git://github.com/synrc/sh",           {tag, "1.9"}    }},
+    {active, ".*", {git, "git://github.com/synrc/active",       {tag, "1.9"}    }},
+    {avz,    ".*", {git, "git://github.com/synrc/avz",          {tag, "4.4"} }},
+    {n2o,    ".*", {git, "git://github.com/synrc/n2o",          {tag, "4.4"} }},
+    {kvs,    ".*", {git, "git://github.com/synrc/kvs",          {tag, "3.9.1"}}}
+]}.
+{fetch_speed,fast_master}.
+{verbose,1}.

+ 46 - 0
samples/sys.config

@@ -0,0 +1,46 @@
+[
+ {n2o, [{port,8000},
+        {app,review},
+        {upload,"./apps/review/priv/static/"},
+        {search,"/Users/5HT/depot/synrc/synrc.com/apps/*/doc/web/*.htm"},
+        {mode,dev},
+        {route,routes},
+        {mq,n2o_mq},
+        {formatter,bert},
+        {minify,{"apps/review/priv/static",
+                ["deps/n2o/priv/bullet.js",
+                 "deps/n2o/priv/n2o.js",
+                 "deps/n2o/priv/ftp.js",
+                 "deps/n2o/priv/protocols/bert.js",
+                 "deps/n2o/priv/protocols/nitrogen.js",
+                 "deps/n2o/priv/utf8.js",
+                 "deps/n2o/priv/validation.js"]}},
+        {log_modules,config},
+        {log_level,config},
+        {log_backend,n2o_log},
+        {session,n2o_session},
+        {origin,<<"*">>},
+        {bridge,n2o_cowboy},
+        {pickler,n2o_pickle},
+        {erroring,n2o_error},
+        {event,pickle}]},
+ {avz, [{after_login_page, "/index"},
+        {login_page, "/interlogin"},
+        {fb_id, "176025532423202"},
+        {tw_consumer_key, "YwfU5qj5AYY0uwPumcw1Q"},
+        {tw_consumer_secret, "O7VjRYLWxwMgtSXZbiiY6kc1Og2il9gbo1KAIqZk"},
+        {g_client_id, "158344909038-j6c0rbvpi09kdaqq03j2eddlf047ht3d.apps.googleusercontent.com"}, 
+        {g_cookiepolicy, "http://localhost:8000"},
+        {g_btn_width, 240},
+        {g_btn_height, 50},
+        {g_btn_theme, "light"},
+        {g_btn_longtitle, true},
+        {github_client_id, "591bfe2556ee60ca8c32"},
+        {github_client_secret, "01411884e3c51624d3ea729ed6b047db52973e8e"},
+        {ms_client_id, "54385d15-f1e0-4fcf-9bf4-042d740e0df4"},
+        {ms_client_secret, "jU0tStEzRdDPFwL9NdVGYxo"},
+        {ms_redirect_uri, "http://localhost:8000/interlogin"}
+      ]},
+ {kvs, [{dba,store_mnesia},
+        {schema, [kvs_user, kvs_acl, kvs_feed, kvs_subscription ]} ]}
+].

+ 7 - 0
samples/vm.args

@@ -0,0 +1,7 @@
+-name review@127.0.0.1
+-setcookie node_runner
++pc unicode
++K true
++A 5
+-env ERL_MAX_PORTS 4096
+-env ERL_FULLSWEEP_AFTER 10

+ 5 - 0
samples/xen.config

@@ -0,0 +1,5 @@
+kernel = "vmling"
+name = "skyline"
+memory = 512
+extra = "-ipaddr 172.16.42.108 -netmask 255.255.255.0 -gateway 172.16.42.108 -root /erlang -pz /samples/deps/erlydtl/ebin -pz /samples/apps/n2o_sample/ebin -pz /samples/deps/n2o/ebin -pz /samples/deps/cowboy/ebin -pz /samples/deps/ranch/ebin -pz /samples/deps/mimetypes/ebin -home /samples/deps/n2o_sample/ebin"
+vif =  [ 'bridge=docker0', ]

+ 33 - 0
src/endpoints/cowboy/n2o_cowboy.erl

@@ -0,0 +1,33 @@
+-module(n2o_cowboy).
+-description('N2O Cowboy Server Bridge to HTTP Server Pages').
+-author('Roman Shestakov').
+-behaviour(cowboy_http_handler).
+-include_lib("n2o/include/wf.hrl").
+-export([init/3, handle/2, terminate/3]).
+-compile(export_all).
+-record(state, {headers, body}).
+
+% Cowboy HTTP Handler
+
+init(_Transport, Req, _Opts) -> {ok, Req, #state{}}.
+terminate(_Reason, _Req, _State) -> ok.
+handle(Req, State) ->  {ok, NewReq} = n2o_document:run(Req), {ok, NewReq, State}.
+
+% Cowboy Bridge Abstraction
+
+params(Req) -> cowboy_req:qs_vals(Req).
+form(Req) -> {ok,Params,NewReq} = cowboy_req:body_qs(Req), {Params,NewReq}.
+path(Req) -> {Path,_NewReq} = cowboy_req:path(Req), Path.
+request_body(Req) -> cowboy_req:body(Req).
+headers(Req) -> cowboy_req:headers(Req).
+header(Name, Value, Req) -> cowboy_req:set_resp_header(Name, Value, Req).
+response(Html,Req) -> cowboy_req:set_resp_body(Html,Req).
+reply(StatusCode,Req) -> cowboy_req:reply(StatusCode, Req).
+cookies(Req) -> element(1,cowboy_req:cookies(Req)).
+cookie(Cookie,Req) -> element(1,cowboy_req:cookie(wf:to_binary(Cookie),Req)).
+cookie(Cookie, Value, Req) -> cookie(Cookie,Value,<<"/">>,0,Req).
+cookie(Name, Value, Path, TTL, Req) ->
+    Options = [{path, Path}, {max_age, TTL}],
+    cowboy_req:set_resp_cookie(Name, Value, Options, Req).
+delete_cookie(Cookie,Req) -> cookie(Cookie,<<"">>,<<"/">>,0,Req).
+peer(Req) -> {{Ip,Port},Req} = cowboy_req:peer(Req), {Ip,Port}.

+ 79 - 0
src/endpoints/cowboy/n2o_multipart.erl

@@ -0,0 +1,79 @@
+-module(n2o_multipart).
+-export([init/3,handle/2,terminate/3]).
+-compile(export_all).
+-define(MAX_FILE_SIZE_LIMIT, 900*1000*1000). % 300Kb
+-define(TMP_PATH,".").
+
+% test multipart
+% curl -F file=@/Users/5HT/Desktop/40.tiff http://localhost:8000/multipart
+
+init(_Type, Req, []) ->
+	{ok, Req, undefined}.
+
+handle(Req, State) ->
+    {Method, Req2} = cowboy_req:method(Req),
+    {B, ReqU} = case multipart(Req2, ?MODULE) of
+                {ok, ReqM} -> {<<"<h1>This is a response for POST</h1>">>, ReqM};
+                {rejected, file_size_limit, ReqM} -> {<<"POST: File Size Limit">>, ReqM} end,
+    {ok, Req3} = cowboy_req:reply(200, [], B, ReqU),
+    {ok, Req3, State}.
+
+terminate(_Reason, _Req, _State) ->
+    ok.
+
+multipart(Req, C) ->
+    multipart(Req, C, ?MAX_FILE_SIZE_LIMIT).
+multipart(Req, C, MaxFileSizeLimit) when is_atom(C) and is_integer(MaxFileSizeLimit) ->
+    case cowboy_req:part(Req) of
+        {ok, Headers, Req2} ->
+            Req5 = case cow_multipart:form_data(Headers) of
+                {data, FieldName} ->
+                    {ok, Body, Req3} = cowboy_req:part_body(Req2),
+                    ok = C:data_payload(FieldName, Body),
+                    Req3;
+                {file, FieldName, Filename, _CType, _CTransferEncoding} ->
+                    TempFilename = temp_filename(),
+                    {ok, IoDevice} = file:open(TempFilename, [raw, write]),
+                    Rsf = stream_file(Req2, IoDevice, 0, MaxFileSizeLimit),
+                    ok = file:close(IoDevice),
+                    case Rsf of
+                            {ok, FileSize, Req4} ->
+                                ok = C:file_payload(FieldName, Filename, TempFilename, FileSize),
+                                Req4;
+                            {limit, Reason, Req4} ->
+                                error_logger:warning_msg("Upload limit detected! Type: ~p, FieldName: ~p, Filename: ~p,~nReq: ~p~n",
+                                    [Reason, FieldName, Filename, Req4]),
+                                ok = file:delete(TempFilename),
+                                Req4
+                    end
+            end,
+            multipart(Req5, C, MaxFileSizeLimit);
+        {done, Req2} ->
+            {ok, Req2}
+    end.
+
+stream_file(Req, IoDevice, FileSize, MaxFileSizeLimit) ->
+    {Control, Data, Req2} = cowboy_req:part_body(Req),
+    NewFileSize = byte_size(Data) + FileSize,
+    case NewFileSize > MaxFileSizeLimit of
+        true -> {limit, file_size, Req2};
+        false ->
+            ok = file:write(IoDevice, Data),
+            case Control of
+                ok -> {ok, NewFileSize, Req2};
+                more -> stream_file(Req2, IoDevice, NewFileSize, MaxFileSizeLimit)
+            end
+    end.
+
+temp_filename() ->
+    list_to_binary(filename:join([?TMP_PATH, atom_to_list(?MODULE) ++ integer_to_list(erlang:phash2(make_ref()))])).
+
+data_payload(FieldName, Body) ->
+    % error_logger:info_msg("DATA PAYLOAD {FieldName, Body} = {~p,~p}", [FieldName, Body]),
+    ok.
+
+file_payload(FieldName, Filename, TempFilename, FileSize) ->
+    NewFileName = <<"/tmp/upload.jpg">>,
+    % error_logger:info_msg("FILE PAYLOAD: {~p, ~p, ~p}", [FieldName, Filename, TempFilename]),
+    {ok, BytesCopied} = file:copy(TempFilename, NewFileName),
+    ok.

+ 71 - 0
src/endpoints/cowboy/n2o_static.erl

@@ -0,0 +1,71 @@
+-module(n2o_static).
+-description('N2O Static Bridge to files in LING image, MAD bundle or OS').
+-author('Maxim Sokhatsky').
+-compile(export_all).
+-include_lib("kernel/include/file.hrl").
+
+init(_, _, _) -> {upgrade, protocol, cowboy_rest}.
+
+rest_init(Req, {dir, Path, Extra}) when is_binary(Path) -> rest_init(Req, {dir, binary_to_list(Path), Extra});
+rest_init(Req, {dir, Path, Extra}) ->
+	{PathInfo, Req2} = cowboy_req:path_info(Req),
+	Info = {ok, #file_info{type=regular,size=0}},
+	FileName = filename:join([Path|PathInfo]),
+	wf:info(?MODULE,"Rest Init: ~p~n\r",[FileName]),
+    {ok, Req2, {FileName, Info, Extra}}.
+
+malformed_request(Req, State) -> {State =:= error, Req, State}.
+
+forbidden(Req, State={_, {ok, #file_info{type=directory}}, _}) -> {true, Req, State};
+forbidden(Req, State={_, {error, eacces}, _}) -> {true, Req, State};
+forbidden(Req, State={_, {ok, #file_info{access=Access}}, _}) when Access =:= write; Access =:= none -> {true, Req, State};
+forbidden(Req, State) -> {false, Req, State}.
+
+content_types_provided(Req, State={Path, _, Extra}) ->
+    wf:info(?MODULE,"Content Type Provided: ~p~n\r",[Path]),
+	case lists:keyfind(mimetypes, 1, Extra) of
+		false -> {[{cow_mimetypes:web(Path), get_file}], Req, State};
+		{mimetypes, Module, Function} -> {[{Module:Function(Path), get_file}], Req, State};
+		{mimetypes, Type} -> {[{Type, get_file}], Req, State}
+	end.
+
+resource_exists(Req, State={_, {ok, #file_info{type=regular}}, _}) -> {true, Req, State};
+resource_exists(Req, State) -> {false, Req, State}.
+
+generate_etag(Req, State={Path, {ok, #file_info{size=Size, mtime=Mtime}}, Extra}) ->
+	case lists:keyfind(etag, 1, Extra) of
+		false -> {generate_default_etag(Size, Mtime), Req, State};
+		{etag, Module, Function} -> {Module:Function(Path, Size, Mtime), Req, State};
+		{etag, false} -> {undefined, Req, State}
+	end.
+
+generate_default_etag(Size, Mtime) ->
+	{strong, list_to_binary(integer_to_list(
+		erlang:phash2({Size, Mtime}, 16#ffffffff)))}.
+
+last_modified(Req, State={_, {ok, #file_info{mtime=Modified}}, _}) -> {Modified, Req, State}.
+
+get_file(Req, State={Path, {ok, #file_info{size=_Size}}, _}) ->
+    StringPath = wf:to_list(unicode:characters_to_binary(Path,utf8,utf8)),
+    [_Type,Name|RestPath]=SplitPath = filename:split(StringPath),
+    wf:info(?MODULE,"Split Path: ~p~n\r",[SplitPath]),
+    %wf:info(?MODULE,"Code Path: ~p~n\r",[filename:join([code:lib_dir(Name)|RestPath])]),
+	FileName = filename:absname(StringPath),
+    wf:info(?MODULE,"Abs Name: ~p~n\r",[FileName]),
+    Raw = case file:read_file(FileName) of
+    {ok,Bin} -> Bin;
+    {error,_} ->
+        case mad_repl:load_file(StringPath) of
+        {ok,ETSFile} -> ETSFile;
+        {error,_} ->
+            case file:read_file(filename:join([code:lib_dir(Name)|RestPath])) of
+            {ok,ReleaseFile} -> ReleaseFile;
+            {error,_} -> <<>> end end end,
+    wf:info(?MODULE,"Cowboy Requested Static File: ~p~n\r ~p~n\r",[Raw,filename:absname(StringPath)]),
+	Sendfile = fun (Socket, Transport) ->
+		case Transport:send(Socket, Raw) of
+			{ok, _} -> ok;
+			{error, closed} -> ok;
+			{error, etimedout} -> ok;
+			_ -> ok end end,
+	{{stream, size(Raw), Sendfile}, Req, State}.

+ 45 - 0
src/endpoints/cowboy/n2o_stream.erl

@@ -0,0 +1,45 @@
+-module(n2o_stream).
+-description('N2O Stream Bridge to WebSocket or XHR channels').
+-behaviour(cowboy_http_handler).
+-behaviour(cowboy_websocket_handler).
+-export([init/3,handle/2,info/3,terminate/3]).
+-export([websocket_init/3,websocket_handle/3,websocket_info/3,websocket_terminate/3]).
+
+% XHR
+
+init(T,R,O)                        -> upgrade(cowboy_req:header(<<"upgrade">>,R),{T,R,O}).
+websocket(R,<<"websocket">>)       -> {upgrade, protocol, cowboy_websocket};
+websocket(R,_)                     -> down(reply([],R,501)).
+upgrade({undefined,R},{T,R,O})     -> initialize(T,R,O);
+upgrade({B,R},_) when is_binary(B) -> websocket(R,cowboy_bstr:to_lower(B)).
+
+handle(R,S)                  -> body(cowboy_req:body(R),S).
+info(M,R,S)                  -> xhr(n2o_proto:info(M,R,S)).
+terminate(_,R,S)             -> n2o_proto:terminate(R,S).
+initialize(T,R,O)            -> xhr(n2o_proto:init(T,R,[{formatter,json}|O],xhr)).
+
+body({ok,D,R2},S)            -> xhr(n2o_proto:stream({type(D),D},R2,S));
+body(R,S)                    -> {ok,R,S}.
+down(R)                      -> {shutdown,R,undefined}.
+reply(D,R,Code)              -> {ok,R2}=cowboy_req:reply(Code,[],D,R), R2.
+
+type(<<"N2O,",_/binary>>)    -> text;
+type(<<"PING">>)             -> text;
+type(_)                      -> binary.
+
+xhr({ok,R,S})                -> {ok,R,S};
+xhr({shutdown,R,S})          -> {shutdown,R,S};
+xhr({reply,D,R,S})           -> {ok,reply(D,R,200),S}.
+
+% WebSocket
+
+websocket_info(I,R,S)        -> ws(n2o_proto:info(I,R,S)).
+websocket_handle(D,R,S)      -> ws(n2o_proto:stream(D,R,S));
+websocket_handle(_,R,S)      -> {ok,R,S,hibernate}.
+websocket_init(T,R,O)        -> ws(n2o_proto:init(T,R,[{formatter,bert}|O],ws)).
+websocket_terminate(_,R,S)   -> n2o_proto:terminate(R,S).
+
+ws({ok,R,S})                 -> {ok,R,S,hibernate};
+ws({shutdown,R,S})           -> {shutdown,R,S};
+ws({reply,{binary,Rep},R,S}) -> {reply,{binary,Rep},R,S,hibernate};
+ws({reply,Rep,R,S})          -> {reply,{text,Rep},R,S,hibernate}.

+ 35 - 0
src/endpoints/n2o_document.erl

@@ -0,0 +1,35 @@
+-module(n2o_document).
+-description('N2O Server Pages HTTP endpoint handler').
+-author('Maxim Sokhatsky').
+-include_lib("n2o/include/wf.hrl").
+-compile (export_all).
+
+transition(Actions) ->
+    receive
+        {'INIT',A} -> transition(A);
+        {'N2O',Pid} -> Pid ! {actions,Actions}
+    after wf:config(n2o,transition_timeout,30000) -> {timeout,transition}
+    end.
+
+run(Req) ->
+    wf:state(status,200),
+    Pid = spawn(fun() -> transition([]) end),
+    wf:script(["var transition = {pid: '", wf:pickle(Pid), "', ",
+                "port:'", wf:to_list(wf:config(n2o,websocket_port,wf:config(n2o,port,8000))),"'}"]),
+    Ctx = wf:init_context(Req),
+    Ctx1 = wf:fold(init,Ctx#cx.handlers,Ctx),
+    wf:actions(Ctx1#cx.actions),
+    wf:context(Ctx1),
+    Elements = try (Ctx1#cx.module):main() catch C:E -> wf:error_page(C,E) end,
+    Html = wf_render:render(Elements),
+    Actions = wf:actions(),
+    Pid ! {'INIT',Actions},
+    Ctx2 = wf:fold(finish,Ctx#cx.handlers,?CTX),
+    Req2 = wf:response(Html,set_cookies(wf:cookies(),Ctx2#cx.req)),
+    wf:info(?MODULE,"Cookies Req: ~p",[Req2]),
+    {ok, _ReqFinal} = wf:reply(wf:state(status), Req2).
+
+set_cookies([],Req)-> Req;
+set_cookies([{Name,Value,Path,TTL}|Cookies],Req)->
+    set_cookies(Cookies,wf:cookie_req(Name,Value,Path,TTL,Req)).
+

+ 47 - 0
src/endpoints/n2o_proto.erl

@@ -0,0 +1,47 @@
+-module(n2o_proto).
+-description('N2O Protocol WebSocket endpoint handler').
+-author('Maxim Sokhatsky').
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+
+formatter(O)-> case lists:keyfind(formatter,1,O) of {formatter,F} -> F; X -> X end.
+upack(D)    -> binary_to_term(D,[safe]).
+protocols() -> wf:config(n2o,protocols,[ n2o_heart,
+                                         n2o_nitrogen,
+                                         n2o_file,
+                                         n2o_client,
+                                         n2o_http ]).
+
+terminate(_,#cx{module=Module}) -> catch Module:event(terminate).
+init(_Transport, Req, _Opts, _) ->
+    wf:actions([]),
+    Ctx = (wf:init_context(Req))#cx{formatter=formatter(_Opts)},
+    NewCtx = wf:fold(init,Ctx#cx.handlers,Ctx),
+    wf:context(NewCtx),
+    wf:reg(broadcast,{wf:peer(Req)}),
+    {Origin, _} = cowboy_req:header(<<"origin">>, Req, <<"*">>),
+    ConfigOrigin = wf:to_binary(wf:config(n2o,origin,Origin)),
+    wf:info(?MODULE,"Origin: ~p",[ConfigOrigin]),
+    Req1 = wf:header(<<"Access-Control-Allow-Origin">>, ConfigOrigin, NewCtx#cx.req),
+    {ok, Req1, NewCtx}.
+
+% N2O top level protocol NOP REPLY PUSH
+
+info(M,R,S)               -> filter(M,R,S,protocols(),[]).
+stream({text,_}=M,R,S)    -> filter(M,R,S,protocols(),[]);
+stream({binary,<<>>},R,S) -> nop(R,S);
+stream({binary,D},R,S)    -> filter(upack(D),R,S,protocols(),[]);
+stream(_,R,S)             -> nop(R,S).
+
+filter(M,R,S,Protos,Acc)  -> case application:get_env(n2o,filter,{?MODULE,push}) of
+                                  undefined -> push(M,R,S,Protos,Acc);
+                                  {Mod,Fun} -> Mod:Fun(M,R,S,Protos,Acc) end.
+
+nop(R,S)                  -> {reply,<<>>,R,S}.
+reply(M,R,S)              -> {reply,M,R,S}.
+push(_,R,S,[],_)          -> nop(R,S);
+push(M,R,S,[H|T],Acc)     ->
+    case H:info(M,R,S) of
+         {unknown,_,_,_}  -> push(M,R,S,T,Acc);
+         {reply,M1,R1,S1} -> reply(M1,R1,S1);
+                        A -> push(M,R,S,T,[A|Acc]) end.

+ 20 - 0
src/endpoints/n2o_relay.erl

@@ -0,0 +1,20 @@
+-module(n2o_relay).
+-description('N2O TCP relay endpoint handler').
+-compile(export_all).
+
+connect(IP, PortNo) ->
+    {ok, Socket} = gen_tcp:connect(IP, PortNo, [{active, false}, {packet, 2}]),
+    spawn(fun() -> recv(Socket) end),
+    Socket.
+
+send(Socket, Message) ->
+    BinMsg = term_to_binary(Message),
+    gen_tcp:send(Socket, BinMsg).
+
+recv(Socket) ->
+    {ok, A} = gen_tcp:recv(Socket, 0),
+    io:format("Received: ~p~n", [A]),
+    recv(Socket).
+
+disconnect(Socket) ->
+    gen_tcp:close(Socket).

+ 173 - 0
src/formatters/wf_convert.erl

@@ -0,0 +1,173 @@
+-module (wf_convert).
+-author('Maxim Sokhatsky').
+-compile(export_all).
+-include_lib("n2o/include/wf.hrl").
+
+% WF to_atom to_list to_binary
+
+-define(IS_STRING(Term), (is_list(Term) andalso Term /= [] andalso is_integer(hd(Term)))).
+
+-ifndef(N2O_JSON).
+-define(N2O_JSON, (application:get_env(n2o,json,jsone))).
+-endif.
+
+
+to_list(L) when ?IS_STRING(L) -> L;
+to_list(L) when is_list(L) -> SubLists = [inner_to_list(X) || X <- L], lists:flatten(SubLists);
+to_list(A) -> inner_to_list(A).
+inner_to_list(A) when is_atom(A) -> atom_to_list(A);
+inner_to_list(B) when is_binary(B) -> binary_to_list(B);
+inner_to_list(I) when is_integer(I) -> integer_to_list(I);
+inner_to_list(L) when is_tuple(L) -> lists:flatten(io_lib:format("~p", [L]));
+inner_to_list(L) when is_list(L) -> L;
+inner_to_list(F) when is_float(F) -> float_to_list(F,[{decimals,9},compact]).
+
+to_atom(A) when is_atom(A) -> A;
+to_atom(B) when is_binary(B) -> to_atom(binary_to_list(B));
+to_atom(I) when is_integer(I) -> to_atom(integer_to_list(I));
+to_atom(F) when is_float(F) -> to_atom(float_to_list(F,[{decimals,9},compact]));
+to_atom(L) when is_list(L) -> list_to_atom(binary_to_list(list_to_binary(L))).
+
+to_binary(A) when is_atom(A) -> atom_to_binary(A,latin1);
+to_binary(B) when is_binary(B) -> B;
+to_binary(T) when is_tuple(T) -> term_to_binary(T);
+to_binary(I) when is_integer(I) -> to_binary(integer_to_list(I));
+to_binary(F) when is_float(F) -> float_to_binary(F,[{decimals,9},compact]);
+to_binary(L) when is_list(L) ->  iolist_to_binary(L).
+
+to_integer(A) when is_atom(A) -> to_integer(atom_to_list(A));
+to_integer(B) when is_binary(B) -> to_integer(binary_to_list(B));
+to_integer(I) when is_integer(I) -> I;
+to_integer([]) -> 0;
+to_integer(L) when is_list(L) -> list_to_integer(L);
+to_integer(F) when is_float(F) -> round(F).
+
+% HTML encode/decode
+
+html_encode(B) -> html_encode(B,normal).
+html_encode(X,Fun) when is_function(Fun) -> Fun(X);
+html_encode(Y,Encode) when is_atom(Y); is_integer(Y); is_float(Y) -> html_encode(wf:to_binary(Y),Encode);
+html_encode(X, false) -> X;
+html_encode(X, true) -> html_encode(X,normal);
+html_encode(B,Encode) when is_binary(B) -> html_encode_aux(B,Encode);
+html_encode(L,Encode) when is_list(L) -> html_encode_aux(iolist_to_binary(L),Encode).
+
+html_encode_aux(<<"\s", T/binary>>,whites) -> [<<"&nbsp;">> | html_encode_aux(T,whites)];
+html_encode_aux(<<"\t", T/binary>>,whites) -> [<<"&nbsp; &nbsp; &nbsp;">> | html_encode_aux(T,whites)];
+html_encode_aux(<<"\n", T/binary>>,E)      -> [<<"<br>">>   | html_encode_aux(T,E)];
+html_encode_aux(<<"\\", T/binary>>,E)      -> [<<"&#92;">>  | html_encode_aux(T,E)];
+html_encode_aux(<<"<",  T/binary>>,E)      -> [<<"&lt;">>   | html_encode_aux(T,E)];
+html_encode_aux(<<">",  T/binary>>,E)      -> [<<"&gt;">>   | html_encode_aux(T,E)];
+html_encode_aux(<<"\"", T/binary>>,E)      -> [<<"&quot;">> | html_encode_aux(T,E)];
+html_encode_aux(<<"'",  T/binary>>,E)      -> [<<"&#39;">>  | html_encode_aux(T,E)];
+html_encode_aux(<<"&",  T/binary>>,E)      -> [<<"&amp;">>  | html_encode_aux(T,E)];
+html_encode_aux(<<C:8,  T/binary>>,E)      -> [C            | html_encode_aux(T,E)];
+html_encode_aux(<<>>, _)                   -> [].
+
+%% URL encode/decode
+
+url_encode(S) -> quote_plus(S).
+url_decode(S) -> unquote(S).
+
+-define(PERCENT, 37).  % $\%
+-define(FULLSTOP, 46). % $\.
+-define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse
+    (C >= $a andalso C =< $f) orelse
+    (C >= $A andalso C =< $F))).
+-define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse
+    (C >= $A andalso C =< $Z) orelse
+    (C >= $0 andalso C =< $9) orelse
+    (C =:= ?FULLSTOP orelse C =:= $- orelse C =:= $~ orelse
+        C =:= $_))).
+
+quote_plus(Atom) when is_atom(Atom) -> quote_plus(atom_to_list(Atom));
+quote_plus(Int) when is_integer(Int) -> quote_plus(integer_to_list(Int));
+quote_plus(Bin) when is_binary(Bin) -> quote_plus(binary_to_list(Bin));
+quote_plus(String) -> quote_plus(String, []).
+
+quote_plus([], Acc) -> lists:reverse(Acc);
+quote_plus([C | Rest], Acc) when ?QS_SAFE(C) -> quote_plus(Rest, [C | Acc]);
+quote_plus([$\s | Rest], Acc) -> quote_plus(Rest, [$+ | Acc]);
+quote_plus([C | Rest], Acc) -> <<Hi:4, Lo:4>> = <<C>>, quote_plus(Rest, [digit(Lo), digit(Hi), ?PERCENT | Acc]).
+
+unquote(Binary) when is_binary(Binary) -> unquote(binary_to_list(Binary));
+unquote(String) -> qs_revdecode(lists:reverse(String)).
+
+unhexdigit(C) when C >= $0, C =< $9 -> C - $0;
+unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10;
+unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10.
+
+qs_revdecode(S) -> qs_revdecode(S, []).
+qs_revdecode([], Acc) -> Acc;
+qs_revdecode([$+ | Rest], Acc) -> qs_revdecode(Rest, [$\s | Acc]);
+qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) -> qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
+qs_revdecode([C | Rest], Acc) -> qs_revdecode(Rest, [C | Acc]).
+
+%% JavaScript encode/decode
+
+js_escape(undefined) -> [];
+js_escape(Value) when is_list(Value) -> binary_to_list(js_escape(iolist_to_binary(Value)));
+js_escape(Value) -> js_escape(Value, <<>>).
+js_escape(<<"\\", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\\\">>);
+js_escape(<<"\r", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\r">>);
+js_escape(<<"\n", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\n">>);
+js_escape(<<"\"", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "\\\"">>);
+js_escape(<<"'",Rest/binary>>,Acc) -> js_escape(Rest, <<Acc/binary, "\\'">>);
+js_escape(<<"<script", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "<scr\" + \"ipt">>);
+js_escape(<<"script>", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "scr\" + \"ipt>">>);
+js_escape(<<C, Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, C>>);
+js_escape(<<>>, Acc) -> Acc.
+
+% JOIN
+
+join([],_) -> [];
+join([Item],_Delim) -> [Item];
+join([Item|Items],Delim) -> [Item,Delim | join(Items,Delim)].
+
+% Fast HEX
+
+digit(0) -> $0;
+digit(1) -> $1;
+digit(2) -> $2;
+digit(3) -> $3;
+digit(4) -> $4;
+digit(5) -> $5;
+digit(6) -> $6;
+digit(7) -> $7;
+digit(8) -> $8;
+digit(9) -> $9;
+digit(10) -> $a;
+digit(11) -> $b;
+digit(12) -> $c;
+digit(13) -> $d;
+digit(14) -> $e;
+digit(15) -> $f.
+
+hex(Bin) -> << << (digit(A1)),(digit(A2)) >> || <<A1:4,A2:4>> <= Bin >>.
+unhex(Hex) -> << << (erlang:list_to_integer([H1,H2], 16)) >> || <<H1,H2>> <= Hex >>.
+
+io(Data)     -> iolist_to_binary(Data).
+bin(Data)    -> Data.
+list(Data)   -> binary_to_list(term_to_binary(Data)).
+format(Term) -> format(Term,?CTX#cx.formatter).
+
+format({Io,Eval,Data},json) -> wf:info(?MODULE,"JSON {~p,_,_}: ~tp~n",[Io,io(Eval)]),
+                               ?N2O_JSON:encode([{t,104},{v,[[{t,100},{v,io}],
+                                                             [{t,109},{v,io(Eval)}],
+                                                             [{t,109},{v,list(Data)}]]}]);
+format({Atom,Data},   json) -> wf:info(?MODULE,"JSON {~p,_}: ~tp~n",[Atom,list(Data)]),
+                               ?N2O_JSON:encode([{t,104},{v,[[{t,100},{v,Atom}],
+                                                             [{t,109},{v,list(Data)}]]}]);
+
+format({Io,Eval,Data},bert) -> wf:info(?MODULE,"BERT {~p,_,_}: ~tp~n",[Io,{io,io(Eval),bin(Data)}]),
+                               {binary,term_to_binary({Io,io(Eval),bin(Data)})};
+format({bin,Data},    bert) -> wf:info(?MODULE,"BERT {bin,_}: ~tp~n",[Data]),
+                               {binary,term_to_binary({bin,Data})};
+format({Atom,Data},   bert) -> wf:info(?MODULE,"BERT {~p,_}: ~tp~n",[Atom,bin(Data)]),
+                               {binary,term_to_binary({Atom,bin(Data)})};
+format(#ftp{}=FTP,    bert) -> wf:info(?MODULE,"BERT {ftp,_,_,_,_,_,_,_,_,_,_,_,_}: ~tp~n",[FTP#ftp{data= <<>>}]),
+                               {binary,term_to_binary(FTP)};
+format(Term,          bert) -> {binary,term_to_binary(Term)};
+
+format(_,_)                 -> {binary,term_to_binary({error,<<>>,
+                                  <<"Only JSON/BERT formatters are available.">>})}.

+ 33 - 0
src/formatters/wf_utils.erl

@@ -0,0 +1,33 @@
+-module(wf_utils).
+-author('Rusty Klophaus').
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+
+f(S) -> f(S, []).
+f(S, Args) -> lists:flatten(io_lib:format(S, Args)).
+
+replace([], _, _) -> [];
+replace(String, S1, S2) when is_list(String), is_list(S1), is_list(S2) ->
+    Length = length(S1),
+    case string:substr(String, 1, Length) of
+        S1 -> S2 ++ replace(string:substr(String, Length + 1), S1, S2);
+        _ -> [hd(String)|replace(tl(String), S1, S2)]
+    end.
+
+coalesce({X,Y}) -> Y;
+coalesce(false) -> undefined;
+coalesce([]) -> undefined;
+coalesce([H]) -> H;
+coalesce([undefined|T]) -> coalesce(T);
+coalesce([[]|T]) -> coalesce(T);
+coalesce([H|_]) -> H.
+
+indexof(Key, Fields) -> indexof(Key, Fields, 2).
+indexof(_Key, [], _N) -> undefined;
+indexof(Key, [Key|_T], N) -> N;
+indexof(Key, [_|T], N) -> indexof(Key, T, N + 1).
+
+config(App, Key, Default) -> application:get_env(App,Key,Default).
+
+os_env(Key) -> os_env(Key, "").
+os_env(Key, Default) -> case os:getenv(Key) of false -> Default; V -> V end.

+ 75 - 0
src/handlers/n2o_async.erl

@@ -0,0 +1,75 @@
+-module(n2o_async).
+-author('Maxim Sokhatsky').
+-include_lib("n2o/include/wf.hrl").
+-behaviour(gen_server).
+-export([start_link/1]).
+-export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2,code_change/3]).
+-compile(export_all).
+
+% n2o_async API
+
+async(Fun) -> async(async,wf:temp_id(),Fun).
+async(Name, F) -> async(async,Name,F).
+async(Class,Name,F) ->
+    Key = key(),
+    Handler = #handler{module=?MODULE,class=async,group=n2o,
+                       name={Name,Key},config={F,?REQ},state=self()},
+    case n2o_async:start(Handler) of
+        {error,{already_started,P}} -> init(P,Class,{Name,Key}), {P,{Class,{Name,Key}}};
+        {P,X} when is_pid(P)        -> init(P,Class,X),          {P,{Class,X}};
+        Else -> Else end.
+
+init(Pid,Class,Name) when is_pid(Pid) -> wf:cache({Class,Name},Pid,infinity), send(Pid,{parent,self()}).
+send(Pid,Message) when is_pid(Pid) -> gen_server:call(Pid,Message);
+send(Name,Message) -> send(async,{Name,key()},Message).
+send(Class,Name,Message) -> gen_server:call(n2o_async:pid({Class,Name}),Message).
+pid({Class,Name}) -> wf:cache({Class,Name}).
+key() -> n2o_session:session_id().
+restart(Name) -> restart(async,{Name,key()}).
+restart(Class,Name) ->
+    case stop(Class,Name) of #handler{}=Async -> start(Async); Error -> Error end.
+flush() -> A=wf:actions(), wf:actions([]), get(parent) ! {flush,A}.
+flush(Pool) -> A=wf:actions(), wf:actions([]), wf:send(Pool,{flush,A}).
+stop(Name) -> stop(async,{Name,key()}).
+stop(Class,Name) ->
+    case n2o_async:pid({Class,Name}) of
+        Pid when is_pid(Pid) ->
+            #handler{group=Group} = Async = send(Pid,{get}),
+            [ supervisor:F(Group,{Class,Name})||F<-[terminate_child,delete_child]],
+            wf:cache({Class,Name},undefined), Async;
+        Data -> {error,{not_pid,Data}} end.
+start(#handler{class=Class,name=Name,module=Module,group=Group} = Async) ->
+    ChildSpec = {{Class,Name},{?MODULE,start_link,[Async]},transient,5000,worker,[Module]},
+    wf:info(?MODULE,"Async Start Attempt ~p~n",[Async#handler{config=[]}]),
+    case supervisor:start_child(Group,ChildSpec) of
+         {ok,Pid}   -> {Pid,Async#handler.name};
+         {ok,Pid,_} -> {Pid,Async#handler.name};
+         Else     -> Else end.
+
+init_context(undefined) -> [];
+init_context(Req) ->
+    Ctx = wf:init_context(Req),
+    NewCtx = wf:fold(init, Ctx#cx.handlers, Ctx),
+    wf:actions(NewCtx#cx.actions),
+    wf:context(NewCtx),
+    NewCtx.
+
+% Generic Async Server
+
+init(#handler{module=Mod,class=Class,name=Name}=Handler) -> wf:cache({Class,Name},self(),infinity), Mod:proc(init,Handler).
+handle_call({get},_,#handler{module=Mod}=Async)   -> {reply,Async,Async};
+handle_call(Message,_,#handler{module=Mod}=Async) -> Mod:proc(Message,Async).
+handle_cast(Message,  #handler{module=Mod}=Async) -> Mod:proc(Message,Async).
+handle_info(timeout,  #handler{module=Mod}=Async) -> Mod:proc(timeout,Async);
+handle_info(Message,  #handler{module=Mod}=Async) -> {noreply,element(3,Mod:proc(Message,Async))}.
+start_link(Parameters) -> gen_server:start_link(?MODULE, Parameters, []).
+code_change(_,State,_) -> {ok, State}.
+terminate(_Reason, #handler{name=Name,group=Group,class=Class}) ->
+    spawn(fun() -> supervisor:delete_child(Group,{Class,Name}) end),
+    wf:cache({Class,Name},undefined), ok.
+
+% wf:async page workers
+
+proc(init,#handler{class=Class,name=Name,config={F,Req},state=Parent}=Async) -> put(parent,Parent), try F(init) catch _:_ -> skip end, init_context(Req), {ok,Async};
+proc({parent,Parent},Async) -> {reply,put(parent,Parent),Async#handler{state=Parent}};
+proc(Message,#handler{config={F,Req}}=Async) -> {reply,F(Message),Async}.

+ 29 - 0
src/handlers/n2o_error.erl

@@ -0,0 +1,29 @@
+-module(n2o_error).
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+-export(?FAULTER_API).
+
+% Plain Text Error Page Render
+% Here is sample
+%
+% ERROR:  error:badarith
+%
+% STACK:  index:body/0:18
+%         index:main/0:8
+%         n2o_document:run/1:15
+
+stack(Error, Reason) ->
+    Stacktrace = [case A of
+         { Module,Function,Arity,Location} ->
+             { Module,Function,Arity,proplists:get_value(line, Location) };
+         Else -> Else end
+    || A <- erlang:get_stacktrace()],
+    [Error, Reason, Stacktrace].
+
+
+error_page(Class,Error) ->
+    io_lib:format("ERROR:  ~w:~w~n~n",[Class,Error]) ++
+    "STACK: " ++
+    [ wf:render([io_lib:format("\t~w:~w/~w:~w",
+        [ Module,Function,Arity,proplists:get_value(line, Location) ]),"\n"])
+    ||  { Module,Function,Arity,Location} <- erlang:get_stacktrace() ].

+ 16 - 0
src/handlers/n2o_io.erl

@@ -0,0 +1,16 @@
+-module(n2o_io).
+-author('Roman Gladkov').
+-export([info/3, warning/3, error/3]).
+
+
+info(Module, String, Args) ->
+    io:format(format_message(Module, String), Args).
+
+warning(Module, String, Args) ->
+    io:format(format_message(Module, String), Args).
+
+error(Module, String, Args) ->
+    io:format(format_message(Module, String), Args).
+
+format_message(Module, String) ->
+    wf:to_list([Module, ":", String, "\n\r"]).

+ 17 - 0
src/handlers/n2o_log.erl

@@ -0,0 +1,17 @@
+-module(n2o_log).
+-author('Roman Gladkov').
+-export([info/3, warning/3, error/3]).
+
+
+info(Module, String, Args) ->
+    error_logger:info_msg(format_message(Module, String), Args).
+
+warning(Module, String, Args) ->
+    error_logger:warning_msg(format_message(Module, String), Args).
+
+error(Module, String, Args) ->
+    error_logger:error_msg(format_message(Module, String), Args).
+
+
+format_message(Module, String) ->
+    wf:to_list([Module, ":", String, "~n"]).

+ 14 - 0
src/handlers/n2o_mq.erl

@@ -0,0 +1,14 @@
+-module(n2o_mq).
+-include_lib("n2o/include/wf.hrl").
+-export(?MESSAGE_API).
+
+send(Pool, Message) -> gproc:send({p,l,Pool},Message).
+reg(Pool) -> reg(Pool,undefined).
+reg(Pool, Value) ->
+    case get({pool,Pool}) of
+         undefined -> gproc:reg({p,l,Pool},Value), put({pool,Pool},Pool);
+         _Defined -> skip end.
+unreg(Pool) ->
+    case get({pool,Pool}) of
+         undefined -> skip;
+         _Defined -> gproc:unreg({p,l,Pool}), erase({pool,Pool}) end.

+ 8 - 0
src/handlers/n2o_pickle.erl

@@ -0,0 +1,8 @@
+-module(n2o_pickle).
+-include_lib("n2o/include/wf.hrl").
+-export(?PICKLES_API).
+
+pickle(Data) -> base64:encode(term_to_binary({Data, os:timestamp()}, [compressed])).
+depickle(PickledData) ->
+    try {Data, _PickleTime} = binary_to_term(base64:decode(wf:to_binary(PickledData))), Data
+    catch _:_ -> undefined end.

+ 11 - 0
src/handlers/n2o_query.erl

@@ -0,0 +1,11 @@
+-module(n2o_query).
+-author('Maxim Sokhatsky').
+-include_lib("n2o/include/wf.hrl").
+-export(?QUERING_API).
+
+init(_State, Ctx) ->
+    {Params,NewReq} = wf:params(Ctx#cx.req),
+    NewCtx = Ctx#cx{params=Params,req=NewReq},
+    {ok, [], NewCtx}.
+
+finish(_State, Ctx) ->  {ok, [], Ctx}.

+ 24 - 0
src/handlers/n2o_secret.erl

@@ -0,0 +1,24 @@
+-module(n2o_secret).
+-author('Oleksandr Nikitin').
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+-export(?PICKLES_API).
+
+pickle(Data) ->
+    Message = term_to_binary({Data,os:timestamp()}),
+    Padding = size(Message) rem 16,
+    Bits = (16-Padding)*8, Key = secret(), IV = crypto:rand_bytes(16),
+    Cipher = crypto:block_encrypt(aes_cbc128,Key,IV,<<Message/binary,0:Bits>>),
+    Signature = crypto:hmac(sha256,Key,<<Cipher/binary,IV/binary>>),
+    base64:encode(<<IV/binary,Signature/binary,Cipher/binary>>).
+
+secret() -> wf:config(n2o,secret,<<"ThisIsClassified">>).
+
+depickle(PickledData) ->
+    try Key = secret(),
+        Decoded = base64:decode(wf:to_binary(PickledData)),
+        <<IV:16/binary,Signature:32/binary,Cipher/binary>> = Decoded,
+        Signature = crypto:hmac(sha256,Key,<<Cipher/binary,IV/binary>>),
+        {Data,_Time} = binary_to_term(crypto:block_decrypt(aes_cbc128,Key,IV,Cipher),[safe]),
+        Data
+    catch E:R -> wf:info(?MODULE,"Depicke Error: ~p",[{E,R}]), undefined end.

+ 128 - 0
src/handlers/n2o_session.erl

@@ -0,0 +1,128 @@
+-module(n2o_session).
+-author('Dmitry Krapivnoy').
+-include_lib("n2o/include/wf.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+-compile(export_all).
+
+finish(State,Ctx) -> {ok,State,Ctx}.
+init(State,Ctx) -> case wf:config(n2o,auto_session) of
+                        disabled -> {ok,State,Ctx};
+                        _ -> n2o_session:ensure_sid(State,Ctx,[]) end.
+
+ensure_sid(State, Ctx, []) -> ensure_sid(State, Ctx, site);
+ensure_sid(State, Ctx, From) ->
+    SessionId   = wf:cookie_req(session_cookie_name(From), Ctx#cx.req),
+    wf:info(?MODULE,"Ensure SID ~p-sid=~p~n",[From,SessionId]),
+    session_sid(State, Ctx, SessionId, From).
+
+session_sid(SID, Source) -> session_sid([], ?CTX, SID, Source).
+session_sid(State, Ctx, SessionId, From) ->
+    wf:info(?MODULE,"Session Init ~p: ~p",[From,SessionId]),
+    Lookup = lookup_ets({SessionId,<<"auth">>}),
+    NewTill = till(calendar:local_time(), ttl()),
+    SessionCookie = case Lookup of
+        undefined ->
+            CookieValue = case SessionId of
+                undefined -> case wf:qc(wf:config(n2o,transfer_session,<<"csid">>),Ctx) of
+                    undefined -> new_cookie_value(From);
+                    Csid -> new_cookie_value(Csid, From) end;
+                _ -> new_cookie_value(SessionId,From) end,
+            Cookie = {{CookieValue,<<"auth">>},<<"/">>,os:timestamp(),NewTill,new},
+            ets:insert(cookies,Cookie),
+            wf:info(?MODULE,"Auth Cookie New: ~p~n",[Cookie]),
+            Cookie;
+        {{Session,Key},Path,Issued,Till,Status} ->
+            case expired(Issued,Till) of
+                false ->
+                    Cookie = {{Session,Key},Path,Issued,Till,Status},
+                    wf:info(?MODULE,"Auth Cookie Same: ~p",[Cookie]),
+                    Cookie;
+                true ->
+                    Cookie = {{new_cookie_value(From),<<"auth">>},<<"/">>,os:timestamp(),NewTill,new},
+                    clear(Session),
+                    ets:insert(cookies,Cookie),
+                    wf:info(?MODULE,"Auth Cookie Expired in Session ~p~n",[Session]),
+                    Cookie end;
+        What -> wf:info(?MODULE,"Auth Cookie Error: ~p",[What]), What
+    end,
+    {{ID,_},_,_,_,_} = SessionCookie,
+    put(session_id,ID),
+    wf:info(?MODULE,"State: ~p",[SessionCookie]),
+    {ok, State, Ctx#cx{session=SessionCookie}}.
+
+expired(_Issued,Till) -> Till < calendar:local_time().
+
+lookup_ets(Key) ->
+    Res = ets:lookup(cookies,Key),
+    %wf:info(?MODULE,"Lookup ETS: ~p",[{Res,Key}]),
+    case Res of
+         [] -> undefined;
+         [Value] -> Value;
+         Values -> Values end.
+
+clear() -> clear(session_id()).
+clear(Session) ->
+    [ ets:delete(cookies,X) || X <- ets:select(cookies,
+        ets:fun2ms(fun(A) when (element(1,element(1,A)) == Session) -> element(1,A) end)) ].
+
+cookie_expire(SecondsToLive) ->
+    Seconds = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
+    DateTime = calendar:gregorian_seconds_to_datetime(Seconds + SecondsToLive),
+    cow_date:rfc2109(DateTime).
+
+ttl() -> wf:config(n2o,ttl,60*15).
+
+till(Now,TTL) ->
+    calendar:gregorian_seconds_to_datetime(
+        calendar:datetime_to_gregorian_seconds(Now) + TTL).
+
+session_id() -> get(session_id).
+
+new_sid() ->
+    wf_convert:hex(binary:part(crypto:hmac(wf:config(n2o,hmac,sha256),
+         n2o_secret:secret(),term_to_binary(os:timestamp())),0,16)).
+
+new_cookie_value(From) -> new_cookie_value(new_sid(), From).
+new_cookie_value(undefined, From) -> new_cookie_value(new_sid(), From);
+new_cookie_value(SessionKey, From) ->
+    F = wf:f("document.cookie='~s=~s; path=/; expires=~s';",
+                [wf:to_list(session_cookie_name(From)),
+                 wf:to_list(SessionKey),
+                 cookie_expire(2147483647)]),
+    io:format("Cookie: ~p~n",[F]),
+    wf:wire(F),
+    % NOTE: Infinity-expire cookie will allow to clean up all session cookies
+    %       by request from browser so we don't need to sweep them on server.
+    %       Actually we should anyway to cleanup outdated cookies
+    %       that will never be requested.
+    SessionKey.
+
+session_cookie_name([]) -> session_cookie_name(site);
+session_cookie_name(From) -> wf:to_binary([wf:to_binary(From), <<"-sid">>]).
+
+set_session_value(Session, Key, Value) ->
+    Till = till(calendar:local_time(), ttl()),
+    ets:insert(cookies,{{Session,Key},<<"/">>,os:timestamp(),Till,Value}),
+    Value.
+
+set_value(Key, Value) ->
+    NewTill = till(calendar:local_time(), ttl()),
+    ets:insert(cookies,{{session_id(),Key},<<"/">>,os:timestamp(),NewTill,Value}),
+    Value.
+
+invalidate_sessions() ->
+    ets:foldl(fun(X,A) -> {Sid,Key} = element(1,X), n2o_session:get_value(Sid,Key,undefined), A end, 0, cookies).
+
+get_value(Key, DefaultValue) ->
+    get_value(session_id(), Key, DefaultValue).
+
+get_value(SID, Key, DefaultValue) ->
+    Res = case lookup_ets({SID,Key}) of
+               undefined -> DefaultValue;
+               {{SID,Key},_,Issued,Till,Value} -> case expired(Issued,Till) of
+                       false -> Value;
+                       true -> ets:delete(cookies,{SID,Key}), DefaultValue end end,
+    %wf:info(?MODULE,"Session Lookup Key ~p Value ~p",[Key,Res]),
+    Res.
+
+remove_value(Key) -> ets:delete(cookies,Key).

+ 16 - 0
src/handlers/n2o_syn.erl

@@ -0,0 +1,16 @@
+-module(n2o_syn).
+-include_lib("n2o/include/wf.hrl").
+-export(?MESSAGE_API).
+
+send(Pool, Message) -> syn:publish(term_to_binary(Pool),Message).
+reg(Pool) -> reg(Pool,undefined).
+reg(Pool, Value) ->
+    case get({pool,Pool}) of
+         undefined -> syn:join(term_to_binary(Pool),self()),
+                      put({pool,Pool},Pool);
+         _Defined -> skip end.
+unreg(Pool) ->
+    case get({pool,Pool}) of
+         undefined -> skip;
+         _Defined -> syn:leave(term_to_binary(Pool), self()), 
+                     erase({pool,Pool}) end.

+ 9 - 0
src/n2o.app.src

@@ -0,0 +1,9 @@
+{application, n2o, [
+    {description,  "N2O WebSocket Application Server"},
+    {vsn,          "4.4"},
+    {applications, [kernel, stdlib, cowboy]},
+    {modules, []},
+    {registered,   []},
+    {mod, { n2o,   []}},
+    {env, []}
+]}.

+ 31 - 0
src/n2o.erl

@@ -0,0 +1,31 @@
+-module(n2o).
+-description('N2O OTP Application Server').
+-behaviour(supervisor).
+-include_lib("n2o/include/wf.hrl").
+-behaviour(application).
+-export([start/2, stop/1, init/1, proc/2]).
+
+tables()   -> [ cookies, actions, globals, caching ].
+opt()      -> [ set, named_table, { keypos, 1 }, public ].
+start(_,_) -> X = supervisor:start_link({local,n2o},n2o,[]),
+              n2o_async:start(#handler{module=?MODULE,class=system,group=n2o,state=[],name="timer"}),
+              X.
+stop(_)    -> ok.
+init([])   -> [ ets:new(T,opt()) || T <- tables() ],
+              case wf:config(n2o,mq) of n2o_syn -> syn:init(); _ -> ok end,
+              { ok, { { one_for_one, 5, 10 }, [] } }.
+
+proc(init,#handler{}=Async) ->
+    wf:info(?MODULE,"Proc Init: ~p~n",[init]),
+    Timer = timer_restart(ping()),
+    {ok,Async#handler{state=Timer}};
+
+proc({timer,ping},#handler{state=Timer}=Async) ->
+    case Timer of undefined -> skip; _ -> erlang:cancel_timer(Timer) end,
+    wf:info(?MODULE,"N2O Timer: ~p~n",[ping]),
+    (wf:config(n2o,session,n2o_session)):invalidate_sessions(),
+    wf:invalidate_cache(),
+    {reply,ok,Async#handler{state=timer_restart(ping())}}.
+
+timer_restart(Diff) -> {X,Y,Z} = Diff, erlang:send_after(1000*(Z+60*Y+60*60*X),self(),{timer,ping}).
+ping() -> application:get_env(n2o,timer,{0,10,0}).

+ 40 - 0
src/n2o_cx.erl

@@ -0,0 +1,40 @@
+-module(n2o_cx).
+-description('N2O Process Context').
+-author('Rusty Klophaus').
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+
+actions() -> get(actions).
+actions(Ac) -> put(actions,Ac).
+context() -> get(context).
+context(Cx) -> put(context,Cx).
+context(Cx,Proto) -> lists:keyfind(Proto,1,Cx#cx.state).
+context(Cx,Proto,UserCx) -> 
+   NewCx = Cx#cx{state=wf:setkey(Proto,1,Cx#cx.state,{Proto,UserCx})},
+   wf:context(NewCx),
+   NewCx.
+clear_actions() -> put(actions,[]).
+add_action(Action) ->
+    Actions = case get(actions) of undefined -> []; E -> E end,
+    put(actions,Actions++[Action]).
+script() -> get(script).
+script(Script) -> put(script,Script).
+cookies() -> C = get(cookies), case is_list(C) of true -> C; _ -> [] end.
+add_cookie(Name,Value,Path,TTL) ->
+    C = cookies(),
+    Cookies = case lists:keyfind(Name,1,C) of
+        {Name,_,_,_} -> lists:keyreplace(Name,1,C,{Name,Value,Path,TTL});
+        false -> [{Name,Value,Path,TTL}|C] end,
+    put(cookies,Cookies).
+
+fold(Fun,Handlers,Ctx) ->
+    lists:foldl(fun({_,Module},Ctx1) ->
+        {ok,_,NewCtx} = Module:Fun([],Ctx1),
+        NewCtx end,Ctx,Handlers).
+
+init_context(Req) ->
+    #cx{
+        actions=[], module=index, path=[], req=Req, params=[],
+        handlers= [ {'query', wf:config(n2o,'query', n2o_query)},
+                    {session, wf:config(n2o,session, n2o_session)},
+                    {route,   wf:config(n2o,route,   n2o_dynroute)} ]}.

+ 20 - 0
src/protocols/n2o_client.erl

@@ -0,0 +1,20 @@
+-module(n2o_client).
+-author('Maxim Sokhatsky').
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+
+info({client,Message}, Req, State) ->
+    wf:info(?MODULE,"Client Message: ~p",[Message]),
+    Module = State#cx.module,
+    Reply = try Module:event({client,Message})
+          catch E:R -> Error = wf:stack(E,R), wf:error(?MODULE,"Catch: ~p:~p~n~p",Error), Error end,
+    {reply,wf:format({io,n2o_nitrogen:render_actions(wf:actions()),Reply}),Req,State};
+
+info({server,Message}, Req, State) ->
+    wf:info(?MODULE,"Server Message: ~p",[Message]),
+    Module = State#cx.module,
+    Reply  = try Module:event({server,Message})
+           catch E:R -> Error = wf:stack(E,R), wf:error(?MODULE,"Catch: ~p:~p~n~p",Error), Error end,
+    {reply,wf:format({io,n2o_nitrogen:render_actions(wf:actions()),Reply}),Req,State};
+
+info(Message, Req, State) -> {unknown,Message, Req, State}.

+ 82 - 0
src/protocols/n2o_file.erl

@@ -0,0 +1,82 @@
+-module(n2o_file).
+-author('Andrii Zadorozhnii').
+-include_lib("n2o/include/wf.hrl").
+-include_lib("kernel/include/file.hrl").
+-compile(export_all).
+
+-define(ROOT, wf:config(n2o,upload,code:priv_dir(n2o))).
+-define(NEXT, 256*1024). % 256K chunks for best 25MB/s speed
+-define(STOP, 0).
+
+% Callbacks
+
+filename(#ftp{sid=Sid,filename=FileName}) -> filename:join(wf:to_list(Sid),FileName).
+
+% N2O Protocols
+
+info(#ftp{status={event,_}}=FTP, Req, State) ->
+    wf:info(?MODULE,"Event Message: ~p",[FTP#ftp{data= <<>>}]),
+    Module=State#cx.module,
+    Reply=try Module:event(FTP)
+          catch E:R -> Error=wf:stack(E,R), wf:error(?MODULE,"Catch: ~p:~p~n~p",Error), Error end,
+    {reply,wf:format({io,n2o_nitrogen:render_actions(wf:actions()),Reply}),Req,State};
+
+info(#ftp{id=Link,sid=Sid,filename=FileName,status= <<"init">>,block=Block,offset=Offset,size=TotalSize}=FTP,Req,State) ->
+    Root=?ROOT,
+    RelPath=(wf:config(n2o,filename,n2o_file)):filename(FTP),
+    FilePath=filename:join(Root,RelPath),
+    ok=filelib:ensure_dir(FilePath),
+    FileSize=case file:read_file_info(FilePath) of {ok,Fi} -> Fi#file_info.size; {error,_} -> 0 end,
+
+    wf:info(?MODULE,"Info Init: ~p Offset: ~p Block: ~p~n",[FilePath,FileSize,Block]),
+
+    % Name={Sid,filename:basename(FileName)},
+    Block2=case Block of 0 -> ?STOP; _ -> ?NEXT end,
+    Offset2=case FileSize >= Offset of true -> FileSize; false -> 0 end,
+    FTP2=FTP#ftp{block=Block2,offset=Offset2,filename=RelPath,data= <<>>},
+
+    n2o_async:stop(file,Link),
+    n2o_async:start(#handler{module=?MODULE,class=file,group=n2o,state=FTP2,name=Link}),
+
+    {reply,wf:format(FTP2),Req,State};
+
+info(#ftp{id=Link,sid=Sid,filename=FileName,status= <<"send">>}=FTP,Req,State) ->
+    wf:info(?MODULE,"Info Send: ~p",[FTP#ftp{data= <<>>}]),
+    Reply=try gen_server:call(n2o_async:pid({file,Link}),FTP)
+        catch E:R -> wf:error(?MODULE,"Info Error call the sync: ~p~n",[FTP#ftp{data= <<>>}]),
+            FTP#ftp{data= <<>>,block=?STOP} end,
+    wf:info(?MODULE,"reply ~p",[Reply#ftp{data= <<>>}]),
+    {reply,wf:format(Reply),Req,State};
+
+info(#ftp{status= <<"recv">>}=FTP,Req,State) -> {reply,wf:format(FTP),Req,State};
+
+info(#ftp{status= <<"relay">>}=FTP,Req,State) -> {reply,wf:format(FTP),Req, State};
+
+info(Message,Req,State) -> wf:info(?MODULE, "Info Unknown message: ~p",[Message]),
+    {unknown,Message,Req,State}.
+
+% N2O Handlers
+
+proc(init,#handler{state=#ftp{sid=Sid}=FTP}=Async) ->
+    wf:info(?MODULE,"Proc Init: ~p",[FTP#ftp{data= <<>>}]),
+    wf:send(Sid,FTP#ftp{data= <<>>,status={event,init}}),
+    {ok,Async};
+
+proc(#ftp{id=Link,sid=Sid,data=Data,filename=FileName,status= <<"send">>,block=Block}=FTP,
+     #handler{state=#ftp{data=State,size=TotalSize,offset=Offset,filename=RelPath}}=Async) when Offset+Block >= TotalSize ->
+	wf:info(?MODULE,"Proc Stop ~p, last piece size: ~p", [FTP#ftp{data= <<>>},byte_size(Data)]),
+	case file:write_file(filename:join(?ROOT,RelPath),<<Data/binary>>,[append,raw]) of
+		{error,Reason} -> {reply,{error,Reason},Async};
+		ok ->
+            FTP2=FTP#ftp{data= <<>>,block=?STOP},
+            wf:send(Sid,FTP2#ftp{status={event,stop},filename=RelPath}),
+			spawn(fun() -> n2o_async:stop(file,Link) end),
+			{stop,normal,FTP2,Async#handler{state=FTP2}} end;
+
+proc(#ftp{sid=Sid,data=Data,block=Block}=FTP,
+     #handler{state=#ftp{data=State,offset=Offset,filename=RelPath}}=Async) ->
+    FTP2=FTP#ftp{status= <<"send">>,offset=Offset+Block },
+    wf:info(?MODULE,"Proc Process ~p",[FTP2#ftp{data= <<>>}]),
+    case file:write_file(filename:join(?ROOT,RelPath),<<Data/binary>>,[append,raw]) of
+        {error,Reason} -> {reply,{error,Reason},Async};
+        ok -> {reply,FTP2#ftp{data= <<>>},Async#handler{state=FTP2#ftp{filename=RelPath}}} end.

+ 43 - 0
src/protocols/n2o_heart.erl

@@ -0,0 +1,43 @@
+-module(n2o_heart).
+-author('Maxim Sokhatsky').
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+
+info({text,<<"PING">> = _Ping}=Message, Req, State) ->
+    wf:info(?MODULE,"PING: ~p",[Message]),
+    {reply, <<"PONG">>, Req, State};
+
+info({text,<<"N2O,",Process/binary>> = _InitMarker}=Message, Req, State) ->
+    wf:info(?MODULE,"N2O INIT: ~p",[Message]),
+    n2o_proto:push({init,Process},Req,State,n2o_proto:protocols(),[]);
+
+% ETS access protocol
+info({cache,Operation,Key,Value},Req,State)   -> {reply, case Operation of
+                                                              get -> wf:cache(Key);
+                                                              set -> wf:cache(Key,Value) end, Req, State};
+info({session,Operation,Key,Value},Req,State) -> {reply, case Operation of
+                                                              get -> wf:session(Key);
+                                                              set -> wf:session(Key,Value) end, Req, State};
+
+% MQ protocol
+info({pub,Topic,Message}=Message,Req,State) -> {reply, <<"OK">>, Req, State};
+info({sub,Topic,Args}=Message,Req,State)    -> {reply, <<"OK">>, Req, State};
+info({unsub,Topic}=Message,Req,State)       -> {reply, <<"OK">>, Req, State};
+
+% WF protocol
+info({q,Operation,Key}=Message,Req,State)                -> {reply, <<"OK">>, Req, State};
+info({qc,Operation,Key}=Message,Req,State)               -> {reply, <<"OK">>, Req, State};
+info({cookie,Operation,Key,Value}=Message,Req,State)     -> {reply, <<"OK">>, Req, State};
+info({wire,Parameter}=Message,Req,State)                 -> {reply, <<"OK">>, Req, State};
+info({update,Target,Elements}=Message,Req,State)         -> {reply, <<"OK">>, Req, State};
+info({insert_top,Target,Elements}=Message,Req,State)     -> {reply, <<"OK">>, Req, State};
+info({insert_bottom,Target,Elements}=Message,Req,State)  -> {reply, <<"OK">>, Req, State};
+
+% ASYNC protocol
+info({start,Handler}=Message,Req,State)          -> {reply, <<"OK">>, Req, State};
+info({stop,Name}=Message,Req,State)              -> {reply, <<"OK">>, Req, State};
+info({restart,Name}=Message,Req,State)           -> {reply, <<"OK">>, Req, State};
+info({async,Name,Function}=Message,Req,State)    -> {reply, <<"OK">>, Req, State};
+info({flush}=Message,Req,State)                  -> {reply, <<"OK">>, Req, State};
+
+info(Message, Req, State) -> {unknown,Message, Req, State}.

+ 15 - 0
src/protocols/n2o_http.erl

@@ -0,0 +1,15 @@
+-module(n2o_http).
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+
+%% ws.send(enc(tuple(atom('http'), bin(url), bin(method), bin(body), [])));
+
+info(#http{} = Message, Req, State) ->
+    wf:info(?MODULE, "Http Message: ~p",[Message]),
+    Module = State#cx.module,
+    Reply = try Module:event(Message)
+          catch E:R -> Error = wf:stack(E,R), wf:error(?MODULE,"Catch: ~p:~p~n~p",Error), Error end,
+    {reply,wf:format({io,n2o_nitrogen:render_actions(wf:actions()),Reply}),Req,State};
+
+info(Message, Req, State) -> {unknown,Message, Req, State}.
+

+ 90 - 0
src/protocols/n2o_nitrogen.erl

@@ -0,0 +1,90 @@
+-module(n2o_nitrogen).
+-author('Maxim Sokhatsky').
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+
+% Nitrogen pickle handler
+
+info({init,Rest},Req,State) ->
+    Module = State#cx.module,
+    InitActionsReply = case Rest of
+         <<>> -> try Elements = Module:main(),
+                     wf_render:render(Elements),
+                     {ok,[]}
+               catch X:Y -> Stack = wf:stack(X,Y),
+                            wf:error(?MODULE,"Event Main: ~p:~p~n~p", Stack),
+                            {error,Stack} end;
+          Binary -> Pid = wf:depickle(Binary),
+                    Pid ! {'N2O',self()},
+                    {ok,receive_actions(Req)} end,
+    case InitActionsReply of
+         {ok,InitActions} -> UserCx = try Module:event(init),
+                                     case wf:config(n2o,auto_session) of
+                              disabled -> skip;
+                                      _ -> n2o_session:ensure_sid([],?CTX,[]) end
+                                    catch C:E -> Error = wf:stack(C,E),
+                                                 wf:error(?MODULE,"Event Init: ~p:~p~n~p",Error),
+                                                 {stack,Error} end,
+                             Actions = render_actions(wf:actions()),
+                             {reply,wf:format({io,iolist_to_binary([InitActions,Actions]),<<>>}),
+                                    Req,wf:context(State,?MODULE,{init,UserCx})};
+           {error,E} ->  {reply,wf:format({io,<<>>,E}), Req, wf:context(State,?MODULE,{error,E})} end;
+
+info({pickle,_,_,_}=Event, Req, State) ->
+    wf:actions([]),
+    Result = try html_events(Event,State)
+           catch E:R -> Stack = wf:stack(E,R),
+                        wf:error(?MODULE,"Catch: ~p:~p~n~p", Stack),
+                        {io,render_actions(wf:actions()),Stack} end,
+    {reply,wf:format(Result),Req,wf:context(State,?MODULE,{pickle,Result})};
+
+info({flush,Actions}, Req, State) ->
+    wf:actions([]),
+    Render = iolist_to_binary(render_actions(Actions)),
+    wf:info(?MODULE,"Flush Message: ~tp",[Render]),
+    {reply,wf:format({io,Render,<<>>}),Req, State};
+
+info({direct,Message}, Req, State) ->
+    wf:actions([]),
+    Module = State#cx.module,
+    Result = try Res = Module:event(Message), {direct,Res}
+           catch E:R -> Stack = wf:stack(E, R),
+                        wf:error(?MODULE,"Catch: ~p:~p~n~p", Stack),
+                        {stack,Stack} end,
+    {reply,wf:format({io,render_actions(wf:actions()),<<>>}),Req, wf:context(State,?MODULE,Result)};
+
+info(Message,Req,State) -> {unknown,Message,Req,State}.
+
+% double render: actions could generate actions
+
+render_actions(Actions) ->
+    wf:actions([]),
+    First  = wf:render(Actions),
+    Second = wf:render(wf:actions()),
+    wf:actions([]),
+    [First,Second].
+
+% N2O events
+
+html_events({pickle,Source,Pickled,Linked}=Pickle, State) ->
+    wf:info(?MODULE,"Pickle: ~tp",[Pickle]),
+    Ev = wf:depickle(Pickled),
+    Result = case Ev of
+         #ev{} -> render_ev(Ev,Source,Linked,State);
+         CustomEnvelop -> wf:error("Only #ev{} events for now: ~p",[CustomEnvelop]) end,
+    {io,render_actions(wf:actions()),<<>>}.
+
+render_ev(#ev{module=M,name=F,msg=P,trigger=T},_Source,Linked,State) ->
+    case F of
+         api_event -> M:F(P,Linked,State);
+         event -> lists:map(fun({K,V})-> put(K,wf:to_binary(V)) end,Linked), M:F(P);
+         _UserCustomEvent -> M:F(P,T,State) end.
+
+receive_actions(Req) ->
+    receive
+        {actions,A} -> n2o_nitrogen:render_actions(A);
+        _ -> receive_actions(Req)
+    after 200 ->
+         QS = element(14, Req),
+         wf:redirect(case QS of <<>> -> ""; _ -> "?" ++ wf:to_list(QS) end),
+         [] end.

+ 14 - 0
src/protocols/n2o_text.erl

@@ -0,0 +1,14 @@
+-module(n2o_text).
+-author('Alexander Salnikov').
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+
+info({text,Text}=Message, Req, State) when is_binary(Text) ->
+    wf:info(?MODULE,"TEXT Message: ~p",[Message]),
+    Module = State#cx.module,
+    Resp = try Module:event(Message) catch 
+    	E:R -> wf:error(?MODULE,"Catch: ~p:~p~n~p", wf:stack(E, R)), <<>> 
+    end,
+    {reply, Resp, Req, State};
+
+info(Message, Req, State) -> {unknown,Message, Req, State}.

+ 278 - 0
src/wf.erl

@@ -0,0 +1,278 @@
+-module(wf).
+-author('Rusty Klophaus').
+-author('Maxim Sokhatsky').
+-include_lib("n2o/include/wf.hrl").
+-include_lib("n2o/include/api.hrl").
+-compile (export_all).
+
+-define(ACTION_BASE(Module), ancestor=action, trigger, target, module=Module, actions, source=[]).
+-record(jq,      {?ACTION_BASE(action_jq), property, method, args=[], right, format="~s"}).
+
+% Here is Nitrogen Web Framework compatible API
+% Please read major changes made to N2O and
+% how to port existing Nitrogen sites at http://synrc.com/apps/n2o
+
+% Update DOM wf:update
+
+update(Target, Elements) ->
+    wf:wire(#jq{target=Target,property=outerHTML,right=Elements,format="'~s'"}).
+
+insert_top(Tag,Target, Elements) ->
+    Pid = self(),
+    Ref = make_ref(),
+    spawn(fun() -> R = wf:render(Elements), Pid ! {R,Ref,wf:actions()} end),
+    {Render,Ref,Actions} = receive {_, Ref, _} = A -> A end,
+    wf:wire(wf:f(
+        "qi('~s').insertBefore("
+        "(function(){var div = qn('~s'); div.innerHTML = '~s'; return div.firstChild; })(),"
+        "qi('~s').firstChild);",
+        [Target,Tag,Render,Target])),
+    wf:wire(wf:render(Actions)).
+
+insert_bottom(Tag, Target, Elements) ->
+    Pid = self(),
+    Ref = make_ref(),
+    spawn(fun() -> R = wf:render(Elements), Pid ! {R,Ref,wf:actions()} end),
+    {Render,Ref,Actions} = receive {_, Ref, _} = A -> A end,
+    wf:wire(wf:f(
+        "(function(){ var div = qn('~s'); div.innerHTML = '~s';"
+                     "qi('~s').appendChild(div.firstChild); })();",
+        [Tag,Render,Target])),
+    wf:wire(wf:render(Actions)).
+
+insert_adjacent(Command,Target, Elements) ->
+    Pid = self(),
+    Ref = make_ref(),
+    spawn(fun() -> R = wf:render(Elements), Pid ! {R,Ref,wf:actions()} end),
+    {Render,Ref,Actions} = receive {_, Ref, _} = A -> A end,
+    wf:wire(wf:f("qi('~s').insertAdjacentHTML('~s', '~s');",[Target,Command,Render])),
+    wf:wire(wf:render(Actions)).
+
+insert_top(Target, Elements) when element(1,Elements) == tr -> insert_top(tbody,Target, Elements);
+insert_top(Target, Elements) -> insert_top('div',Target, Elements).
+insert_bottom(Target, Elements) when element(1,Elements) == tr -> insert_bottom(tbody, Target, Elements);
+insert_bottom(Target, Elements) -> insert_bottom('div', Target, Elements).
+insert_before(Target, Elements) -> insert_adjacent(beforebegin,Target, Elements).
+insert_after(Target, Elements) -> insert_adjacent(afterend,Target, Elements).
+
+remove(Target) ->
+    wf:wire("var x=qi('"++wf:to_list(Target)++"'); x && x.parentNode.removeChild(x);").
+
+% Wire JavaScript wf:wire
+
+wire(Actions) -> action_wire:wire(Actions).
+
+% Spawn async processes wf:async, wf:flush
+
+async(Function) -> n2o_async:async(Function).
+start(#handler{}=Handler) -> n2o_async:start(Handler).
+stop(Name) -> n2o_async:stop(Name).
+restart(Name) -> n2o_async:restart(Name).
+async(Name,Function) -> n2o_async:async(Name,Function).
+flush() -> n2o_async:flush().
+flush(Key) -> n2o_async:flush(Key).
+
+% Redirect and purge connection wf:redirect
+
+redirect({http,Url}) -> wf:header(<<"Location">>,wf:to_binary(Url)), wf:state(status,302), [];
+redirect(Url) -> wf:wire(#jq{target='window.top',property=location,args=simple,right=Url}).
+header(K,V) -> wf:context((?CTX)#cx{req=wf:header(K,V,?REQ)}).
+
+% Message Bus communications wf:reg wf:send
+
+-ifndef(REGISTRATOR).
+-define(REGISTRATOR, (wf:config(n2o,mq,n2o_mq))).
+-endif.
+
+send(Pool, Message) -> ?REGISTRATOR:send(Pool,Message).
+reg(Pool) -> ?REGISTRATOR:reg(Pool).
+reg(Pool,Value) -> ?REGISTRATOR:reg(Pool,Value).
+unreg(Pool) -> ?REGISTRATOR:unreg(Pool).
+
+% Pickling wf:pickle
+
+-ifndef(PICKLER).
+-define(PICKLER, (wf:config(n2o,pickler,n2o_pickle))).
+-endif.
+
+pickle(Data) -> ?PICKLER:pickle(Data).
+depickle(SerializedData) -> ?PICKLER:depickle(SerializedData).
+depickle(SerializedData, TTLSeconds) -> ?PICKLER:depickle(SerializedData, TTLSeconds).
+
+% Error handler wf:error_page
+
+-ifndef(ERRORING).
+-define(ERRORING, (wf:config(n2o,erroring,n2o_error))).
+-endif.
+
+stack(Error, Reason)    -> ?ERRORING:stack(Error, Reason).
+error_page(Class,Error) -> ?ERRORING:error_page(Class, Error).
+
+% Session handling wf:session wf:user wf:role
+
+-ifndef(SESSION).
+-define(SESSION, (wf:config(n2o,session,n2o_session))).
+-endif.
+
+session(Key) -> ?SESSION:get_value(Key,undefined).
+session(Key, Value) -> ?SESSION:set_value(Key, Value).
+session_default(Key, DefaultValue) -> ?SESSION:get_value(Key, DefaultValue).
+clear_session() -> ?SESSION:clear().
+session_id() -> ?SESSION:session_id().
+user() -> wf:session(<<"user">>).
+user(User) -> wf:session(<<"user">>,User).
+clear_user() -> wf:session(<<"user">>,undefined).
+logout() -> clear_user(), clear_session().
+invalidate_cache() -> ets:foldl(fun(X,A) -> wf:cache(element(1,X)) end, 0, caching).
+cache(Key, undefined) -> ets:delete(caching,Key);
+cache(Key, Value) -> ets:insert(caching,{Key,n2o_session:till(calendar:local_time(), n2o_session:ttl()),Value}), Value.
+cache(Key, Value, Till) -> ets:insert(caching,{Key,Till,Value}), Value.
+cache(Key) ->
+    Res = ets:lookup(caching,Key),
+    Val = case Res of [] -> undefined; [Value] -> Value; Values -> Values end,
+    case Val of undefined -> undefined;
+                {_,infinity,X} -> X;
+                {_,Expire,X} -> case Expire < calendar:local_time() of
+                                  true ->  ets:delete(caching,Key), undefined;
+                                  false -> X end end.
+
+% Process State
+
+state(Key) -> erlang:get(Key).
+state(Key,Value) -> erlang:put(Key,Value).
+
+% Context Variables and URL Query Strings from ?REQ and ?CTX wf:q wf:qc wf:qp
+
+q(Key) -> Val = get(Key), case Val of undefined -> qc(Key); A -> A end.
+qc(Key) -> qc(Key,?CTX).
+qc(Key,Ctx) -> proplists:get_value(to_binary(Key),Ctx#cx.params).
+qp(Key) -> qp(Key,?REQ).
+qp(Key,Req) -> {Params,_} = params(Req), proplists:get_value(to_binary(Key),Params).
+lang() -> ?CTX#cx.lang.
+
+% Cookies
+
+cookies() -> n2o_cx:cookies().
+cookie(Name) -> lists:keyfind(Name,1,cookies()).
+cookie(Name,Value) -> cookie(Name,Value,"/", 24 * 60 * 60).
+cookie(Name,Value,Path,TTL) -> n2o_cx:add_cookie(Name,Value,Path,TTL).
+
+% Bridge Information
+
+-ifndef(BRIDGE).
+-define(BRIDGE, (wf:config(n2o,bridge,n2o_cowboy))).
+-endif.
+
+cookie_req(Cookie,Req) -> ?BRIDGE:cookie(Cookie, Req).
+cookie_req(Name, Value, Path, TTL, Req) -> ?BRIDGE:cookie(Name, Value, Path, TTL, Req).
+params(Req) -> ?BRIDGE:params(Req).
+form(Req) -> ?BRIDGE:form(Req).
+cookies_req(Req) -> ?BRIDGE:cookies(Req).
+headers(Req) -> ?BRIDGE:headers(Req).
+peer(Req) -> ?BRIDGE:peer(Req).
+path(Req) -> ?BRIDGE:path(Req).
+request_body(Req) -> ?BRIDGE:request_body(Req).
+delete_cookie(Cookie,Req) -> ?BRIDGE:delete_cookie(Cookie,Req).
+header(Name,Val,Req) -> ?BRIDGE:header(Name, Val, Req).
+response(Html,Req) -> ?BRIDGE:response(Html,Req).
+reply(Status,Req) -> ?BRIDGE:reply(Status,Req).
+
+% Logging API
+
+-define(LOGGER, (wf:config(n2o,log_backend,n2o_log))).
+log_modules() -> [wf].
+log_level() -> info.
+-define(LOG_MODULES, (wf:config(n2o,log_modules,wf))).
+-define(LOG_LEVEL, (wf:config(n2o,log_level,wf))).
+
+log_level(none) -> 3;
+log_level(error) -> 2;
+log_level(warning) -> 1;
+log_level(_) -> 0.
+
+log(Module, String, Args, Fun) ->
+    case log_level(Fun) < log_level(?LOG_LEVEL:log_level()) of
+        true -> skip;
+        false -> case ?LOG_MODULES:log_modules() of
+            any -> ?LOGGER:Fun(Module, String, Args);
+            Allowed -> case lists:member(Module, Allowed) of
+                true -> ?LOGGER:Fun(Module, String, Args);
+                false -> skip end end end.
+
+info(Module, String, Args) -> log(Module, String, Args, info).
+info(String, Args) -> log(?MODULE, String, Args, info).
+info(String) -> log(?MODULE, String, [], info).
+
+warning(Module, String, Args) -> log(Module, String, Args, warning).
+warning(String, Args) -> log(?MODULE, String, Args, warning).
+warning(String) -> log(?MODULE, String, [], warning).
+
+error(Module, String, Args) -> log(Module, String, Args, error).
+error(String, Args) -> log(?MODULE, String, Args, error).
+error(String) -> log(?MODULE, String, [], error).
+
+% Convert and Utils API
+
+display(Element,Status) -> 
+   wf:wire("{ var x = qi('"++
+      wf:to_list(Element)++"'); if (x) x.style.display = '"++wf:to_list(Status)++"'; }").
+
+show(Element) -> display(Element,block).
+hide(Element) -> display(Element,none).
+
+atom(List) when is_list(List) -> wf:to_atom(string:join([ wf:to_list(L) || L <- List],"_"));
+atom(Scalar) -> wf:to_atom(Scalar).
+
+f(S)        -> wf_utils:f(S).
+f(S, Args)  -> wf_utils:f(S, Args).
+coalesce(L) -> wf_utils:coalesce(L).
+json(Json)  -> jsone:encode(Json).
+
+to_list(T)    -> wf_convert:to_list(T).
+to_atom(T)    -> wf_convert:to_atom(T).
+to_binary(T)  -> wf_convert:to_binary(T).
+to_integer(T) -> wf_convert:to_integer(T).
+
+jse(String)            -> wf_convert:js_escape(String).
+js_escape(String)      -> wf_convert:js_escape(String).
+hte(S)                 -> wf_convert:html_encode(S).
+hte(S, Encode)         -> wf_convert:html_encode(S, Encode).
+html_encode(S)         -> wf_convert:html_encode(S).
+html_encode(S, Encode) -> wf_convert:html_encode(S, Encode).
+url_encode(S)          -> wf_convert:url_encode(S).
+url_decode(S)          -> wf_convert:url_decode(S).
+hex_encode(S)          -> wf_convert:hex(S).
+hex_decode(S)          -> wf_convert:unhex(S).
+join(List,Delimiter)   -> wf_convert:join(List,Delimiter).
+format(Term)           -> wf_convert:format(Term).
+
+% These api are not really API
+
+unique_integer() -> try erlang:unique_integer() catch _:_ -> {MS,S,US} = erlang:now(), (MS*1000000+S)*1000000+US end.
+temp_id() -> "auto" ++ integer_to_list(unique_integer() rem 1000000).
+append(List, Key, Value) -> case Value of undefined -> List; _A -> [{Key, Value}|List] end.
+render(X) -> wf_render:render(X).
+
+init_context(R)-> n2o_cx:init_context(R).
+actions()      -> n2o_cx:actions().
+actions(Ac)    -> n2o_cx:actions(Ac).
+script()       -> n2o_cx:script().
+script(Script) -> n2o_cx:script(Script).
+context()      -> n2o_cx:context().
+context(Cx)    -> n2o_cx:context(Cx).
+context(Cx,Proto)        -> n2o_cx:context(Cx,Proto).
+context(Cx,Proto,UserCx) -> n2o_cx:context(Cx,Proto,UserCx).
+add_action(Action)       -> n2o_cx:add_action(Action).
+fold(Fun,Handlers,Ctx) -> n2o_cx:fold(Fun,Handlers,Ctx).
+
+config_multiple(Keys) -> [config(Key, "") || Key <- Keys].
+config(Key) -> config(n2o, Key, "").
+config(App, Key) -> config(App,Key, "").
+config(App, Key, Default) -> wf_utils:config(App, Key, Default).
+
+version() -> proplists:get_value(vsn,element(2,application:get_all_key(n2o))).
+
+setkey(Name,Pos,List,New) ->
+    case lists:keyfind(Name,Pos,List) of
+        false -> [New|List];
+        _Element -> lists:keyreplace(Name,Pos,List,New) end.

+ 2 - 0
test/bert.sh

@@ -0,0 +1,2 @@
+./bert_gen.erl > bert.data
+node bert_test.js

+ 32 - 0
test/bert_gen.erl

@@ -0,0 +1,32 @@
+#!/usr/bin/env escript
+-module(genbert).
+-compile([export_all]).
+
+object(D)   ->
+    case crypto:rand_uniform(0,5) of
+         0 -> tuple(D);
+         1 -> bin(D);
+         2 -> list(D);
+         3 -> bytes(D);
+         4 -> atom(D) end.
+
+uni(1) -> rnd(0,16#7F);
+uni(2) -> rnd(16#80,16#7FF);
+uni(3) -> [rnd(16#800,16#D7FF),rnd(16#E000,16#FFFD)];
+uni(4) -> rnd(16#10000,16#10FFFF).
+utf8() -> [uni(X)||X<-lists:seq(1,3)].
+unicode(0,Acc) -> lists:flatten(Acc);
+unicode(N,Acc) -> unicode(N-1, [utf8()|Acc]).
+
+size()     -> 20.
+rnd(A)     -> rnd(1,A).
+rnd(L,H)   -> crypto:rand_uniform(L,H).
+list(2)    -> [];
+list(D)    -> [ object(D+1) || _<- lists:seq(1,size()-D) ].
+tuple(D)   -> list_to_tuple(list(D)).
+bin(D)     -> list_to_binary(bytes(D)).
+bytes(_)   -> latin(rnd(size()),[]).
+atom(D)    -> list_to_atom(bytes(D)).
+latin(0,A) -> A;
+latin(N,A) -> latin(N-1, [rnd(size())+96|A]).
+main(_)    -> [ io:format("~w\n",[binary_to_list(term_to_binary(tuple(0)))]) || _ <- lists:seq(1,size()) ].

+ 23 - 0
test/bert_test.js

@@ -0,0 +1,23 @@
+var bert = require('../priv/protocols/bert.js');
+var utf8 = require('../priv/utf8.js');
+var fs = require('fs');
+
+utf8_dec = utf8.dec;
+utf8_toByteArray = utf8.enc;
+
+print = function (x) { return "["+Array.apply([], x ).join(",")+"]"; }
+pass = true;
+counter = 0;
+
+fs.readFileSync('bert.data').toString().split('\n').forEach(function (data) {
+    if (data == "") return;
+    pass = pass && (data==print(bert.enc(bert.dec(new Uint8Array(JSON.parse(data)).buffer))));
+    if (pass) { console.log("OK: "+counter); }
+    counter+=1;
+    if (!pass) {
+        console.log(data);
+        console.log(print(bert.enc(bert.dec(new Uint8Array(JSON.parse(data)).buffer))));
+        console.log(bert.dec(new Uint8Array(JSON.parse(data)).buffer));
+        console.log("ERROR: "+data);
+    }
+});

+ 19 - 0
test/casper/casper.js

@@ -0,0 +1,19 @@
+casper.test.begin("Basic connectivity", 1, function (test)
+{
+	test.info("Connecting")
+	casper
+	.start('http://localhost:8000/static/spa/login.htm')
+	.then(function ()
+	{
+		test.assertHttpStatus(200)
+		test.info('Loggin')
+		this.click('#loginButton')
+	}).waitForSelector('#upload', function()
+	{
+		test.info('Logged In.')
+	})
+	.run(function ()
+	{
+		test.done()
+	})
+})

+ 16 - 0
test/elements.erl

@@ -0,0 +1,16 @@
+-module(elements).
+-include_lib("n2o/include/wf.hrl").
+-include_lib("common_test/include/ct.hrl").
+-compile(export_all).
+
+main() -> 
+    Title = wf_render_elements:render_elements(title()),
+    Body = wf_render_elements:render_elements(body()),
+    #dtl{file = "index.html", app=n2o, folder = "test", bindings=[{title,Title},{body,Body}]}.
+
+title() -> "N2O Test".
+body() ->
+    [
+     #label{body = "test label"},
+     #textbox{body = "test textbox"}
+    ].

+ 12 - 0
test/index.html

@@ -0,0 +1,12 @@
+<html>
+<head>
+<title>{{title}}</title>
+</head>
+<body>
+{{body}}
+<script src='/static/nitrogen/jquery.js' type='text/javascript' charset='utf-8'></script>
+<script src='/static/nitrogen/n2o.js' type='text/javascript' charset='utf-8'></script>
+<script src='/static/nitrogen/bert.js' type='text/javascript' charset='utf-8'></script>
+<script>{{script}}</script>
+</body>
+</html>

+ 76 - 0
test/n2o_SUITE.erl

@@ -0,0 +1,76 @@
+-module(n2o_SUITE).
+-behaviour(cowboy_http_handler).
+-include_lib("common_test/include/ct.hrl").
+-include_lib("n2o/include/wf.hrl").
+-compile(export_all).
+
+suite() -> [{timetrap,{seconds,30}}].
+all() -> [{group, elements}].
+groups() -> [
+	     {elements, [], [elements]}
+	    ].
+
+init_per_suite(Config) ->
+    ok = application:start(crypto),
+    ok = application:start(ranch),
+    ok = application:start(cowboy),
+    Config.
+
+end_per_suite(_Config) ->
+    application:stop(cowboy),
+    application:stop(ranch),
+    application:stop(crypto),
+    ok.
+
+init_per_group(elements, Config) ->
+    Port = 33084,
+    Transport = ranch_tcp,
+    {ok, _} = cowboy:start_http(elements, 100, [{port, Port}], [
+								 {env, [{dispatch, init_dispatch()}]},
+								 {max_keepalive, 50},
+								 {timeout, 500}
+								]),
+    {ok, Client} = cowboy_client:init([]),
+    [{scheme, <<"http">>}, {port, Port}, {opts, []},
+     {transport, Transport}, {client, Client} | Config].
+
+end_per_group(Name, _) ->
+    cowboy:stop_listener(Name),
+    ok.
+
+init_dispatch() ->
+    cowboy_router:compile([{"localhost", [{'_', n2o_SUITE, []}]}]).
+
+build_url(Path, Config) ->
+    {scheme, Scheme} = lists:keyfind(scheme, 1, Config),
+    {port, Port} = lists:keyfind(port, 1, Config),
+    PortBin = list_to_binary(integer_to_list(Port)),
+    PathBin = list_to_binary(Path),
+    << Scheme/binary, "://localhost:", PortBin/binary, PathBin/binary >>.
+
+elements(Config) ->
+    Client = ?config(client, Config),
+    URL = build_url("/elements", Config),
+    ct:log("-> url ~p", [URL]),
+    {ok, Client2} = cowboy_client:request(<<"GET">>, URL, Client),
+    {ok, 200, Headers, Client3} = cowboy_client:response(Client2),
+    {ok, Body, _} = cowboy_client:response_body(Client3),
+    ct:log("-> response Body: ~p", [Body]),
+    {_, 10} = binary:match(Body, <<"test label">>),
+    {_, 12} = binary:match(Body, <<"test textbox">>),
+    ok.
+
+%% handle to process http request
+-record(state, {headers, body}).
+init({_Transport, http}, Req, _Opts) ->
+    {ok, Req, #state{}}.
+handle(Req, State) ->
+    RequestBridge = simple_bridge:make_request(cowboy_request_bridge, Req),
+    ResponseBridge = simple_bridge:make_response(cowboy_response_bridge, RequestBridge),
+    wf_context:init_context(RequestBridge, ResponseBridge),
+    %% wf_handler:set_handler(http_basic_auth_security_handler, n2o_auth),
+    {ok, NewReq} = wf_core:run(),
+    {ok, NewReq, State}.
+
+terminate(_Reason, _Req, _State) ->
+    ok.

+ 2 - 0
test/test.hrl

@@ -0,0 +1,2 @@
+-define(APP, n2o).
+-define(TEMPLATE, filename:join(lists:reverse(tl(lists:reverse(filename:split(code:priv_dir(?APP)))))) ++ "/test/index.html").