├── .gitignore ├── .gitmodules ├── CHANGES.md ├── Makefile ├── README.md ├── assets ├── ajax_spinner.gif ├── bootstrap │ ├── css │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap-responsive.min.css │ │ ├── bootstrap.css │ │ └── bootstrap.min.css │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ └── js │ │ ├── bootstrap.js │ │ └── bootstrap.min.js ├── css │ ├── docs.css │ └── sitewide.css ├── docs-agent-diagram.jpg ├── docs-grimwidget.png ├── docs-messaging-diagram.png ├── docs.js ├── favicon.png ├── hero-bg.jpg ├── icon-download.png ├── icon-idea.png ├── icon-link.png ├── icon-network.png ├── index.js ├── jquery-2.1.0.min.js ├── jquery.sticky.js ├── logo.png ├── marked.js ├── mdworker.js └── prism │ ├── prism.css │ └── prism.js ├── docs.html ├── docs └── en │ └── 0.6.2 │ ├── api │ ├── agent.md │ ├── bindrequestevents.md │ ├── bridgeserver.md │ ├── contenttypes.md │ ├── dispatch.md │ ├── eventemitter.md │ ├── eventhost.md │ ├── eventstream.md │ ├── httpheaders.md │ ├── pageserver.md │ ├── patchxhr.md │ ├── pipe.md │ ├── preferredtypes.md │ ├── promises.md │ ├── querylinks.md │ ├── relay.md │ ├── request.md │ ├── response.md │ ├── rtcbridgeserver.md │ ├── server.md │ ├── setdispatchwrapper.md │ ├── subscribe.md │ ├── uri_helpers.md │ ├── worker.md │ └── workerbridgeserver.md │ ├── example_mdviewer.md │ ├── hosts.md │ ├── httpl.md │ ├── linkheader.md │ ├── managing_servers.md │ ├── navscheme.md │ └── todosoa.md ├── examples ├── charcount.js ├── slugify.js └── toupper.js ├── index.html ├── local.js ├── local.min.js ├── package.json ├── scripts └── minify.sh ├── src ├── config.js ├── constants.js ├── index.js ├── promises.js ├── request-event.js ├── spawners.js ├── util │ ├── dom.js │ ├── event-emitter.js │ └── index.js ├── web │ ├── agent.js │ ├── bridge-server.js │ ├── content-types.js │ ├── dispatch.js │ ├── helpers.js │ ├── http-headers.js │ ├── httpl.js │ ├── relay.js │ ├── request.js │ ├── response.js │ ├── rtc-bridge-server.js │ ├── schemes.js │ ├── server.js │ ├── subscribe.js │ ├── uri-template.js │ └── worker-bridge-server.js └── worker │ ├── config.js │ ├── index.js │ └── page-bridge-server.js └── test ├── client.html ├── client └── browser.js ├── doctest ├── .gitignore ├── .gitmodules ├── .hgignore ├── .resources │ ├── CNAME │ ├── boilerplate │ │ ├── 404.html │ │ ├── css │ │ │ ├── main.css │ │ │ ├── normalize.css │ │ │ └── normalize.min.css │ │ ├── favicon.ico │ │ ├── index.html │ │ └── js │ │ │ ├── main.js │ │ │ └── vendor │ │ │ ├── jquery-1.8.1.min.js │ │ │ └── modernizr-2.6.1.min.js │ ├── build │ ├── doc.css │ ├── example.xml │ ├── footer.html │ ├── header.html │ ├── include-scripts.sh │ ├── retemplate.py │ ├── template.html │ ├── toc.js │ └── try.js ├── .syncignore ├── README.md ├── doctest.css ├── doctest.js ├── esprima │ ├── .travis.yml │ ├── ChangeLog │ ├── LICENSE.BSD │ ├── README.md │ ├── assets │ │ ├── codemirror │ │ │ ├── codemirror.css │ │ │ ├── codemirror.js │ │ │ └── javascript.js │ │ ├── forkme_right_red_aa0000.png │ │ ├── json2.js │ │ ├── prettify │ │ │ ├── prettify.css │ │ │ └── prettify.js │ │ ├── require.js │ │ ├── style.css │ │ └── yui │ │ │ ├── treeview-min.js │ │ │ ├── treeview-sprite.gif │ │ │ ├── treeview.css │ │ │ └── yahoo-dom-event.js │ ├── bin │ │ └── esparse.js │ ├── component.json │ ├── demo │ │ ├── checkenv.js │ │ ├── collector.html │ │ ├── collector.js │ │ ├── functiontrace.html │ │ ├── functiontrace.js │ │ ├── highlight.html │ │ ├── highlight.js │ │ ├── minify.html │ │ ├── minify.js │ │ ├── parse.css │ │ ├── parse.html │ │ ├── precedence.html │ │ ├── rewrite.html │ │ ├── rewrite.js │ │ ├── validate.html │ │ └── validate.js │ ├── doc │ │ └── index.html │ ├── esprima.js │ ├── examples │ │ ├── detectnestedternary.js │ │ ├── findbooleantrap.js │ │ └── tokendist.js │ ├── index.html │ ├── package.json │ ├── test │ │ ├── 3rdparty │ │ │ ├── XMLHttpRequest.js │ │ │ ├── acorn.js │ │ │ ├── angular-1.0.2.js │ │ │ ├── backbone-0.9.2.js │ │ │ ├── benchmark.js │ │ │ ├── codemirror-2.34.js │ │ │ ├── escodegen.js │ │ │ ├── esmorph.js │ │ │ ├── jquery-1.8.2.js │ │ │ ├── jquery.mobile-1.2.0.js │ │ │ ├── mootools-1.4.1.js │ │ │ ├── parse-js.js │ │ │ ├── platform.js │ │ │ ├── threejs-r51.js │ │ │ └── underscore-1.4.1.js │ │ ├── benchmarks.html │ │ ├── benchmarks.js │ │ ├── compare.html │ │ ├── compare.js │ │ ├── compat.html │ │ ├── compat.js │ │ ├── coverage.footer.html │ │ ├── coverage.header.html │ │ ├── coverage.html │ │ ├── index.html │ │ ├── module.html │ │ ├── module.js │ │ ├── reflect.js │ │ ├── run.js │ │ ├── runner.js │ │ └── test.js │ └── tools │ │ ├── generate-test-fixture.js │ │ ├── generate-unicode-regex.py │ │ └── update-coverage.sh ├── examples │ ├── examples-2.html │ ├── examples-2.js │ ├── examples.html │ └── node-example.js ├── index.html ├── package.json ├── reference.html ├── try.html └── tutorial.html ├── helpers.js ├── rtcpeer.html ├── rtcpeer └── tests.js ├── web.html ├── web ├── _setup.js ├── _worker.js ├── agent.js ├── core.js ├── events.js ├── helpers.js └── test-server.js ├── worker.html ├── worker ├── messaging.js ├── worker1.js └── worker2.js ├── xhrpatch.html └── xhrpatch ├── _setup.js └── requests.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | #*# 3 | node_modules 4 | .htaccess 5 | db 6 | .DS_Store 7 | UglifyJS2 8 | *.log 9 | build.sh 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/promises"] 2 | path = src/promises 3 | url = git@github.com:pfraze/promises.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | setup: clean build buildmin 2 | @echo "Done!" 3 | 4 | clean: 5 | @-rm local.js local.min.js 6 | @echo Cleaned Out Libraries 7 | 8 | build: local.js 9 | @echo Browserified library 10 | local.js: 11 | browserify src/index.js -o local.js 12 | 13 | buildmin: local.min.js 14 | @echo Built Minified Versions 15 | local.min.js: local.js 16 | @./scripts/minify.sh $@ $^ 17 | 18 | deps: uglifyjs 19 | uglifyjs: 20 | -git clone git://github.com/mishoo/UglifyJS2.git 21 | (cd UglifyJS2 && npm link .) -------------------------------------------------------------------------------- /assets/ajax_spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/ajax_spinner.gif -------------------------------------------------------------------------------- /assets/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /assets/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /assets/css/docs.css: -------------------------------------------------------------------------------- 1 | 2 | #viewer-nav { width: 190px; float: left; margin-left: 25px; } 3 | #viewer { margin: 30px 20px 0 280px; max-width: 960px; } 4 | #viewer:empty { background: center 200px no-repeat url(../ajax_spinner.gif); min-height: 300px; } 5 | @media (max-width: 1300px) { 6 | #viewer-nav { width: 140px; margin-left: 0; } 7 | #viewer { margin: 30px 20px 0 190px; max-width: 960px; } 8 | } -------------------------------------------------------------------------------- /assets/css/sitewide.css: -------------------------------------------------------------------------------- 1 | body { color: #555; font-family: 'Roboto', sans-serif; font-weight: 300; } 2 | pre, code { font-family: Consolas, "Courier New", monospace; color: #5E5E5E; background: #f5f2f0; } 3 | a { color: #3184D6; } 4 | p { margin: 13px 0 17px; } 5 | blockquote { border-left: 5px solid #ddd; } 6 | blockquote p { font-size:14px } 7 | h2 { font-size:30px; border-bottom:1px solid gray; font-weight:normal; margin-top:16px } 8 | h3 { font-size:20px } 9 | 10 | .centered { text-align: center; } 11 | #hero { width: 100%; height: 450px; background: linear-gradient(rgba(31, 108, 177, 1), rgba(0, 0, 0, 0.5)), url(/assets/hero-bg.jpg) left -640px no-repeat; } 12 | @media (min-width: 1500px) { 13 | /* hero background is now too small, so lets autosize it to cover */ 14 | #hero { background-position: top left; background-size: cover; } 15 | } 16 | #hero h1 { text-align: center; color: white; font-size: 70px; padding-top: 155px; margin: 0 50px 0 0; } 17 | #hero h1 span { color: #2D9AE4; } 18 | #hero h4 { font-weight: normal; text-align: center; color: white; padding-top: 75px; margin: 0 50px 0 0; } 19 | 20 | #nav { background: #1F6CB1; box-shadow: 5px 0px 25px rgba(0,0,0,0.5); width: 100%; text-align: center; } 21 | #nav ul { font-size: 26px; margin: 0 auto; display: inline-block; padding-right: 50px; } 22 | #nav ul li { margin: 0; } 23 | #nav ul li.active { border-bottom: 3px solid white; } 24 | #nav ul li:hover { border-bottom: 3px solid white; } 25 | #nav ul li a { color: #FAFAFA; display: inline-block; padding: 20px 20px; } 26 | #nav ul li a:hover { color: #FAFAFA; text-decoration: none; } 27 | #nav #logo { display: none; color: white; font-weight: bold; position: absolute; top: 0; left: 0; font-size: 32px; padding: 20px;} 28 | #nav #logo span { color: #2D9AE4; } 29 | .is-sticky #nav #logo { display: block; } 30 | 31 | .container h3 { font-size: 24px; } 32 | .container p, .container ul li { font-size: 16px; } 33 | .container ul li { color: gray; margin-bottom: 12px; padding: 0 2px; } 34 | .container .intro { margin: 200px 20px; } 35 | .container .intro td:first-child { width: 125px; } 36 | .container .intro td:first-child img { width: 100px; } 37 | .container .overview { margin-bottom: 200px; width: 100%; } 38 | .container .overview td { vertical-align: top; } 39 | @media (min-width: 1080px) { 40 | .container.container-wide { width: 1080px; } 41 | .container .overview td div { width: 300px; margin: 0 auto; } 42 | } 43 | .container .overview td img { display: block; margin: 0 auto; padding-right: 20px; } 44 | .container .overview td:nth-child(1) { padding-right: 10px; } 45 | .container .overview td:nth-child(2) { padding: 0 10px; } 46 | .container .overview td:nth-child(3) { padding-left: 10px; } 47 | .container > .row { margin-bottom: 150px } -------------------------------------------------------------------------------- /assets/docs-agent-diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/docs-agent-diagram.jpg -------------------------------------------------------------------------------- /assets/docs-grimwidget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/docs-grimwidget.png -------------------------------------------------------------------------------- /assets/docs-messaging-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/docs-messaging-diagram.png -------------------------------------------------------------------------------- /assets/docs.js: -------------------------------------------------------------------------------- 1 | var viewer = document.getElementById('viewer'); 2 | var viewNav = document.getElementById('viewer-nav'); 3 | 4 | // Load the markdown conversion worker 5 | local.spawnWorkerServer('./assets/mdworker.js', { domain: 'mdworker.js' }); 6 | 7 | function getContent() { 8 | var path = window.location.hash.slice(1) || 'README.md'; 9 | var baseUrl = (location.origin||(location.protocol+'//'+location.host)); 10 | var url = local.joinRelPath(baseUrl, path); 11 | 12 | // Update nav higlight 13 | var active = viewNav.querySelector('.active'); 14 | if (active) active.classList.remove('active'); 15 | viewNav.querySelector('a[href="#'+path+'"]').parentNode.classList.add('active'); 16 | 17 | // Get markdown 18 | local.GET(url) 19 | .then(function(res) { 20 | // Convert to HTML 21 | return local.POST(res.body, 'httpl://mdworker.js'); 22 | }) 23 | .then(function (res) { 24 | // Render 25 | viewer.innerHTML = res.body; 26 | window.scrollTo(0,0); 27 | Prism.highlightAll(); 28 | }) 29 | .fail(function (res) { 30 | console.error('Failed loading '+path, res); 31 | viewer.innerHTML = '

'+res.status+' '+res.reason+'

There was an error loading the document.
Sorry for the inconvenience! Please let me know at github.com/grimwire/local/issues.'; 32 | }); 33 | } 34 | window.onhashchange = getContent; 35 | getContent(); -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/favicon.png -------------------------------------------------------------------------------- /assets/hero-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/hero-bg.jpg -------------------------------------------------------------------------------- /assets/icon-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/icon-download.png -------------------------------------------------------------------------------- /assets/icon-idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/icon-idea.png -------------------------------------------------------------------------------- /assets/icon-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/icon-link.png -------------------------------------------------------------------------------- /assets/icon-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/icon-network.png -------------------------------------------------------------------------------- /assets/index.js: -------------------------------------------------------------------------------- 1 | // Traffic logging 2 | local.logAllExceptions = true; 3 | local.setDispatchWrapper(function(req, res, dispatch) { 4 | var res_ = dispatch(req, res); 5 | res_.then( 6 | function() { console.log(req, res); }, 7 | function() { console.error(req, res); } 8 | ); 9 | }); 10 | 11 | // Request events 12 | try { local.bindRequestEvents(document.body); } 13 | catch (e) { console.error('Failed to bind body request events.', e); } 14 | document.body.addEventListener('request', function(e) { 15 | local.dispatch(e.detail); 16 | }); 17 | 18 | // Pipes server 19 | var pipesLinks = []; 20 | local.addServer('pipes', function(req, res) { 21 | if (req.path != '/') { return res.writeHead(404, 'Not Found').end(); } 22 | if (req.method == 'ADD') { 23 | $('#pipes-errout').html(''); 24 | req.on('end', function() { 25 | if (!req.body.url) { 26 | $('#pipes-errout').html('URL is required.'); 27 | return res.writeHead(422, 'Invalid Entity').end(); 28 | } 29 | verifyTransformer(req.body.url).then(function(selfLink) { 30 | $('#url-input').val(''); 31 | pipesLinks.push(selfLink); 32 | renderPipeline(); 33 | res.writeHead(204, 'OK, no content').end(); 34 | }).fail(function(err) { 35 | if (typeof err.status != 'undefined') { 36 | $('#pipes-errout').html(err.status + ': ' + err.reason); 37 | } else { 38 | $('#pipes-errout').html(err.toString()); 39 | } 40 | res.writeHead(422, 'Invalid Entity').end(); 41 | }); 42 | }); 43 | return; 44 | } 45 | res.writeHead(405, 'Invalid Method').end(); 46 | }); 47 | 48 | // Verify the given URL is a transformer 49 | function verifyTransformer(url) { 50 | return local.HEAD(url).then(function(res) { 51 | var selfLink = local.queryLinks(res, { rel: 'self httplocal.com/transformer' })[0]; 52 | if (!selfLink) { 53 | throw 'Not a valid transformer'; 54 | } 55 | return selfLink; 56 | }); 57 | } 58 | 59 | // Draw current links, then run sequence and output data 60 | function renderPipeline() { 61 | $('#pipes-sequence').html(pipesLinks.map(function(link) { 62 | var url = local.UriTemplate.parse(link.href).expand({}); 63 | var title = link.title || link.id || url; 64 | return makeSafe(title); 65 | }).join('
')); 66 | $('#pipes-errout').html('Running...'); 67 | var outs = {}; 68 | execSequence( 69 | function(i, s) { 70 | if (!outs[i]) outs[i] = ''; 71 | outs[i] += s; 72 | renderOutput(outs); 73 | }, 74 | function() { 75 | $('#pipes-errout').html('Done!'); 76 | renderOutput(outs); 77 | } 78 | ).write('Welcome, my son. Welcome... to the machine.').end(); 79 | } 80 | 81 | function renderOutput(outs) { 82 | var out = ''; 83 | for (var k in outs) { 84 | out += 'Output '+(+k+1)+': '+makeSafe(outs[k])+'
'; 85 | } 86 | $('#pipes-output').html(out); 87 | } 88 | 89 | function execSequence(outCb, doneCb) { 90 | var requests = []; 91 | pipesLinks.forEach(function(link, i) { 92 | var url = local.UriTemplate.parse(link.href).expand({}); 93 | // Create a new request stream and add it to our array 94 | var req = new local.Request({ 95 | method: 'POST', 96 | url: url, 97 | Content_Type: 'text/plain', 98 | Accept: 'text/plain', 99 | stream: true 100 | }); 101 | requests.push(req); 102 | 103 | // Start the request stream 104 | local.dispatch(req).always(function(res) { 105 | // On each chunk, pipe or emit 106 | res.on('data', function(chunk) { 107 | outCb(i, chunk); 108 | if (requests[i+1]) { requests[i+1].write(chunk); } 109 | }); 110 | // On each 'end', cascade the stream-close 111 | res.on('end', function() { 112 | delete requests[i]; 113 | if (i+1 === pipesLinks.length) { closeRequests(); doneCb(); } 114 | else if (requests[i+1]) { requests[i+1].end(); } 115 | }); 116 | }); 117 | }); 118 | // Helper to shutdown any unclosed streams at exit 119 | function closeRequests() { 120 | requests.forEach(function(req) { if (req) { req.end(); }}); 121 | } 122 | return requests[0]; 123 | } 124 | 125 | function makeSafe(str) { 126 | return str.replace(//g, '>'); 127 | } -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/assets/logo.png -------------------------------------------------------------------------------- /assets/mdworker.js: -------------------------------------------------------------------------------- 1 | importScripts('../local.js'); 2 | importScripts('./marked.js'); 3 | marked.setOptions({ gfm: true, tables: true }); 4 | 5 | function main(req, res) { 6 | req.on('end', function() { 7 | res.writeHead(200, 'OK', {'Content-Type': 'text/html'}); 8 | res.end(marked(''+req.body)); 9 | }); 10 | } -------------------------------------------------------------------------------- /assets/prism/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 11 | direction: ltr; 12 | text-align: left; 13 | white-space: pre; 14 | word-spacing: normal; 15 | 16 | -moz-tab-size: 4; 17 | -o-tab-size: 4; 18 | tab-size: 4; 19 | 20 | -webkit-hyphens: none; 21 | -moz-hyphens: none; 22 | -ms-hyphens: none; 23 | hyphens: none; 24 | } 25 | 26 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 27 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 28 | text-shadow: none; 29 | background: #b3d4fc; 30 | } 31 | 32 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 33 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 34 | text-shadow: none; 35 | background: #b3d4fc; 36 | } 37 | 38 | @media print { 39 | code[class*="language-"], 40 | pre[class*="language-"] { 41 | text-shadow: none; 42 | } 43 | } 44 | 45 | /* Code blocks */ 46 | pre[class*="language-"] { 47 | padding: 1em; 48 | margin: .5em 0; 49 | overflow: auto; 50 | } 51 | 52 | :not(pre) > code[class*="language-"], 53 | pre[class*="language-"] { 54 | background: #f5f2f0; 55 | } 56 | 57 | /* Inline code */ 58 | :not(pre) > code[class*="language-"] { 59 | padding: .1em; 60 | border-radius: .3em; 61 | } 62 | 63 | .token.comment, 64 | .token.prolog, 65 | .token.doctype, 66 | .token.cdata { 67 | color: slategray; 68 | } 69 | 70 | .token.punctuation { 71 | color: #999; 72 | } 73 | 74 | .namespace { 75 | opacity: .7; 76 | } 77 | 78 | .token.property, 79 | .token.tag, 80 | .token.boolean, 81 | .token.number, 82 | .token.constant, 83 | .token.symbol { 84 | color: #905; 85 | } 86 | 87 | .token.selector, 88 | .token.attr-name, 89 | .token.string, 90 | .token.builtin { 91 | color: #690; 92 | } 93 | 94 | .token.operator, 95 | .token.entity, 96 | .token.url, 97 | .language-css .token.string, 98 | .style .token.string, 99 | .token.variable { 100 | color: #a67f59; 101 | background: hsla(0,0%,100%,.5); 102 | } 103 | 104 | .token.atrule, 105 | .token.attr-value, 106 | .token.keyword { 107 | color: #07a; 108 | } 109 | 110 | 111 | .token.regex, 112 | .token.important { 113 | color: #e90; 114 | } 115 | 116 | .token.important { 117 | font-weight: bold; 118 | } 119 | 120 | .token.entity { 121 | cursor: help; 122 | } 123 | 124 | pre.line-numbers { 125 | position: relative; 126 | padding-left: 3.8em; 127 | counter-reset: linenumber; 128 | } 129 | 130 | pre.line-numbers > code { 131 | position: relative; 132 | } 133 | 134 | .line-numbers .line-numbers-rows { 135 | position: absolute; 136 | pointer-events: none; 137 | top: 0; 138 | font-size: 100%; 139 | left: -3.8em; 140 | width: 3em; /* works for line-numbers below 1000 lines */ 141 | letter-spacing: -1px; 142 | border-right: 1px solid #999; 143 | 144 | -webkit-user-select: none; 145 | -moz-user-select: none; 146 | -ms-user-select: none; 147 | user-select: none; 148 | 149 | } 150 | 151 | .line-numbers-rows > span { 152 | pointer-events: none; 153 | display: block; 154 | counter-increment: linenumber; 155 | } 156 | 157 | .line-numbers-rows > span:before { 158 | content: counter(linenumber); 159 | color: #999; 160 | display: block; 161 | padding-right: 0.8em; 162 | text-align: right; 163 | } -------------------------------------------------------------------------------- /docs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Local.js 0.6.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /docs/en/0.6.2/api/bindrequestevents.md: -------------------------------------------------------------------------------- 1 | bindRequestEvents() 2 | =================== 3 | 4 | --- 5 | 6 | ```javascript 7 | local.bindRequestEvents(document.body); 8 | document.body.addEventListener('request', function(e) { local.dispatch(e.detail); }); 9 | ``` 10 | Converts unhandled 'click' and 'submit' events into custom 'request' events. Allows links and forms to target 'httpl' hosts. 11 | 12 | ### local.bindRequestEvents(element) 13 | 14 | - `element`: required Element, sets the area which will capture events 15 | 16 | --- 17 | 18 | ### Request Generation 19 | 20 | Local.js extracts request parameters from the HTML element attributes. Elements with `target` set to "_top" or "_blank" are not caught, causing them to execute their default behavior. 21 | 22 | ```markup 23 | Foo 24 | 25 | 26 | Foo 27 | 28 | 29 |
30 | 31 | 32 | 33 |
34 | 40 | ``` 41 | 42 | --- 43 | 44 | ### Form Overrides 45 | 46 | The button that was clicked to submit the form will have its value added to the body, if set. Additionally, the submitting element (and its wrapping fieldset) can override request options. 47 | 48 | ```markup 49 |
50 | 51 | 52 | 53 | 54 | 59 | 60 | 61 |
66 | 67 |
68 |
69 | ``` 70 | 71 | Each of the buttons in this example will produce a different request. 72 | 73 | --- 74 | 75 | ### Element Aliases 76 | 77 | If you want a non-`` element to generate requests, use the `data-local-alias` attribute. 78 | 79 | ```markup 80 |

Foo

81 | 82 |
Foo 83 | ``` 84 | 85 | Currently, "a" is the only supported alias, but "button" is planned for future releases to add form-submit behavior to arbitrary elements. 86 | 87 | --- 88 | 89 | ### File Uploads 90 | 91 | If a form includes an ``, Local.js will use the `FileReader` API to load the file and include it in the request body. This is an asyncronous process, so you should call `finishPayloadFileReads()` before dispatching the request. 92 | 93 | ```javascript 94 | document.body.addEventListener('request', function(e) { 95 | local.util.finishPayloadFileReads(e.detail) 96 | .then(function(request) { 97 | local.dispatch(request); 98 | }); 99 | }); 100 | 101 | ``` 102 | 103 | ### local.util.finishPayloadFileReads(request) 104 | 105 | - `request`: required `local.Request` 106 | - returns `local.Promise` -------------------------------------------------------------------------------- /docs/en/0.6.2/api/bridgeserver.md: -------------------------------------------------------------------------------- 1 | BridgeServer 2 | ============ 3 | 4 | --- 5 | 6 | Descends from `local.Server`. 7 | 8 | Base type for all servers which pipe requests between separated environments (eg WorkerBridgeServer, RTCBridgeServer). Requires the channel between the environments to be reliable and capable of transporting arbitrarily-sized messages. 9 | 10 | ## local.BridgeServer 11 | 12 | ### .isChannelActive() 13 | 14 | - returns boolean 15 | 16 | Indicates whether the channel is ready. If false, new outgoing messages will be buffered. Should be overridden. 17 | 18 | --- 19 | 20 | ### .channelSendMsg(msg) 21 | 22 | - `msg`: required string 23 | 24 | Sends the given message to the remote environment. Should be overridden. 25 | 26 | ```javascript 27 | WorkerServer.prototype.channelSendMsg = function(msg) { 28 | this.worker.postMessage(msg); 29 | }; 30 | ``` 31 | 32 | --- 33 | 34 | ### .onChannelMessage(msg) 35 | 36 | - `msg`: required string 37 | 38 | Handles messages from the remote environment. Should be called by the descendent type on new messages. 39 | 40 | --- 41 | 42 | ### .handleRemoteRequest(req, res) 43 | 44 | - `req`: required local.Request 45 | - `res`: required local.Response 46 | 47 | Called to handle requests by the remote environment. Should be overridden. 48 | 49 | --- 50 | 51 | ### .flushBufferedMessages() 52 | 53 | Flushes any messages which were buffered before the channel was active. Should be called by the descendent type on channel open. 54 | 55 | --- 56 | 57 | ### .useMessageReordering(v) 58 | 59 | - `v`: required boolean 60 | 61 | Enables/disables head-of-line blocking in streams through message-numbering. Useful for channels which do not guarantee order. -------------------------------------------------------------------------------- /docs/en/0.6.2/api/contenttypes.md: -------------------------------------------------------------------------------- 1 | contentTypes 2 | ============ 3 | 4 | --- 5 | 6 | A registry of de/serializers for various mimetypes. Includes "application/json", "application/x-www-form-urlencoded", and "text/event-stream" by default. 7 | 8 | ```javascript 9 | local.contentTypes.register('application/json', 10 | function (obj) { 11 | try { 12 | return JSON.stringify(obj); 13 | } catch (e) { 14 | return ''; 15 | } 16 | }, 17 | function (str) { 18 | try { 19 | return JSON.parse(str); 20 | } catch (e) { 21 | return null; 22 | } 23 | } 24 | ); 25 | local.contentTypes.serialize('application/json', { foo:'bar' }); 26 | // => '{ "foo":"bar" }' 27 | local.contentTypes.deserialize('application/json', '{ "foo":"bar" }'); 28 | // => { foo:'bar' } 29 | ``` 30 | 31 | De/serializers are chosen by best match, according to the specificity of the provided mimetype. For instance, if 'application/foobar+json' is used, it will search for 'application/foobar+json', then 'application/json', then 'application'. If no match is found, the given parameter is returned as-is. 32 | 33 | ## local.contentTypes 34 | 35 | ### .register(mimetype, serializeFn, deserializeFn) 36 | 37 | - `mimetype`: required string 38 | - `serializeFn`: required function(obj) 39 | - `deserializeFn`: required function(str) 40 | 41 | ### .serialize(mimetype, object) 42 | 43 | - `mimetype`: required string 44 | - `object`: required object 45 | - returns string 46 | 47 | ### .deserialize(mimetype, string) 48 | 49 | - `mimetype`: required string 50 | - `string`: required string 51 | - returns object -------------------------------------------------------------------------------- /docs/en/0.6.2/api/dispatch.md: -------------------------------------------------------------------------------- 1 | dispatch() 2 | ========== 3 | 4 | --- 5 | 6 | Sends a new request and returns a promise for the response. 7 | 8 | ```javascript 9 | var resPromise = local.dispatch({ 10 | method: 'POST', 11 | url: 'httpl://myserver', 12 | headers: { 'content-type': 'application/json', accept: 'text/html' }, 13 | body: myMessage 14 | }); 15 | resPromise.then( 16 | function(res) { 17 | assert(res.status >= 200 && res.status <= 399); 18 | }, 19 | function(res) { 20 | assert(res.status >= 400 && res.status <= 599); 21 | } 22 | ); 23 | ``` 24 | 25 | ### local.http.dispatch(request) 26 | 27 | `request` must fit the following schema description: 28 | 29 | ``` 30 | { 31 | url: required string 32 | method: optional string; defaults to 'GET' 33 | query: optional object; a map of query parameters to add to the url 34 | headers: optional object; should include a (all lower-case) map of headers to values 35 | body: optional object; the request payload 36 | stream: optional boolean; specifies whether the response should be given in chunks 37 | binary: optional boolean; receive a binary arraybuffer response? Only applies to HTTP/S 38 | } 39 | ``` 40 | 41 | All requests are sent on the next tick. 42 | 43 | » Request, » Response 44 | 45 | Local includes the following common request sugars: 46 | 47 | - `local.HEAD(request)` 48 | - `local.GET(request)` 49 | - `local.DELETE(request)` 50 | - `local.SUBSCRIBE(request)` 51 | - `local.POST(body, request)` 52 | - `local.PUT(body, request)` 53 | - `local.PATCH(body, request)` 54 | - `local.NOTIFY(body, request)` 55 | 56 | Any capitalized attributes in the request object are treated as headers and moved into the `headers` object on dispatch. Underscores are converted into dashes: 57 | 58 | ```javascript 59 | local.POST(mydata, { url: 'http://grimwire.com', Content_Type: 'application/json', Accept: 'text/html' }) 60 | ``` 61 | 62 | --- 63 | 64 | ### Server Functions 65 | 66 | Server functions are given copies of the `local.Request` object, but with `url` replaced with `path`. 67 | 68 | ```javascript 69 | local.addServer('myserver', function(req, res) { 70 | console.log(req); /* => 71 | { 72 | path: '/', 73 | method: 'GET', 74 | headers: { ... } 75 | } 76 | */ 77 | req.on('end', function() { 78 | // ... 79 | }); 80 | }); 81 | ``` 82 | 83 | All servers are called when the request headers are first dispatched, not when the request ends. This means you must wait for the 'end' event if you want to use the request body. 84 | 85 | --- 86 | 87 | ### Request/Response Bodies 88 | 89 | In the case of both request and response streams, the body is automatically buffered. When the stream ends, Local.js will use the 'content-type' header to attempt deserialization. It will set the `body` attribute of the request/response to the resulting object (if parsing is successful) or string (if not). 90 | 91 | You can add new content-type support with `local.contentTypes.register()`. 92 | 93 | » contentTypes 94 | 95 | --- 96 | 97 | ### Request Streaming 98 | 99 | If you want to stream the body of the request, use a `local.Request` object in `dispatch()`, then use the request's API to add data and finish the stream. 100 | 101 | ```javascript 102 | var req = new local.Request({ 103 | method: 'POST', 104 | url: 'httpl://myserver', 105 | headers: { 'content-type': 'application/json', accept: 'text/html' } 106 | }); 107 | local.http.dispatch(req); 108 | req.send(chunk1); 109 | req.send(chunk2); 110 | req.end(); 111 | ``` 112 | 113 | You must always call end() to complete the request when streaming. 114 | 115 | --- 116 | 117 | ### Response Streaming 118 | 119 | By default, Local.js will fulfill the response promise after the response stream ends. However, if `stream` is set to true, it will fulfill when the headers arrive, allowing you to handle the chunks individually. 120 | 121 | ```javascript 122 | local.dispatch({ url: 'httpl://myserver', headers: { accept: 'text/plain' }, stream: true }) 123 | .then(function(res) { 124 | res.on('data', function(chunk) { /* ... */ }); 125 | res.on('end', function(chunk) { /* ... */ }); 126 | }); 127 | ``` -------------------------------------------------------------------------------- /docs/en/0.6.2/api/eventemitter.md: -------------------------------------------------------------------------------- 1 | EventEmitter 2 | ============ 3 | 4 | --- 5 | 6 | Event emitter utility class. 7 | 8 | ```javascript 9 | var emitter = new local.util.EventEmitter(); 10 | emitter.on(['foo', 'bar'], console.log.bind(console, 'foobar')); 11 | emitter.on('baz', console.log.bind(console, 'baz')); 12 | emitter.emit('foo', 'hello'); 13 | // => foobar hello 14 | emitter.emit('baz', 'world'); 15 | // => baz world 16 | ``` 17 | 18 | ### local.util.mixinEventEmitter(obj) 19 | 20 | - `obj`: required object 21 | 22 | Calls the EventEmitter constructor on the object and mixes in all of the methods. 23 | 24 | ## local.util.EventEmitter 25 | 26 | ### .emit(eventName, args...) 27 | 28 | - `eventName`: required string 29 | - `args`: optional, each argument after `eventName` will be applied to the listener functions 30 | 31 | Returns `false` if no handlers are registered to the `eventName` and `true` otherwise. 32 | 33 | --- 34 | 35 | ### .on/addListener(eventName, listenerFn) 36 | 37 | - `eventName`: required string 38 | - `listenerFn`: required function 39 | - returns `this` 40 | 41 | --- 42 | 43 | ### .once(eventName, listenerFn) 44 | 45 | - `eventName`: required string 46 | - `listenerFn`: required function 47 | - returns `this` 48 | 49 | --- 50 | 51 | ### .removeListener(eventName, listenerFn) 52 | 53 | - `eventName`: required string 54 | - `listenerFn`: required function 55 | - returns `this` 56 | 57 | --- 58 | 59 | ### .removeAllListeners(eventName) 60 | 61 | - `eventName`: required string 62 | - `listenerFn`: required function 63 | - returns `this` 64 | 65 | --- 66 | 67 | ### .suspendEvents() 68 | 69 | Adds to the lock counter on the emitter, causing it to buffers all received events until `resumeEvents()` releases the lock. 70 | 71 | --- 72 | 73 | ### .resumeEvents() 74 | 75 | Decrements the lock counter. If the lock counter reaches 0, buffered events are fired (in order received) and future events are fired immediately. 76 | 77 | --- 78 | 79 | ### .isSuspended() 80 | 81 | - returns bool -------------------------------------------------------------------------------- /docs/en/0.6.2/api/eventhost.md: -------------------------------------------------------------------------------- 1 | EventHost 2 | ========= 3 | 4 | Manages response streams and provides functions to send event chunks en masse. 5 | 6 | ```javascript 7 | var myevents = new local.EventHost(); 8 | local.addServer('eventhost', function(req, res) { 9 | res.writeHead(200, 'ok', { 'content-type': 'text/event-stream' }); 10 | myevents.addStream(res); 11 | }); 12 | // ... 13 | myEvents.emit('news', { foo: 'bar' }); 14 | ``` 15 | 16 | ### local.EventHost() 17 | 18 | Creates a new instance. 19 | 20 | ## local.EventHost 21 | 22 | ### .addStream(response) 23 | 24 | - `response`: required local.Response, the stream 25 | - returns number 26 | 27 | Adds the stream as a recipient of future events. The returned number can be used as an identifier for the stream in future calls to the EventHost. 28 | 29 | ### .endStream(response) 30 | 31 | - `response`: required number|local.Response 32 | 33 | Closes the stream and removes it from the EventHost. 34 | 35 | ### .endAllStreams() 36 | 37 | Closes all streams and removes them from the EventHost. 38 | 39 | ### .emit(event, data, opts) 40 | 41 | - `event`: required string, the event name 42 | - `data`: optional any, the data to send with the event 43 | - `ops.exclude`: optional number|local.Response|Array(number)|Array(local.Response), streams which should not receive the event 44 | 45 | ### .emitTo(response, event, data) 46 | 47 | - `response`: required number|local.Response, the stream that should receive the event 48 | - `event`: required string, the event name 49 | - `data`: optional any, the data to send with the event -------------------------------------------------------------------------------- /docs/en/0.6.2/api/eventstream.md: -------------------------------------------------------------------------------- 1 | EventStream 2 | =========== 3 | 4 | --- 5 | 6 | Wraps a response stream with an event-listening interface. Created by `local.subscribe()`. Descends from `local.util.EventEmitter`. 7 | 8 | ```javascript 9 | var news = local.subscribe('http://myhost.com/news'); 10 | news.on('update', console.log.bind(console)); 11 | // { event: 'update', data: 'something has happened!' } 12 | ``` 13 | 14 | ## local.EventStream 15 | 16 | ### .close() 17 | 18 | Closes the stream. 19 | 20 | ### .reconnect() 21 | 22 | Establishes the event-stream by redispatching the original request. 23 | 24 | ### .getUrl() 25 | 26 | - returns string, the URL hosting the event stream -------------------------------------------------------------------------------- /docs/en/0.6.2/api/httpheaders.md: -------------------------------------------------------------------------------- 1 | httpHeaders 2 | =========== 3 | 4 | --- 5 | 6 | A registry of de/serializers for various headers. Includes "link" and "accept" by default. 7 | 8 | ```javascript 9 | local.httpHeaders.register('link', 10 | function (obj) { /* ... */ }, 11 | function (str) { /* ... */ } 12 | ); 13 | local.httpHeaders.serialize('link', { href: 'httpl://foo', rel: 'service' }); 14 | // => '; rel="service"' 15 | local.httpHeaders.deserialize('link', '; rel="service"'); 16 | // => { href: 'httpl://foo', rel: 'service' } 17 | ``` 18 | 19 | Received requests and responses have their headers automatically parsed by the available deserializers and stored in `parsedHeaders`. The reverse is true of sent requests/responses. 20 | 21 | ## local.httpHeaders 22 | 23 | ### .register(header, serializeFn, deserializeFn) 24 | 25 | - `header`: required string 26 | - `serializeFn`: required function(obj) 27 | - `deserializeFn`: required function(str) 28 | 29 | ### .serialize(header, object) 30 | 31 | - `header`: required string 32 | - `object`: required object 33 | - returns string 34 | 35 | ### .deserialize(header, string) 36 | 37 | - `header`: required string 38 | - `string`: required string 39 | - returns object -------------------------------------------------------------------------------- /docs/en/0.6.2/api/pageserver.md: -------------------------------------------------------------------------------- 1 | PageServer 2 | ========== 3 | 4 | Descends from `local.BridgeServer`. Provides request/response exchange over connections to the page. 5 | 6 | Automatically created and assigned a URL on connect. The URL follows the scheme of `.page`. For instance, the hostpage can be reached at 'httpl://0.page'. 7 | 8 | ## local.PageServer 9 | 10 | ### .id 11 | 12 | A number, the index assigned to the page on connection. This host page will always have an id of 0. 13 | 14 | ### .isHostPage 15 | 16 | A boolean, is this the page that created the worker? -------------------------------------------------------------------------------- /docs/en/0.6.2/api/patchxhr.md: -------------------------------------------------------------------------------- 1 | patchXHR() 2 | ========== 3 | 4 | --- 5 | 6 | Patches `XMLHttpRequest` to recognize "httpl:" URIs and correctly dispatch to them. Useful for libraries which rely on XHR and can't easily be ported to use Local.js' dispatcher. 7 | 8 | ```javascript 9 | local.patchXHR(); 10 | var xhr = new XMLHttpRequest(); 11 | xhr.open('GET', 'httpl://myserver'); 12 | xhr.onreadystatechange = function() { 13 | if (xhr.readyState == 4) { 14 | console.log(xhr.getResponseHeader('content-type')); // => 'application/json' 15 | console.log(xhr.response); // => { foo: 'bar' } 16 | } 17 | }; 18 | xhr.send(); 19 | ``` 20 | --- 21 | 22 | ### local.patchXHR() 23 | 24 | Overwrites `XMLHttpRequest` with the localjs dispatcher. 25 | -------------------------------------------------------------------------------- /docs/en/0.6.2/api/pipe.md: -------------------------------------------------------------------------------- 1 | pipe() 2 | ====== 3 | 4 | --- 5 | 6 | Pipes the response from a request into a destination response. 7 | 8 | ```javascript 9 | // Fetch JSON and format it for HTML 10 | function headerRewrite(headers) { 11 | headers['content-type'] = 'text/html'; 12 | return headers; 13 | } 14 | function bodyRewrite(body) { 15 | return JSON.stringify(body || {}) 16 | .replace(//g,'>'); 18 | } 19 | local.pipe(response, local.dispatch(jsonRequest), headerRewrite, bodyRewrite); 20 | ``` 21 | 22 | --- 23 | 24 | ### local.pipe(targetRes, sourceRes, headersCb, bodyCb) 25 | 26 | - `targetRes`: required local.Response, the stream to fill 27 | - `sourceRes`: required local.Response|promise(local.Response), the stream to draw from 28 | - `headersCb`: optional function(headers), its return value is used as the headers 29 | - `bodyCb`: optional function(bdy), its return value is used as the body (called on "data" events) -------------------------------------------------------------------------------- /docs/en/0.6.2/api/preferredtypes.md: -------------------------------------------------------------------------------- 1 | preferredTypes() 2 | ================ 3 | 4 | --- 5 | 6 | A utility function for content-negotiation, takes the accept header and a list of provided types, then provides a list of acceptable types ordered by the client's preference. Originally written by Federico Romero in the Negotiator library for node.js. 7 | 8 | ```javascript 9 | var type = local.preferredType(req, ['application/json', 'text/html']); 10 | if (type == 'application/json') { /* ... */ } 11 | else if (type == 'text/html') { /* ... */ } 12 | else { res.writeHead(406, 'not acceptable').end(); } 13 | ``` 14 | 15 | --- 16 | 17 | ### local.preferredTypes(accept, provided) 18 | 19 | - `accept`: string|local.Request, given accept header or request object 20 | - `provided`: optional Array(string), allowed media types 21 | - returns Array(string), the acceptable types in order of client preference 22 | 23 | --- 24 | 25 | ### local.preferredType(accept, provided) 26 | 27 | - `accept`: string|local.Request, given accept header or request object 28 | - `provided`: optional Array(string), allowed media types 29 | - returns string, the acceptable type most preferable to the client. -------------------------------------------------------------------------------- /docs/en/0.6.2/api/querylinks.md: -------------------------------------------------------------------------------- 1 | queryLinks() 2 | ============ 3 | 4 | --- 5 | 6 | Helper used by `local.Agent` to search links. Uses query rules explained in the Agent.follow() documentation. 7 | 8 | ```javascript 9 | local.queryLinks([{ href: 'httpl://foo', rel: 'service' }], { rel: 'service '}); 10 | // => { href: 'httpl://foo', rel: 'service' } 11 | ``` 12 | 13 | --- 14 | 15 | ### Query Rules 16 | 17 | Queries operate by the following rules: 18 | 19 | - MISS: if a query attribute is present on the link, but does not match 20 | - MISS: if a query attribute is not present on the link or in the link's href as a URI Template token 21 | - otherwise, MATCH 22 | 23 | The first match will be followed, and order is maintained from the response header. Additionally, the following rules are observed on the query values: 24 | 25 | - Query values preceded by an exclamation-point (!) will invert (logical NOT) 26 | - `rel`: can take multiple values, space-separated, which are ANDed logically 27 | - `rel`: will ignore the preceding scheme and trailing slash on URI values 28 | - `rel`: items preceded by an exclamation-point (!) will invert (logical NOT) 29 | 30 | Some examples: 31 | 32 | ```javascript 33 | console.log(links); 34 | /* => [ 35 | { href: 'httpl://foo', rel: 'up service' } 36 | { href: 'httpl://foo/messages{?isnew}', rel: 'self collection foobar.com/messages' } 37 | { href: 'httpl://foo/messages/{id}' rel: 'item foobar.com/message' } 38 | ] */ 39 | local.queryLinks(link, { rel: 'up' }); // => [{ href: 'httpl://foo', ...}] 40 | local.queryLinks(link, { rel: 'item foobar.com/message', id: 500 }); // => [{ href: 'httpl://foo/messages/{id}', ...}] 41 | local.queryLinks(link, { rel: 'self', isnew: 1 }); // => [{ href: 'httpl://foo/messages{?isnew}', ...}] 42 | 43 | local.queryLinks(link, { rel: '!up' }); // => [{ href: 'httpl://foo/messages{?isnew}', ...}, { href: 'httpl://foo/messages/{id}' ...}] 44 | local.queryLinks(link, { rel: 'item', id: 500 }); // => [{ href: 'httpl://foo/messages/{id}', ...}] 45 | local.queryLinks(link, { rel: 'foobar.com/message', id: 500 }); // => [{ href: 'httpl://foo/messages/{id}', ...}] 46 | local.queryLinks(link, { rel: 'item foobar.com/user' }); // => [] 47 | ``` 48 | 49 | Local.js does additional parsing on link headers to add the following queryable attributes: 50 | 51 | - `host_domain`: the domain of the href, equivalent to `local.parseUri(link.href).authority` 52 | - `host_user`: if a peer URI, the user id part of the URI 53 | - `host_relay`: if a peer URI, the relay domain part of the URI 54 | - `host_app`: if a peer URI, the app domain part of the URI 55 | - `host_sid`: if a peer URI, the stream id part of the URI 56 | 57 | Additionally, links hosted on Grimwire relays include the following queryable attribite: 58 | 59 | - `relay_user`: the id of the user who registered the link 60 | 61 | 62 | --- 63 | 64 | ### local.queryLinks(links, query) 65 | 66 | - `links`: required Array(object)|local.Response, the (parsed) array of links or a response with a link header 67 | - `query`: required object, the query to run against the links 68 | - returns Array(object), the list of matching links 69 | 70 | --- 71 | 72 | ### local.queryLink(link, query) 73 | 74 | - `link`: required objec, the (parsed) link 75 | - `query`: required object, the query to run against the links 76 | - returns bool, does the query match the link 77 | 78 | Used by `local.queryLinks()` -------------------------------------------------------------------------------- /docs/en/0.6.2/api/request.md: -------------------------------------------------------------------------------- 1 | Request 2 | ======= 3 | 4 | --- 5 | 6 | ```javascript 7 | var request = new local.Request({ 8 | method: 'POST', 9 | url: 'httpl://foo', 10 | headers: { 'content-type': 'application/json' } 11 | }); 12 | request.setHeader('accept', 'text/html'); 13 | var responsePromise = local.dispatch(request); 14 | request.write('["foo":"bar"]'); 15 | request.end(); 16 | ``` 17 | 18 | ## local.Request 19 | 20 | ### .setHeader(header, value) 21 | 22 | - `header`: required string, the header key/name 23 | - `value`: required string|object 24 | 25 | --- 26 | 27 | ### .getHeader(header) 28 | 29 | - `header`: required string, the header key/name 30 | - returns string|object 31 | 32 | --- 33 | 34 | ### .removeHeader(header) 35 | 36 | - `header`: required string, the header key/name 37 | 38 | --- 39 | 40 | ### .header(header, value) 41 | 42 | - `header`: required string, the header key/name 43 | - `value`: optional string|object 44 | - returns string|object or undefined 45 | 46 | If `value` is given, will update the value of `header` and returns undefined. Otherwise, returns the current value. 47 | 48 | --- 49 | 50 | ### .setTimeout(ms) 51 | 52 | - `ms`: required number, the timeout duration in milliseconds 53 | 54 | --- 55 | 56 | ### .write(data) 57 | 58 | - `data`: required string|object 59 | - returns `this` 60 | 61 | Writes data to the request. This will notify the server with the "data" event. 62 | 63 | If an object is given and a serialization function for the request's content-type is registered with `local.contentTypes`, Local.js will attempt to stringify the content before emitting "data". 64 | 65 | --- 66 | 67 | ### .end(data) 68 | 69 | - `data`: optional string|object 70 | - returns `this` 71 | 72 | Closes the request, calling `write()` if `data` is provided, then emitting the "end" event at the server. 73 | 74 | `local.Request` buffers all data written to the stream and save it to the `body` attribute. If a deserialization function for the request's content-type is registered with `local.contentTypes`, Local.js will attempt to parse the data before emitting "end". 75 | 76 | --- 77 | 78 | ### .close() 79 | 80 | - returns `this` 81 | 82 | Closes the stream, emitting the "close" event on the server. 83 | 84 | --- 85 | 86 | ### .method 87 | 88 | The string method, always uppercase 89 | 90 | --- 91 | 92 | ### .url 93 | 94 | The string target resource (clientside only) 95 | 96 | --- 97 | 98 | ### .path 99 | 100 | The string target resource (serverside only) 101 | 102 | --- 103 | 104 | ### .query 105 | 106 | The query flags of the url, extracted and parsed into an object 107 | 108 | --- 109 | 110 | ### .headers 111 | 112 | The headers as set by the client 113 | 114 | --- 115 | 116 | ### .parsedHeaders 117 | 118 | The headers parsed by `local.httpHeaders` 119 | 120 | --- 121 | 122 | ### .body 123 | 124 | The buffered request body 125 | 126 | --- 127 | 128 | ### .body_ 129 | 130 | A promise to the request body 131 | 132 | --- 133 | 134 | ### .stream 135 | 136 | A boolean, should `dispatch()` fulfill its returned promise on the "headers" event or on the "end" event? 137 | 138 | --- 139 | 140 | ### .timeout 141 | 142 | A number, the amount of time (in ms) to wait until aborting the request. 143 | 144 | --- 145 | 146 | ### .isConnOpen 147 | 148 | A boolean 149 | 150 | ## Events 151 | 152 | ### "headers" 153 | 154 | ``` 155 | function (response) { } 156 | ``` 157 | 158 | --- 159 | 160 | ### "data" 161 | 162 | ``` 163 | function (chunk) { } 164 | ``` 165 | 166 | --- 167 | 168 | ### "end" 169 | 170 | ``` 171 | function () { } 172 | ``` 173 | 174 | --- 175 | 176 | ### "close" 177 | 178 | ``` 179 | function () { } 180 | ``` -------------------------------------------------------------------------------- /docs/en/0.6.2/api/response.md: -------------------------------------------------------------------------------- 1 | Response 2 | ======== 3 | 4 | --- 5 | 6 | ```javascript 7 | response.setHeader('link', [ 8 | { rel:'self', href:'/' } 9 | ]); 10 | response.writeHead(200, 'ok', { 'content-type': 'text/html' }); 11 | response.end('

Hello, World!

'); 12 | ``` 13 | 14 | ## local.Response 15 | 16 | ### .writeHead(status, reason, headers) 17 | 18 | - `status`: required number, the numeric result code (Statuses Reference). 19 | - `reason`: optional string, a phrase explaining the result 20 | - `headers`: optional object, a dictionary of header values 21 | - returns `this` 22 | 23 | Initiates the response transmission. After calling this function, headers can not be altered. 24 | 25 | Header values may be objects if a serialization function is registered with `local.httpHeaders`. 26 | 27 | » httpHeaders 28 | 29 | --- 30 | 31 | ### .setHeader(header, value) 32 | 33 | - `header`: required string, the header key/name 34 | - `value`: required string|object 35 | 36 | --- 37 | 38 | ### .getHeader(header) 39 | 40 | - `header`: required string, the header key/name 41 | - returns string|object 42 | 43 | --- 44 | 45 | ### .removeHeader(header) 46 | 47 | - `header`: required string, the header key/name 48 | 49 | --- 50 | 51 | ### .header(header, value) 52 | 53 | - `header`: required string, the header key/name 54 | - `value`: optional string|object 55 | - returns string|object or undefined 56 | 57 | If `value` is given, will update the value of `header` and returns undefined. Otherwise, returns the current value. 58 | 59 | --- 60 | 61 | ### .write(data) 62 | 63 | - `data`: required string|object 64 | - returns `this` 65 | 66 | Writes data to the response. This will notify the client with the "data" event. 67 | 68 | If an object is given and a serialization function for the response's content-type is registered with `local.contentTypes`, Local.js will attempt to stringify the content before emitting "data". 69 | 70 | --- 71 | 72 | ### .end(data) 73 | 74 | - `data`: optional string|object 75 | - returns `this` 76 | 77 | Closes the response, calling `write()` if `data` is provided, then emitting the "end" event to the client. 78 | 79 | `local.Response` buffers all data written to the stream and save it to the `body` attribute. If a deserialization function for the response's content-type is registered with `local.contentTypes`, Local.js will attempt to parse the data before emitting "end". 80 | 81 | --- 82 | 83 | ### .close() 84 | 85 | - returns `this` 86 | 87 | Closes the stream, emitting the "close" event on the client. 88 | 89 | --- 90 | 91 | ### .status 92 | 93 | The numeric response code 94 | 95 | --- 96 | 97 | ### .reason 98 | 99 | The string response phrase 100 | 101 | --- 102 | 103 | ### .headers 104 | 105 | The headers as set by the server 106 | 107 | --- 108 | 109 | ### .parsedHeaders 110 | 111 | The headers parsed by `local.httpHeaders` 112 | 113 | --- 114 | 115 | ### .body 116 | 117 | The buffered response body 118 | 119 | --- 120 | 121 | ### .body_ 122 | 123 | A promise to the response body 124 | 125 | --- 126 | 127 | ### .isConnOpen 128 | 129 | A boolean 130 | 131 | --- 132 | 133 | ### .latency 134 | 135 | The round-trip time from dispatch to stream close in MS 136 | 137 | ## Events 138 | 139 | ### "headers" 140 | 141 | ``` 142 | function (response) { } 143 | ``` 144 | 145 | --- 146 | 147 | ### "data" 148 | 149 | ``` 150 | function (chunk) { } 151 | ``` 152 | 153 | --- 154 | 155 | ### "end" 156 | 157 | ``` 158 | function () { } 159 | ``` 160 | 161 | --- 162 | 163 | ### "close" 164 | 165 | ``` 166 | function () { } 167 | ``` -------------------------------------------------------------------------------- /docs/en/0.6.2/api/rtcbridgeserver.md: -------------------------------------------------------------------------------- 1 | RTCBridgeServer 2 | ================== 3 | 4 | --- 5 | 6 | Descends from `local.BridgeServer`. Handles the WebRTC peer connection process and establishes an SCTP DataChannel for exchanging requests with the peer. 7 | 8 | Typically created and managed by a `local.Relay` instance. 9 | 10 | ### local.RTCBridgeServer(config) 11 | 12 | - `config.peer`: required string, who we are connecting to (a valid peer domain) 13 | - `config.relay`: required local.Relay 14 | - `config.initiate`: optional bool, if true will initiate the connection processes 15 | - `config.loopback`: optional bool, is this the local host? If true, will connect to self 16 | - `config.retryTimeout`: optional number, time (in ms) before a connection is aborted and retried (defaults to 15000) 17 | - `config.retries`: optional number, number of times to retry before giving up (defaults to 3) 18 | 19 | ## local.RTCBridgeServer 20 | 21 | ### .getPeerInfo() 22 | 23 | - returns `{ domain:, user:, relay:, app:, sid: }` 24 | 25 | ```javascript 26 | peerServer.getPeerInfo(); 27 | /* => { 28 | domain: 'bob@grimwire.net!chat.grimwire.com!123', 29 | user: 'bob', 30 | relay: 'grimwire.net', 31 | app: 'chat.grimwire.com', 32 | sid: '123' 33 | } */ 34 | ``` 35 | 36 | --- 37 | 38 | ### .handleRemoteRequest(request, response) 39 | 40 | - `request`: required local.Request 41 | - `response`: required local.Response 42 | 43 | Handles requests from the peer to the page. If not overridden directly or with a server function provided by `config.relay`, will respond 500. 44 | 45 | --- 46 | 47 | ### .terminate() 48 | 49 | Closes the peer connection. -------------------------------------------------------------------------------- /docs/en/0.6.2/api/server.md: -------------------------------------------------------------------------------- 1 | Server 2 | ====== 3 | 4 | --- 5 | 6 | The base prototype for server objects. 7 | 8 | ```javascript 9 | function MyServer(config) { 10 | local.Server.call(this, config); 11 | } 12 | MyServer.prototype = Object.create(local.Server.prototype); 13 | 14 | MyServer.prototype.handleLocalRequest(req, res) { 15 | res.writeHead(204, 'ok, no content'); 16 | res.end(); 17 | } 18 | ``` 19 | 20 | ## local.Server 21 | 22 | ### .handleLocalRequest( request, response ) => undefined 23 | 24 | Request handler, should be overridden by subtypes. 25 | 26 | --- 27 | 28 | ### .terminate() 29 | 30 | Teardown handler, should be overridden by subtypes. 31 | 32 | --- 33 | 34 | ### .getDomain() 35 | 36 | - returns string, the registered hostname of the server instance 37 | 38 | --- 39 | 40 | ### .getUrl() 41 | 42 | - returns string, the full URL of the server instance 43 | 44 | --- 45 | 46 | ### .config 47 | 48 | An object, contains values in the constructor's config object. 49 | 50 | - `config.domain`: the hostname given to the server by `local.addServer()` -------------------------------------------------------------------------------- /docs/en/0.6.2/api/setdispatchwrapper.md: -------------------------------------------------------------------------------- 1 | setDispatchWrapper() 2 | ==================== 3 | 4 | --- 5 | 6 | An optional middleware that is injected between `dispatch()` and delivery of the request. 7 | 8 | ```javascript 9 | local.setDispatchWrapper(function(request, response, dispatch) { 10 | dispatch(request, response).always(console.log.bind(console, request)); 11 | }); 12 | ``` 13 | 14 | It can be used for setting global policies such as: 15 | 16 | - Logging 17 | - Caching strategies 18 | - Traffic rerouting 19 | - Permissioning 20 | - Header behaviors 21 | - Formatting and sanitizing 22 | 23 | Make sure that all requests eventually receive a response, even if the request is not passed on to the given dispatch function. 24 | 25 | ### local.setDispatchWrapper(wrapperFn) 26 | 27 | - `wrapperFn`: required function(request, response, dispatch) -------------------------------------------------------------------------------- /docs/en/0.6.2/api/subscribe.md: -------------------------------------------------------------------------------- 1 | subscribe() 2 | =========== 3 | 4 | --- 5 | 6 | Opens an event-stream at the target URL and provides an object for listening to specific events. Uses the Server Sent Events protocol. 7 | 8 | ```javascript 9 | var news = local.subscribe('http://myhost.com/news'); 10 | news.on('update', console.log.bind(console)); 11 | // { event: 'update', data: 'something has happened!' } 12 | ``` 13 | 14 | ### local.subscribe(request) 15 | 16 | - `request`: required string|object. If just a URL, will default the method to SUBSCRIBE and the 'accept' header to 'text/event-stream'. 17 | - returns `local.EventStream` 18 | 19 | Dispatches the request, then returns a `local.EventStream` which wraps around the response stream with an event-subscription interface. 20 | 21 | » EventStream 22 | 23 | --- 24 | 25 | ### Hosting Event Streams 26 | 27 | Use `local.EventHost` to track event streams and emit messages to them in bulk. 28 | 29 | ```javascript 30 | var newsEventHost = new local.EventHost(); 31 | function myhost(req, res) { 32 | if (req.path == '/news' && local.preferredType(req, 'text/event-stream')) { 33 | newsEventHost.addStream(res); 34 | res.writeHead(200, 'ok', { 'content-type': 'text/event-stream' }); 35 | // NOTE: do not call end (yet) 36 | } 37 | // ... 38 | newsEventHost.emit('update', 'something has happened!'); 39 | } 40 | ``` 41 | 42 | » EventHost -------------------------------------------------------------------------------- /docs/en/0.6.2/api/uri_helpers.md: -------------------------------------------------------------------------------- 1 | URI Helpers 2 | =========== 3 | 4 | ### local.parseUri(uri) 5 | 6 | - `uri`: required string|local.Request 7 | - returns object 8 | 9 | Written by Stephen Levithan. It breaks the input URL into its component parts: 10 | 11 | ```javascript 12 | local.parseUri('http://usr:pwd@www.test.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value#top') 13 | { 14 | anchor: "top" 15 | authority: "usr:pwd@www.test.com:81" 16 | directory: "/dir/dir.2/" 17 | file: "index.htm" 18 | host: "www.test.com" 19 | password: "pwd" 20 | path: "/dir/dir.2/index.htm" 21 | port: "81" 22 | protocol: "http" 23 | query: "q1=0&&test1&test2=value" 24 | queryKey: { 25 | q1: "0" 26 | test1: "" 27 | test2: "value" 28 | } 29 | relative: "/dir/dir.2/index.htm?q1=0&&test1&test2=value#top" 30 | source: "http://usr:pwd@www.test.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value#top" 31 | user: "usr" 32 | userInfo: "usr:pwd" 33 | } 34 | ``` 35 | 36 | Read the full reference at Steven Levithan's site. 37 | 38 | --- 39 | 40 | ### local.parseNavUri(uri) 41 | 42 | - `uri`: required string, a 'nav:' URI 43 | - returns Array(object) 44 | 45 | Converts a 'nav:' URI into an array of http/s/l URIs and link query objects. 46 | 47 | --- 48 | 49 | ### local.parsePeerDomain(domain) 50 | 51 | - `domain`: required string, the peer domain 52 | - returns object 53 | 54 | Breaks a peer domain (assigned by a Grimwire relay) into its constituent parts. Returns null if not a valid peer uri. 55 | 56 | ```javascript 57 | local.parsePeerDomain('bob@grimwire.net!chat.grimwire.com!123') 58 | { 59 | domain: 'bob@grimwire.net!chat.grimwire.com:123', 60 | user: 'bob', 61 | relay: 'grimwire.net', 62 | app: 'chat.grimwire.com', 63 | sid: '123' 64 | } 65 | ``` 66 | 67 | --- 68 | 69 | ### local.makePeerDomain(user, relay, app, sid) 70 | 71 | - `user`: required string 72 | - `relay`: required string 73 | - `app`: required string 74 | - `sid`: required number 75 | - returns string 76 | 77 | Constructs a peer domain from its constituent parts. 78 | 79 | ```javascript 80 | local.makePeerDomain('bob', 'grimwire.net', 'chat.grimwire.com', 123) 81 | // => bob@grimwire.net!chat.grimwire.com!123 82 | ``` 83 | 84 | --- 85 | 86 | ### local.makeProxyUri(uri, templates) 87 | 88 | - `uri`: required string 89 | - `templates`: required [string] 90 | - returns string 91 | 92 | Builds a proxy URI out of an array of templates. 93 | 94 | ```javascript 95 | local.makePemakeProxyUrierDomain('httpl://my_worker.js/', ['httpl://0.page/{uri}', 'httpl://foo/{?uri}']) 96 | // => httpl://0.page/httpl://foo/?uri=httpl://my_worker.js/ 97 | // ^ actual output will be percent-encoded 98 | ``` 99 | 100 | --- 101 | 102 | ### local.joinUri(uris...) 103 | 104 | - `uris`: a list of URI fragments 105 | 106 | Correctly joins together all url segments given in the arguments. 107 | 108 | ```javascript 109 | local.joinUrl('/foo/', '/bar', '/baz/') 110 | // => /foo/bar/baz/ 111 | ``` 112 | 113 | --- 114 | 115 | ### local.isAbsUri(uri) 116 | 117 | - `uri`: required string 118 | - returns bool 119 | 120 | Is the URL is absolute (in the HTTP/S/L schemes)? 121 | 122 | --- 123 | 124 | ### local.isNavSchemeUri(uri) 125 | 126 | - `uri`: required string 127 | - returns bool 128 | 129 | Is the URL is using the nav scheme? 130 | 131 | --- 132 | 133 | ### local.joinRelPath(url, relpath) 134 | 135 | - `url`: required string 136 | - `relpath`: required string 137 | - returns string 138 | 139 | Takes a base (absolute) URL and a relative path and forms a new valid URL. 140 | 141 | ```javascript 142 | local.joinRelPath('http://grimwire.com/foo/bar', '../fuz/bar') 143 | // => http://grimwire.com/foo/fuz/bar 144 | ``` -------------------------------------------------------------------------------- /docs/en/0.6.2/api/worker.md: -------------------------------------------------------------------------------- 1 | local.worker 2 | ============ 3 | 4 | --- 5 | 6 | Worker environments are provided with an extra `local.worker` object for handling Worker-specific tasks. It provides an EventEmitter interface, page management, logging, and configuration. 7 | 8 | ```javascript 9 | local.worker.setServer(function(req, res, page) { 10 | // ... 11 | }); 12 | local.worker.on('connect', function(page) { 13 | console.log('page '+id+' has connected'); 14 | }); 15 | ``` 16 | 17 | If the worker is shared, the originating page is considered the "Host" and given elevated privileges. 18 | 19 | `local.PageServer` instances are automatically created and assigned a URL when a page connects. The URL follows the scheme of `.page`. For instance, the hostpage can be reached at 'httpl://0.page'. 20 | 21 | --- 22 | 23 | ### console.log(args...) 24 | 25 | Approximates standard `console.log`. Also supports: 26 | 27 | - `dir()` 28 | - `debug()` 29 | - `warn()` 30 | - `error()` 31 | 32 | --- 33 | 34 | ### local.worker.setServer(server) 35 | 36 | - `server`: required function|local.Server 37 | 38 | Sets the server function for handling requests from the pages. The server is stored as `self.main`, which worker scripts may set directly. 39 | 40 | --- 41 | 42 | ### local.worker.config 43 | 44 | Populated with the config object from the host page's WorkerBridgeServer. 45 | 46 | **Note**, this value is populated with a message and will not be available during load. 47 | 48 | ### local.worker.pages 49 | 50 | An array of the connected `local.PageServer` instances. 51 | 52 | --- 53 | 54 | ### "connect" event 55 | 56 | ``` 57 | function (page) { } 58 | ``` 59 | 60 | Emitted on page-connect. Useful for SharedWorkers, which can accept multiple page connections. -------------------------------------------------------------------------------- /docs/en/0.6.2/api/workerbridgeserver.md: -------------------------------------------------------------------------------- 1 | WorkerBridgeServer 2 | ================== 3 | 4 | --- 5 | 6 | Descends from `local.BridgeServer`. Creates a Web Worker and provides a server interface for exchanging requests. Most applications will want to use `local.spawnWorkerServer` instead of instantiating this object directly. 7 | 8 | ```javascript 9 | local.spawnWorkerServer('/js/myworker.js'); // => WorkerBridgeServer instance 10 | ``` 11 | 12 | ### local.WorkerBridgeServer(config) 13 | 14 | - `config.src`: optional string, the URL to the worker source. Required unless `config.domain` is given 15 | - `config.domain`: optional string, overrides the automatic domain generation 16 | - If given in place of `config.src`, must include a source-path in order to fetch the worker 17 | - `config.temp`: boolean, should the workerserver be destroyed after it handles its requests? 18 | - `config.shared`: boolean, should the workerserver be shared? 19 | - `config.namespace`: optional string, what should the shared worker be named? 20 | - defaults to `config.src` if undefined 21 | - `serverFn`: optional function, a handler for requests from the worker 22 | 23 | If specified, `serverFn` will define the worker's `handleRemoteRequest()` behavior. 24 | 25 | ## local.WorkerBridgeServer 26 | 27 | ### .handleRemoteRequest(request, response) 28 | 29 | - `request`: required local.Request 30 | - `response`: required local.Response 31 | 32 | Handles requests from the Worker to the page. If not overridden directly or with the `serverFn` config, will respond 500. 33 | 34 | --- 35 | 36 | ### .terminate() 37 | 38 | Destroys the Worker. -------------------------------------------------------------------------------- /docs/en/0.6.2/example_mdviewer.md: -------------------------------------------------------------------------------- 1 | Example: Markdown Viewer 2 | ======================== 3 | 4 | --- 5 | 6 | This docs site is a Markdown viewer built with Marked, by Christopher Jeffrey, and Prism, by Lea Verou. 7 | 8 | The page consists of a static sidenav and a content area. It loads a markdown-to-html proxy into a Web Worker, then interprets hash-changes as load events for the given path. 9 | 10 | The full source is included below. 11 | 12 | ## assets/index.js 13 | 14 | ```javascript 15 | var viewer = document.getElementById('viewer'); 16 | var viewNav = document.getElementById('viewer-nav'); 17 | 18 | // Load the markdown conversion worker 19 | local.spawnWorkerServer('./assets/mdworker.js', { domain: 'mdworker.js' }); 20 | 21 | function getContent() { 22 | var path = window.location.hash.slice(1) || 'README.md'; 23 | var baseUrl = (location.origin||(location.protocol+'//'+location.host)); 24 | var url = local.joinRelPath(baseUrl, path); 25 | 26 | // Update nav higlight 27 | var active = viewNav.querySelector('.active'); 28 | if (active) active.classList.remove('active'); 29 | viewNav.querySelector('a[href="#'+path+'"]').parentNode.classList.add('active'); 30 | 31 | // Get markdown 32 | local.GET(url) 33 | .then(function(res) { 34 | // Convert to HTML 35 | return local.POST(res.body, 'httpl://mdworker.js'); 36 | }) 37 | .then(function (res) { 38 | // Render 39 | viewer.innerHTML = res.body; 40 | window.scrollTo(0,0); 41 | Prism.highlightAll(); 42 | }) 43 | .fail(function (res) { 44 | console.error('Failed loading '+path, res); 45 | viewer.innerHTML = '

'+res.status+' '+res.reason+'

'; 46 | }); 47 | } 48 | window.onhashchange = getContent; 49 | getContent(); 50 | ``` 51 | 52 | ## assets/mdworker.js 53 | 54 | ```javascript 55 | importScripts('../local.js'); 56 | importScripts('./marked.js'); 57 | marked.setOptions({ gfm: true, tables: true }); 58 | 59 | function main(req, res) { 60 | req.on('end', function() { 61 | res.writeHead(200, 'OK', {'Content-Type': 'text/html'}); 62 | res.end(marked(''+req.body)); 63 | }); 64 | } 65 | ``` -------------------------------------------------------------------------------- /docs/en/0.6.2/hosts.md: -------------------------------------------------------------------------------- 1 | httpl://hosts 2 | ============= 3 | 4 | --- 5 | 6 | Local.js includes a 'hosts' server which responses to HEAD and GET requests with links to the active local hosts. 7 | 8 | ```javascript 9 | local.addServer('foobar', function(req, res) { 10 | res.writeHead(204, 'ok', { 11 | link: [{ href: '/', rel: 'self service', id: 'helloworld' }], 12 | 'content-type': 'text/plain' 13 | }); 14 | res.end('Hello, World!'); 15 | }); 16 | local.agent('httpl://hosts') 17 | .follow({ rel: 'service', id: 'helloworld' }) 18 | .get(); 19 | ``` 20 | 21 | On every request, hosts will dispatch a HEAD to every local server and extract the links with a 'self' reltype. If a server doesn't provide any 'self' link, hosts will add a simple default. -------------------------------------------------------------------------------- /docs/en/0.6.2/httpl.md: -------------------------------------------------------------------------------- 1 | HTTPL: JSON encoded message streams with HTTP semantics 2 | ======================================================= 3 | 4 | --- 5 | 6 | HTTPL is used in Local.js to communicate over Browser messaging channels (postMessage, WebRTC DataChannels). The channels offer guarantees on order and delivery which are similar to that of TCP, while HTTPL provides stream multiplexing. For unordered channels, HTTPL supports head-of-line blocking. 7 | 8 | Unlike HTTP, HTTPL is full-duplex, meaning the request and response can stream simultaneously. 9 | 10 | JSON encoding was chosen over HTTP's format to take advantage of the browser's native JSON library. It's a larger message format (it includes more syntax and labeling) but it parses much faster than a JS routine for HTTP does. 11 | 12 | --- 13 | 14 | ### Message Types 15 | 16 | The format consists of 4 kinds of request messages: the header, the body chunk, the end, and the close. They are: 17 | 18 | ```javascript 19 | // header 20 | { 21 | sid: 1, // stream id 22 | mid: (isReorderingMessages) ? 1 : undefined, // message id 23 | method: 'POST', // request method 24 | path: '/foo?k=v', // target resource 25 | headers: { 26 | accept: 'text/html', 27 | 'content-type': 'application/json', 28 | host: 'myworker.js' 29 | } 30 | } 31 | ``` 32 | 33 | ```javascript 34 | // chunk 35 | { sid: 1, mid: (isReorderingMessages) ? ++midCounter : undefined, body: '{"foo":"bar"}' } 36 | ``` 37 | 38 | ```javascript 39 | // end 40 | { sid: 1, mid: (isReorderingMessages) ? ++midCounter : undefined, end: true } 41 | ``` 42 | 43 | ```javascript 44 | // close 45 | { sid: 1, mid: (isReorderingMessages) ? ++midCounter : undefined, close : true } 46 | ``` 47 | 48 | These messages can be combined together if the client chooses to buffer them, though at the time of this writing local.js hasn't implemented that process yet. 49 | 50 | ```javascript 51 | { 52 | sid: 1, 53 | method: 'POST', 54 | path: '/foo', 55 | headers: { 56 | accept: 'text/html', 57 | 'content-type': 'application/json', 58 | host: 'myworker.js' 59 | } 60 | body: '{"foo":"bar"}', 61 | end: true, 62 | close: true 63 | } 64 | ``` 65 | 66 | The `body` attribute on chunk messages are always strings in the native encoding of the given content-type. Multiple chunk messages can be issued to spread the content across the messages sequentially. 67 | 68 | The difference between end and close messages are subtle, but important. An end message signals the end of the request, but not the transaction. The requester is still listening for response data. The close message then signals that the client has ceased listening to the response. Typically, the server sends the close message, with an exception being server-sent event-streams, which clients often close. 69 | 70 | The response has the same four types of messages, but with slightly different semantics. Here are all four combined: 71 | 72 | ```javascript 73 | { 74 | sid: 1, // stream id 75 | mid: (isReorderingMessages) ? midCounter++ : undefined, // message id 76 | status: 200, // status code 77 | reason: 'Ok', // short explanation of the response 78 | headers: { 'content-type': 'text/html' }, 79 | body: '

success

', 80 | end: true, 81 | close: true 82 | } 83 | ``` -------------------------------------------------------------------------------- /docs/en/0.6.2/linkheader.md: -------------------------------------------------------------------------------- 1 | Hypermedia Indexing Protocol 2 | ============================ 3 | 4 | --- 5 | 6 | Local.js uses the [Web Linking](http://tools.ietf.org/html/rfc5988) standard to export metadata about services in the form of links. 7 | 8 | ```javascript 9 | local.dispatch({ method: 'HEAD', url: 'http://foobar.com' }) 10 | ``` 11 | ```text 12 | > HEAD http://foobar.com HTTP/1.1 13 | < HTTP/1.1 204 no content 14 | < Link: ; rel="self service", 15 | < ; rel="collection"; id="mailboxes", 16 | < ; rel="collection"; id="users" 17 | ``` 18 | ```javascript 19 | .then(function(res) { 20 | local.queryLinks(res, { rel: 'collection' }); 21 | // => [ 22 | // { href: "http://foobar.com/mail", rel: "collection", id: "mailboxes" }, 23 | // { href: "http://foobar.com/users", rel: "collection", id: "users" } 24 | // ] 25 | }); 26 | ``` 27 | 28 | --- 29 | 30 | ### URI Templates 31 | 32 | Servers can use [URI Templates](http://tools.ietf.org/html/rfc6570) in their exported links (with [fxa/uritemplate-js](https://github.com/fxa/uritemplate-js)). 33 | 34 | ```javascript 35 | local.dispatch({ method: 'HEAD', url: 'http://foobar.com/users' }) 36 | ``` 37 | ```text 38 | > HEAD http://foobar.com/users HTTP/1.1 39 | < HTTP/1.1 204 no content 40 | < Link: ; rel="up service via", 41 | < ; rel="self collection"; id="users", 42 | < ; rel="item"; id="admin", 43 | < ; rel="item" 44 | ``` 45 | ```javascript 46 | .then(function(res) { 47 | var selfLink = local.web.queryLinks(res, { rel: 'self collection' })[0]; 48 | local.UriTemplate.parse(selfLink.href).expand({ fname: 'Bob', lname: 'Robertson' }); 49 | // => http://foobar.com/users?fname=Bob&lname=Robertson 50 | 51 | var itemLinks = local.web.queryLinks(res, { rel: 'item' }); 52 | local.UriTemplate.parse(itemLinks[1].href).expand({ id: 'brobertson' }); 53 | // => http://foobar.com/users/brobertson 54 | }); 55 | ``` 56 | 57 | --- 58 | 59 | ### Index Navigation 60 | 61 | The [Agent](#docs/en/0.6.2/api/agent.md) follows queries by issuing HEAD requests, searching the links, and issuing subsequent HEAD requests based on matches. The navigations are followed "lazily," meaning that the HEAD requests are only sent when the application calls a dispatch function. 62 | 63 | > Read about link headers in [[The Hypermedia Indexing Protocol]] 64 | 65 | ```javascript 66 | local.agent('http://foobar.com') 67 | .follow({ rel: 'collection', id: 'users' }) 68 | .follow({ rel: 'item', id: 'brobertson' }) 69 | .dispatch({ method: 'GET', headers: { accept: 'application/json' }}) 70 | ``` 71 | ```text 72 | > HEAD http://foobar.com 73 | < HTTP/1.1 204 no content 74 | < Link: ; rel="self service", 75 | < ; rel="collection"; id="mailboxes", 76 | < ; rel="collection"; id="users" 77 | 78 | > HEAD http://foobar.com/users HTTP/1.1 79 | < HTTP/1.1 204 no content 80 | < Link: ; rel="up service via foobar.com/-service", 81 | < ; rel="item"; id="admin", 82 | < ; rel="item" 83 | 84 | > GET http://foobar.com/users/brobertson HTTP/1.1 85 | < HTTP/1.1 200 ok 86 | > Accept: application/json 87 | < ... 88 | ``` 89 | 90 | Notice that the item navigation did not find a full match. The navigator first looks for an exact match, then for a match with all attributes either matching or present in the URI template. In this case, `{ rel: 'item', id: 'brobertson' }` matched with `; rel="item"`. -------------------------------------------------------------------------------- /docs/en/0.6.2/navscheme.md: -------------------------------------------------------------------------------- 1 | nav:|| URIs 2 | =========== 3 | 4 | --- 5 | 6 | Agent navigations can be serialized into 'nav:' URIs, allowing you to embed them in GUIs and navigate links without creating an agent. The following two examples are equivalent: 7 | 8 | ```javascript 9 | local.agent('httpl://myhost') 10 | .follow({ rel: 'collection', id: 'users' }) 11 | .follow({ rel: 'item', id: 'bob' }) 12 | .get({ Accept: 'application/json' }); 13 | 14 | // has the same result as: 15 | local.dispatch({ 16 | method: 'GET', 17 | url: 'nav:||httpl://myhost|collection=users|item=bob', 18 | headers: { accept: 'application/json' } 19 | }); 20 | ``` 21 | 22 | ## Scheme Format 23 | 24 | Nav URIs are a serialization of `local.Agent` navigations. They are constructed as follows: 25 | 26 | nav:||httpl://host|reltype=id,attr1=value1,attr2=value2|reltype=id,attr1=value1,attr2=value2,... 27 | 28 | 1. Starting URL: required, an absolute location 29 | 2. Reltype: required, sets the `rel` attribute of the navigation query 30 | 3. ID: optional, sets the `id` attribute of the query 31 | 4. Attributes: optional, any number of additional query attributes 32 | 33 | Spaces are encoded with `+`, which can be used to specify multiple reltypes. 34 | 35 | Examples: 36 | 37 | ``` 38 | nav:||httpl://myhost|collection=users 39 | nav:||httpl://myhost|foobar.com/users,online=1 40 | nav:||httpl://myhost|collection+foobar.com/users|item=bob 41 | ``` 42 | 43 | ## Relative Nav URIs 44 | 45 | Agents can use relative nav URIs in its `follow()` calls. 46 | 47 | ```javascript 48 | local.agent('httpl://myhost') 49 | .follow('|collection=users|item=bob') 50 | .get({ Accept: 'application/json' }); 51 | ``` -------------------------------------------------------------------------------- /examples/charcount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * httpl://httplocal.com(/examples/charcount.js)/ 3 | */ 4 | importScripts('/local.min.js'); 5 | function main(req, res) { 6 | // Set headers 7 | res.header('Link', [{ href: '/', rel: 'self httplocal.com/transformer', id: 'charcount', title: 'Character Count (total stream)' }]); 8 | res.header('Content-Type', 'text/plain'); 9 | 10 | if (req.method == 'HEAD') { 11 | // Respond with headers only 12 | res.writeHead(204, 'OK, no content').end(); 13 | return; 14 | } 15 | 16 | if (req.method == 'POST') { 17 | // Apply transformation 18 | res.writeHead(200, 'OK'); // because HTTPL is full-duplex, we can respond while the request is streaming 19 | var count = 0; 20 | req.on('data', function(chunk) { count += chunk.length; }); 21 | req.on('end', function() { res.end(''+count); }); 22 | return; 23 | } 24 | 25 | // Invalid method 26 | res.writeHead(405, 'Bad Method').end(); 27 | } -------------------------------------------------------------------------------- /examples/slugify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * httpl://httplocal.com(/examples/slugify.js)/ 3 | */ 4 | importScripts('/local.min.js'); 5 | function main(req, res) { 6 | // Set headers 7 | res.header('Link', [{ href: '/', rel: 'self httplocal.com/transformer', id: 'slugify', title: 'Slugify' }]); 8 | res.header('Content-Type', 'text/plain'); 9 | 10 | if (req.method == 'HEAD') { 11 | // Respond with headers only 12 | res.writeHead(204, 'OK, no content').end(); 13 | return; 14 | } 15 | 16 | if (req.method == 'POST') { 17 | // Apply transformation 18 | res.writeHead(200, 'OK'); // because HTTPL is full-duplex, we can respond while the request is streaming 19 | req.on('data', function(chunk) { res.write(slugify(chunk)); }); 20 | req.on('end', function() { res.end(); }); 21 | return; 22 | } 23 | 24 | // Invalid method 25 | res.writeHead(405, 'Bad Method').end(); 26 | } 27 | 28 | function slugify(s) { 29 | s = s.replace(/<[^>]+>/gi, ''); 30 | s = s.replace(/&[^;\s]+;/gi, ''); 31 | s = s.replace(/[^-a-z0-9_ ]/gi, ''); 32 | s = s.replace(/\s+/gi, '-'); 33 | s = s.replace(/_+$/i, ''); 34 | s = s.replace(/_+/gi, '-'); 35 | return s; 36 | } -------------------------------------------------------------------------------- /examples/toupper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * httpl://httplocal.com(/examples/toupper.js)/ 3 | */ 4 | importScripts('/local.js'); 5 | function main(req, res) { 6 | // Set headers 7 | res.header('Link', [{ href: '/', rel: 'self httplocal.com/transformer', id: 'toupper', title: 'To Uppercase' }]); 8 | res.header('Content-Type', 'text/plain'); 9 | 10 | if (req.method == 'HEAD') { 11 | // Respond with headers only 12 | res.writeHead(204, 'OK, no content').end(); 13 | return; 14 | } 15 | 16 | if (req.method == 'POST') { 17 | // Apply transformation 18 | res.writeHead(200, 'OK'); // because HTTPL is full-duplex, we can respond while the request is streaming 19 | req.on('data', function(chunk) { res.write(chunk.toUpperCase()); }); 20 | req.on('end', function() { res.end(); }); 21 | return; 22 | } 23 | 24 | // Invalid method 25 | res.writeHead(405, 'Bad Method').end(); 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "local", 3 | "version": "0.6.2", 4 | "author": "Paul Frazee ", 5 | "description": "Ajax library that implements a client-side variation of HTTP", 6 | "main": "./local.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/grimwire/local.git" 10 | }, 11 | "keywords": [ 12 | "ajax", 13 | "http", 14 | "httpl" 15 | ], 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /scripts/minify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | output=$1; shift 4 | 5 | if ! command -v uglifyjs &> /dev/null ; then 6 | echo ========================================================== 7 | echo UglifyJS not found -- minified files are just concatenated 8 | echo ========================================================== 9 | cat > ${output} $@ 10 | exit 0 11 | fi 12 | 13 | uglifyjs $@ -o ${output} -c -m --screw-ie8 -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | // Worker API whitelisting code 2 | // ============================ 3 | var whitelist = [ // a list of global objects which are allowed in the worker 4 | 'null', 'self', 'console', 'atob', 'btoa', 5 | 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 6 | 'Proxy', 7 | 'importScripts', 'navigator', 8 | 'postMessage', 'addEventListener', 'removeEventListener', 9 | 'onmessage', 'onerror', 'onclose', 10 | 'dispatchEvent' 11 | ]; 12 | var blacklist = [ // a list of global objects which are not allowed in the worker, and which dont enumerate on `self` for some reason 13 | 'XMLHttpRequest', 'WebSocket', 'EventSource', 14 | 'Worker' 15 | ]; 16 | var whitelistAPIs_src = [ // nullifies all toplevel variables except those listed above in `whitelist` 17 | '(function() {', 18 | 'var nulleds=[];', 19 | 'var whitelist = ["'+whitelist.join('", "')+'"];', 20 | 'for (var k in self) {', 21 | 'if (whitelist.indexOf(k) === -1) {', 22 | 'Object.defineProperty(self, k, { value: null, configurable: false, writable: false });', 23 | 'nulleds.push(k);', 24 | '}', 25 | '}', 26 | 'var blacklist = ["'+blacklist.join('", "')+'"];', 27 | 'blacklist.forEach(function(k) {', 28 | 'Object.defineProperty(self, k, { value: null, configurable: false, writable: false });', 29 | 'nulleds.push(k);', 30 | '});', 31 | 'if (typeof console != "undefined") { console.log("Nullified: "+nulleds.join(", ")); }', 32 | '})();\n' 33 | ].join(''); 34 | var importScriptsPatch_src = [ // patches importScripts() to allow relative paths despite the use of blob uris 35 | '(function() {', 36 | 'var orgImportScripts = importScripts;', 37 | 'function joinRelPath(base, relpath) {', 38 | 'if (relpath.charAt(0) == \'/\') {', 39 | 'return "{{HOST}}" + relpath;', 40 | '}', 41 | '// totally relative, oh god', 42 | '// (thanks to geoff parker for this)', 43 | 'var hostpath = "{{HOST_DIR_PATH}}";', 44 | 'var hostpathParts = hostpath.split(\'/\');', 45 | 'var relpathParts = relpath.split(\'/\');', 46 | 'for (var i=0, ii=relpathParts.length; i < ii; i++) {', 47 | 'if (relpathParts[i] == \'.\')', 48 | 'continue; // noop', 49 | 'if (relpathParts[i] == \'..\')', 50 | 'hostpathParts.pop();', 51 | 'else', 52 | 'hostpathParts.push(relpathParts[i]);', 53 | '}', 54 | 'return "{{HOST}}/" + hostpathParts.join(\'/\');', 55 | '}', 56 | 'var isImportingAllowed = true;', 57 | 'setTimeout(function() { isImportingAllowed = false; },0);', // disable after initial run 58 | 'importScripts = function() {', 59 | 'if (!isImportingAllowed) { throw "Local.js - Imports disabled after initial load to prevent data-leaking"; }', 60 | 'return orgImportScripts.apply(null, Array.prototype.map.call(arguments, function(v, i) {', 61 | 'return (v.indexOf(\'/\') < v.indexOf(/[.:]/) || v.charAt(0) == \'/\' || v.charAt(0) == \'.\') ? joinRelPath(\'{{HOST_DIR_URL}}\',v) : v;', 62 | '}));', 63 | '};', 64 | '})();\n' 65 | ].join('\n'); 66 | 67 | module.exports = { 68 | logAllExceptions: false, 69 | workerBootstrapScript: whitelistAPIs_src+importScriptsPatch_src 70 | }; -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Local status codes 3 | // ================== 4 | // used to specify client operation states 5 | 6 | // link query failed to match 7 | LINK_NOT_FOUND: 1 8 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var util = require('./util'); 2 | 3 | module.exports = { 4 | Request: require('./web/request.js'), 5 | Response: require('./web/response.js'), 6 | Server: require('./web/server.js'), 7 | Relay: require('./web/relay.js'), 8 | BridgeServer: require('./web/bridge-server.js'), 9 | WorkerBridgeServer: require('./web/worker-bridge-server.js'), 10 | RTCBridgeServer: require('./web/rtc-bridge-server.js'), 11 | UriTemplate: require('./web/uri-template.js'), 12 | 13 | util: util, 14 | schemes: require('./web/schemes.js'), 15 | httpHeaders: require('./web/http-headers.js'), 16 | contentTypes: require('./web/content-types.js'), 17 | 18 | worker: require('./worker'), 19 | }; 20 | util.mixin.call(module.exports, require('./constants.js')); 21 | util.mixin.call(module.exports, require('./config.js')); 22 | util.mixin.call(module.exports, require('./promises.js')); 23 | util.mixin.call(module.exports, require('./spawners.js')); 24 | util.mixin.call(module.exports, require('./request-event.js')); 25 | util.mixin.call(module.exports, require('./web/helpers.js')); 26 | util.mixin.call(module.exports, require('./web/httpl.js')); 27 | util.mixin.call(module.exports, require('./web/dispatch.js')); 28 | util.mixin.call(module.exports, require('./web/subscribe.js')); 29 | util.mixin.call(module.exports, require('./web/agent.js')); 30 | 31 | if (typeof window != 'undefined') window.local = module.exports; 32 | else if (typeof self != 'undefined') self.local = module.exports; 33 | else local = module.exports; 34 | 35 | // Local Registry Host 36 | local.addServer('hosts', function(req, res) { 37 | var localHosts = local.getServers(); 38 | 39 | if (!(req.method == 'HEAD' || req.method == 'GET')) 40 | return res.writeHead(405, 'bad method').end(); 41 | 42 | if (req.method == 'GET' && !local.preferredType(req, 'application/json')) 43 | return res.writeHead(406, 'bad accept - only provides application/json').end(); 44 | 45 | var responses_ = []; 46 | var domains = [], links = []; 47 | links.push({ href: '/', rel: 'self service via', id: 'hosts', title: 'Page Hosts' }); 48 | for (var domain in localHosts) { 49 | if (domain == 'hosts') 50 | continue; 51 | domains.push(domain); 52 | responses_.push(local.dispatch({ method: 'HEAD', url: 'local://'+domain, timeout: 500 })); 53 | } 54 | 55 | local.promise.bundle(responses_).then(function(ress) { 56 | ress.forEach(function(res, i) { 57 | var selfLink = local.queryLinks(res, { rel: 'self' })[0]; 58 | if (!selfLink) { 59 | selfLink = { rel: 'service', id: domains[i], href: 'local://'+domains[i] }; 60 | } 61 | selfLink.rel = (selfLink.rel) ? selfLink.rel.replace(/(^|\b)(self|up|via)(\b|$)/gi, '') : 'service'; 62 | links.push(selfLink); 63 | }); 64 | 65 | res.setHeader('link', links); 66 | if (req.method == 'HEAD') 67 | return res.writeHead(204, 'ok, no content').end(); 68 | res.writeHead(200, 'ok', { 'content-type': 'application/json' }); 69 | res.end({ host_names: domains }); 70 | }); 71 | }); -------------------------------------------------------------------------------- /src/request-event.js: -------------------------------------------------------------------------------- 1 | // Standard DOM Events 2 | // =================== 3 | 4 | var util = require('./util'); 5 | 6 | // bindRequestEvents() 7 | // =================== 8 | // EXPORTED 9 | // Converts 'click' and 'submit' events into custom 'request' events 10 | // - within the container, all 'click' and 'submit' events will be consumed 11 | // - 'request' events will be dispatched by the original dispatching element 12 | // Parameters: 13 | // - `container` must be a valid DOM element 14 | // - `options` may disable event listeners by setting `links` or `forms` to false 15 | function bindRequestEvents(container, options) { 16 | container.__localEventHandlers = []; 17 | options = options || {}; 18 | 19 | var handler; 20 | if (options.links !== false) { 21 | // anchor-click handler 22 | handler = { name: 'click', handleEvent: Local__clickHandler, container: container }; 23 | container.addEventListener('click', handler, false); 24 | container.__localEventHandlers.push(handler); 25 | } 26 | if (options.forms !== false) { 27 | // submitter tracking 28 | handler = { name: 'click', handleEvent: Local__submitterTracker, container: container }; 29 | container.addEventListener('click', handler, true); // must be on capture to happen in time 30 | container.__localEventHandlers.push(handler); 31 | // submit handler 32 | handler = { name: 'submit', handleEvent: Local__submitHandler, container: container }; 33 | container.addEventListener('submit', handler, false); 34 | container.__localEventHandlers.push(handler); 35 | } 36 | } 37 | 38 | // unbindRequestEvents() 39 | // ===================== 40 | // EXPORTED 41 | // Stops listening to 'click' and 'submit' events 42 | function unbindRequestEvents(container) { 43 | if (container.__localEventHandlers) { 44 | container.__localEventHandlers.forEach(function(handler) { 45 | container.removeEventListener(handler.name, handler); 46 | }); 47 | delete container.__localEventHandlers; 48 | } 49 | } 50 | 51 | // INTERNAL 52 | // transforms click events into request events 53 | function Local__clickHandler(e) { 54 | if (e.button !== 0) { return; } // handle left-click only 55 | var request = util.extractRequest.fromAnchor(e.orgtarget || e.target); 56 | if (request && ['_top','_blank'].indexOf(request.target) !== -1) { return; } 57 | if (request) { 58 | e.preventDefault(); 59 | e.stopPropagation(); 60 | util.dispatchRequestEvent(e.target, request); 61 | return false; 62 | } 63 | } 64 | 65 | // INTERNAL 66 | // marks the submitting element (on click capture-phase) so the submit handler knows who triggered it 67 | function Local__submitterTracker(e) { 68 | if (e.button !== 0) { return; } // handle left-click only 69 | util.trackFormSubmitter(e.target); 70 | } 71 | 72 | // INTERNAL 73 | // transforms submit events into request events 74 | function Local__submitHandler(e) { 75 | var request = util.extractRequest(e.target, this.container); 76 | if (request && ['_top','_blank'].indexOf(request.target) !== -1) { return; } 77 | if (request) { 78 | e.preventDefault(); 79 | e.stopPropagation(); 80 | util.finishPayloadFileReads(request).then(function() { 81 | util.dispatchRequestEvent(e.target, request); 82 | }); 83 | return false; 84 | } 85 | } 86 | 87 | module.exports = { 88 | bindRequestEvents: bindRequestEvents, 89 | unbindRequestEvents: unbindRequestEvents 90 | }; -------------------------------------------------------------------------------- /src/spawners.js: -------------------------------------------------------------------------------- 1 | // Helpers to create servers 2 | // - 3 | 4 | var helpers = require('./web/helpers.js'); 5 | var httpl = require('./web/httpl.js'); 6 | var WorkerBridgeServer = require('./web/worker-bridge-server.js'); 7 | var Relay = require('./web/relay.js'); 8 | 9 | // EXPORTED 10 | // Creates a Web Worker and a bridge server to the worker 11 | // eg `local.spawnWorkerServer('http://foo.com/myworker.js', localServerFn) 12 | // - `src`: optional string, the URI to load into the worker. If null, must give `config.domain` with a source-path 13 | // - `config`: optional object, additional config options to pass to the worker 14 | // - `config.domain`: optional string, overrides the automatic domain generation 15 | // - `config.temp`: boolean, should the workerserver be destroyed after it handles it's requests? 16 | // - `config.shared`: boolean, should the workerserver be shared? 17 | // - `config.namespace`: optional string, what should the shared worker be named? 18 | // - defaults to `config.src` if undefined 19 | // - `serverFn`: optional function, a response generator for requests from the worker 20 | function spawnWorkerServer(src, config, serverFn) { 21 | if (typeof config == 'function') { serverFn = config; config = null; } 22 | if (!config) { config = {}; } 23 | config.src = src; 24 | config.serverFn = serverFn; 25 | 26 | // Create the domain 27 | var domain = config.domain; 28 | if (!domain) { 29 | if (local.isAbsUri(src)) { 30 | var urld = helpers.parseUri(src); 31 | domain = urld.authority + '(' + urld.path.slice(1) + ')'; 32 | } else { 33 | var src_parts = src.split(/[\?#]/); 34 | domain = window.location.host + '(' + src_parts[0].slice(1) + ')'; 35 | } 36 | } 37 | 38 | // Create the server 39 | if (httpl.getServer(domain)) throw "Worker already exists"; 40 | var server = new WorkerBridgeServer(config); 41 | httpl.addServer(domain, server); 42 | 43 | return server; 44 | } 45 | 46 | // EXPORTED 47 | // Opens a stream to a peer relay 48 | // - `providerUrl`: optional string, the relay provider 49 | // - `config.app`: optional string, the app to join as (defaults to window.location.host) 50 | // - `serverFn`: optional function, a response generator for requests from connected peers 51 | function joinRelay(providerUrl, config, serverFn) { 52 | if (typeof config == 'function') { serverFn = config; config = null; } 53 | if (!config) config = {}; 54 | config.provider = providerUrl; 55 | config.serverFn = serverFn; 56 | return new Relay(config); 57 | } 58 | module.exports = { 59 | spawnWorkerServer: spawnWorkerServer, 60 | joinRelay: joinRelay 61 | }; -------------------------------------------------------------------------------- /src/util/event-emitter.js: -------------------------------------------------------------------------------- 1 | // EventEmitter 2 | // ============ 3 | // EXPORTED 4 | // A minimal event emitter, based on the NodeJS api 5 | // initial code borrowed from https://github.com/tmpvar/node-eventemitter (thanks tmpvar) 6 | function EventEmitter() { 7 | Object.defineProperty(this, '_events', { 8 | value: {}, 9 | configurable: false, 10 | enumerable: false, 11 | writable: true 12 | }); 13 | Object.defineProperty(this, '_suspensions', { 14 | value: 0, 15 | configurable: false, 16 | enumerable: false, 17 | writable: true 18 | }); 19 | Object.defineProperty(this, '_history', { 20 | value: [], 21 | configurable: false, 22 | enumerable: false, 23 | writable: true 24 | }); 25 | } 26 | module.exports = EventEmitter; 27 | 28 | EventEmitter.prototype.suspendEvents = function() { 29 | this._suspensions++; 30 | }; 31 | 32 | EventEmitter.prototype.resumeEvents = function() { 33 | this._suspensions--; 34 | if (this._suspensions <= 0) 35 | this.playbackHistory(); 36 | }; 37 | 38 | EventEmitter.prototype.isSuspended = function() { return this._suspensions > 0; }; 39 | 40 | EventEmitter.prototype.playbackHistory = function() { 41 | var e; 42 | // always check if we're suspended - a handler might resuspend us 43 | while (!this.isSuspended() && (e = this._history.shift())) 44 | this.emit.apply(this, e); 45 | }; 46 | 47 | EventEmitter.prototype.emit = function(type) { 48 | var args = Array.prototype.slice.call(arguments); 49 | 50 | if (this.isSuspended()) { 51 | this._history.push(args); 52 | return; 53 | } 54 | 55 | var handlers = this._events[type]; 56 | if (!handlers) return false; 57 | 58 | args = args.slice(1); 59 | for (var i = 0, l = handlers.length; i < l; i++) 60 | handlers[i].apply(this, args); 61 | 62 | return true; 63 | }; 64 | 65 | EventEmitter.prototype.addListener = function(type, listener) { 66 | if (Array.isArray(type)) { 67 | type.forEach(function(t) { this.addListener(t, listener); }, this); 68 | return; 69 | } 70 | 71 | if ('function' !== typeof listener) { 72 | throw new Error('addListener only takes instances of Function'); 73 | } 74 | 75 | // To avoid recursion in the case that type == "newListeners"! Before 76 | // adding it to the listeners, first emit "newListeners". 77 | this.emit('newListener', type, listener); 78 | 79 | if (!this._events[type]) { 80 | this._events[type] = [listener]; 81 | } else { 82 | this._events[type].push(listener); 83 | } 84 | 85 | return this; 86 | }; 87 | 88 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 89 | 90 | EventEmitter.prototype.once = function(type, listener) { 91 | var self = this; 92 | self.on(type, function g() { 93 | self.removeListener(type, g); 94 | listener.apply(this, arguments); 95 | }); 96 | 97 | return this; 98 | }; 99 | 100 | EventEmitter.prototype.removeListener = function(type, listener) { 101 | if ('function' !== typeof listener) { 102 | throw new Error('removeListener only takes instances of Function'); 103 | } 104 | if (!this._events[type]) return this; 105 | 106 | var list = this._events[type]; 107 | var i = list.indexOf(listener); 108 | if (i < 0) return this; 109 | list.splice(i, 1); 110 | if (list.length === 0) { 111 | delete this._events[type]; 112 | } 113 | 114 | return this; 115 | }; 116 | 117 | EventEmitter.prototype.removeAllListeners = function(type) { 118 | if (type) this._events[type] = null; 119 | else this._events = {}; 120 | if (this._history[type]) this._history[type] = null; 121 | return this; 122 | }; 123 | 124 | EventEmitter.prototype.listeners = function(type) { 125 | return this._events[type]; 126 | }; -------------------------------------------------------------------------------- /src/util/index.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('./event-emitter.js'); 2 | var DOM = require('./dom.js'); 3 | 4 | // - must have a `this` bound 5 | // - eg `mixin.call(dest, src)` 6 | function mixin(obj) { 7 | for (var k in obj) 8 | this[k] = obj[k]; 9 | } 10 | 11 | // Adds event-emitter behaviors to the given object 12 | // - should be used on instantiated objects, not prototypes 13 | function mixinEventEmitter(obj) { 14 | EventEmitter.call(obj); 15 | mixin.call(obj, EventEmitter.prototype); 16 | } 17 | 18 | // http://jsperf.com/cloning-an-object/2 19 | function deepClone(obj) { 20 | return JSON.parse(JSON.stringify(obj)); 21 | } 22 | 23 | var nextTick; 24 | if (typeof window == 'undefined' || window.ActiveXObject || !window.postMessage) { 25 | // fallback for other environments / postMessage behaves badly on IE8 26 | nextTick = function(fn) { setTimeout(fn, 0); }; 27 | } else { 28 | var nextTickIndex = 0, nextTickFns = {}; 29 | nextTick = function(fn) { 30 | if (typeof fn != 'function') { throw "Invalid function provided to nextTick"; } 31 | window.postMessage('nextTick'+nextTickIndex, '*'); 32 | nextTickFns['nextTick'+nextTickIndex] = fn; 33 | nextTickIndex++; 34 | }; 35 | window.addEventListener('message', function(evt){ 36 | var fn = nextTickFns[evt.data]; 37 | if (fn) { 38 | delete nextTickFns[evt.data]; 39 | fn(); 40 | } 41 | }, true); 42 | 43 | // The following is the original version by // https://github.com/timoxley/next-tick 44 | // It was replaced by the above to avoid the try/catch block 45 | /* 46 | var nextTickQueue = []; 47 | nextTick = function(fn) { 48 | if (!nextTickQueue.length) window.postMessage('nextTick', '*'); 49 | nextTickQueue.push(fn); 50 | }; 51 | window.addEventListener('message', function(evt){ 52 | if (evt.data != 'nextTick') { return; } 53 | var i = 0; 54 | while (i < nextTickQueue.length) { 55 | try { nextTickQueue[i++](); } 56 | catch (e) { 57 | nextTickQueue = nextTickQueue.slice(i); 58 | window.postMessage('nextTick', '*'); 59 | throw e; 60 | } 61 | } 62 | nextTickQueue.length = 0; 63 | }, true); 64 | */ 65 | } 66 | 67 | module.exports = { 68 | EventEmitter: EventEmitter, 69 | 70 | mixin: mixin, 71 | mixinEventEmitter: mixinEventEmitter, 72 | deepClone: deepClone, 73 | nextTick: nextTick 74 | }; 75 | mixin.call(module.exports, DOM); -------------------------------------------------------------------------------- /src/web/server.js: -------------------------------------------------------------------------------- 1 | // Server 2 | // ====== 3 | // EXPORTED 4 | // core type for all servers 5 | // - should be used as a prototype 6 | function Server(config) { 7 | this.config = { domain: null, log: false }; 8 | if (config) { 9 | for (var k in config) 10 | this.config[k] = config[k]; 11 | } 12 | } 13 | module.exports = Server; 14 | 15 | Server.prototype.getDomain = function() { return this.config.domain; }; 16 | Server.prototype.getUrl = function() { return 'local://' + this.config.domain; }; 17 | 18 | Server.prototype.debugLog = function() { 19 | if (!this.config.log) return; 20 | var args = [this.config.domain].concat([].slice.call(arguments)); 21 | console.debug.apply(console, args); 22 | }; 23 | 24 | // Local request handler 25 | // - should be overridden 26 | Server.prototype.handleLocalRequest = function(request, response) { 27 | console.warn('handleLocalRequest not defined', this); 28 | response.writeHead(501, 'server not implemented'); 29 | response.end(); 30 | }; 31 | 32 | // Called before server destruction 33 | // - may be overridden 34 | // - executes syncronously; does not wait for cleanup to finish 35 | Server.prototype.terminate = function() { 36 | }; 37 | -------------------------------------------------------------------------------- /src/worker/config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /src/worker/page-bridge-server.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var workerConfig = require('./config.js'); 3 | var BridgeServer = require('../web/bridge-server.js'); 4 | 5 | // PageServer 6 | // ========== 7 | // EXPORTED 8 | // wraps the comm interface to a page for messaging 9 | // - `id`: required number, should be the index of the connection in the list 10 | // - `port`: required object, either `self` (for non-shared workers) or a port from `onconnect` 11 | // - `isHost`: boolean, should connection get host privileges? 12 | function PageServer(id, port, isHost) { 13 | BridgeServer.call(this); 14 | this.id = id; 15 | this.port = port; 16 | this.isHostPage = isHost; 17 | 18 | // Setup the incoming message handler 19 | this.port.addEventListener('message', (function(event) { 20 | var message = event.data; 21 | if (!message) 22 | return console.error('Invalid message from page: Payload missing', event); 23 | 24 | // Handle messages with an `op` field as worker-control packets rather than HTTPL messages 25 | switch (message.op) { 26 | case 'configure': 27 | this.onPageConfigure(message.body); 28 | break; 29 | case 'terminate': 30 | this.terminate(); 31 | break; 32 | default: 33 | // If no recognized 'op' field is given, treat it as an HTTPL request and pass onto our BridgeServer parent method 34 | this.onChannelMessage(message); 35 | break; 36 | } 37 | }).bind(this)); 38 | } 39 | PageServer.prototype = Object.create(BridgeServer.prototype); 40 | module.exports = PageServer; 41 | 42 | // Returns true if the channel is ready for activity 43 | // - returns boolean 44 | PageServer.prototype.isChannelActive = function() { 45 | return true; 46 | }; 47 | 48 | // Sends a single message across the channel 49 | // - `msg`: required string 50 | PageServer.prototype.channelSendMsg = function(msg) { 51 | this.port.postMessage(msg); 52 | }; 53 | 54 | // Remote request handler 55 | PageServer.prototype.handleRemoteRequest = function(request, response) { 56 | var server = self.main; 57 | if (server && typeof server == 'function') { 58 | server.call(this, request, response, this); 59 | } else if (server && server.handleRemoteRequest) { 60 | server.handleRemoteRequest(request, response, this); 61 | } else { 62 | response.writeHead(501, 'not implemented'); 63 | response.end(); 64 | } 65 | }; 66 | 67 | // Stores configuration sent by the page 68 | PageServer.prototype.onPageConfigure = function(message) { 69 | if (!this.isHostPage) { 70 | console.log('rejected "configure" from non-host connection'); 71 | return; 72 | } 73 | var serverFn = workerConfig.serverFn; 74 | util.mixin.call(workerConfig, message); 75 | workerConfig.serverFn = serverFn; 76 | }; -------------------------------------------------------------------------------- /test/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Local Client Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Local Client Test Suite

16 |

brought to you by doctest.js

17 | 18 |
19 | anchor tag 1 20 | anchor tag 2 21 | anchor tag 3 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 42 | 47 | 48 | 49 |
50 | 51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 |
original
59 |
original
60 |
original
61 |
original
62 |
original
63 |
original
64 |
original
65 |
original
66 |
67 |
68 |
69 |
70 | 71 | 72 |

73 | 
74 |   
75 | 
76 | 


--------------------------------------------------------------------------------
/test/doctest/.gitignore:
--------------------------------------------------------------------------------
1 | .resources/_build
2 | 


--------------------------------------------------------------------------------
/test/doctest/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule ".resources/esprima"]
2 | 	path = .resources/esprima
3 | 	url = https://github.com/ariya/esprima.git
4 | 


--------------------------------------------------------------------------------
/test/doctest/.hgignore:
--------------------------------------------------------------------------------
1 | syntax:glob
2 | .svn
3 | .hgsvn
4 | 


--------------------------------------------------------------------------------
/test/doctest/.resources/CNAME:
--------------------------------------------------------------------------------
1 | doctestjs.org
2 | 


--------------------------------------------------------------------------------
/test/doctest/.resources/boilerplate/css/normalize.min.css:
--------------------------------------------------------------------------------
 1 | /*! normalize.css v1.0.1 | MIT License | git.io/normalize */
 2 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block}
 3 | audio,canvas,video{display:inline-block;*display:inline;*zoom:1}
 4 | audio:not([controls]){display:none;height:0}
 5 | [hidden]{display:none}
 6 | html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}
 7 | html,button,input,select,textarea{font-family:sans-serif}
 8 | body{margin:0}
 9 | a:focus{outline:thin dotted}
10 | a:active,a:hover{outline:0}
11 | h1{font-size:2em;margin:.67em 0}
12 | h2{font-size:1.5em;margin:.83em 0}
13 | h3{font-size:1.17em;margin:1em 0}
14 | h4{font-size:1em;margin:1.33em 0}
15 | h5{font-size:.83em;margin:1.67em 0}
16 | h6{font-size:.75em;margin:2.33em 0}
17 | abbr[title]{border-bottom:1px dotted}
18 | b,strong{font-weight:bold}
19 | blockquote{margin:1em 40px}
20 | dfn{font-style:italic}
21 | mark{background:#ff0;color:#000}
22 | p,pre{margin:1em 0}
23 | code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}
24 | pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}
25 | q{quotes:none}
26 | q:before,q:after{content:'';content:none}
27 | small{font-size:80%}
28 | sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
29 | sup{top:-0.5em}
30 | sub{bottom:-0.25em}
31 | dl,menu,ol,ul{margin:1em 0}
32 | dd{margin:0 0 0 40px}
33 | menu,ol,ul{padding:0 0 0 40px}
34 | nav ul,nav ol{list-style:none;list-style-image:none}
35 | img{border:0;-ms-interpolation-mode:bicubic}
36 | svg:not(:root){overflow:hidden}
37 | figure{margin:0}
38 | form{margin:0}
39 | fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
40 | legend{border:0;padding:0;white-space:normal;*margin-left:-7px}
41 | button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}
42 | button,input{line-height:normal}
43 | button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}
44 | button[disabled],input[disabled]{cursor:default}
45 | input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}
46 | input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}
47 | input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}
48 | button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
49 | textarea{overflow:auto;vertical-align:top}
50 | table{border-collapse:collapse;border-spacing:0}


--------------------------------------------------------------------------------
/test/doctest/.resources/boilerplate/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/test/doctest/.resources/boilerplate/favicon.ico


--------------------------------------------------------------------------------
/test/doctest/.resources/boilerplate/js/main.js:
--------------------------------------------------------------------------------
1 | 
2 | 


--------------------------------------------------------------------------------
/test/doctest/.resources/build:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | set -ex
 4 | 
 5 | mv .resources/CNAME CNAME
 6 | mv .resources resources
 7 | 
 8 | echo "Updating files with template..."
 9 | 
10 | files="index.html tutorial.html reference.html try.html"
11 | 
12 | python resources/retemplate.py $files
13 | for file in $files ; do
14 |     sed -i '' 's/\.resources/resources/g' $file
15 | done
16 | 


--------------------------------------------------------------------------------
/test/doctest/.resources/doc.css:
--------------------------------------------------------------------------------
 1 | i, em {
 2 |     font-family: serif;
 3 | }
 4 | 
 5 | #contents ul {
 6 |     margin-bottom: 0;
 7 |     margin-top: 0;
 8 | }
 9 | 
10 | #contents > ul {
11 |     padding-left: 0;
12 | }
13 | 
14 | #contents li {
15 |     list-style: none;
16 | }
17 | 
18 | #contents {
19 |     border-bottom: 1px solid #999;
20 |     padding-left: 1em;
21 | }
22 | 
23 | .header-container header {
24 |     width: 98%;
25 | }
26 | 
27 | code {
28 |     color: #288;
29 | }
30 | 
31 | aside code {
32 |     color: #9ff;
33 | }
34 | 
35 | section:target h1, section:target h2, section:target h3, section:target h4,
36 | section:target h5, section:target h6, h3:target, h4:target {
37 |     border-bottom: 3px solid #f90;
38 | }
39 | 
40 | footer a:link, footer a:visited,
41 | aside a:link, aside a:visited {
42 |     text-decoration: none;
43 |     color: #fd1;
44 | }
45 | 
46 | section header h1, section header h2, section header h3, section header h4,
47 | section header h5, section header h6 {
48 |     margin-left: -1em;
49 |     margin-top: 1.75em;
50 |     border-bottom: 1px solid #000;
51 | }
52 | 
53 | pre {
54 |     font-size: 90%;
55 |     line-height: 1.3;
56 |     border: 1px solid #999;
57 |     border-radius: 4px;
58 | }
59 | 
60 | @media only screen and (min-width: 768px) {
61 | 
62 |     .main aside {
63 |         position: absolute;
64 |         right: 1em;
65 |     }
66 | 
67 | }
68 | 
69 | .header-container header {
70 |     margin-left: 1em;
71 | }
72 | 
73 | h1.title a:link, h1.title a:visited {
74 |     text-decoration: none;
75 |     color: #fff;
76 | }
77 | 
78 | h1.title a:hover {
79 |     text-decoration: underline;
80 | }
81 | 


--------------------------------------------------------------------------------
/test/doctest/.resources/example.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 	Example Feed
 5 | 	A subtitle.
 6 | 	
 7 | 	
 8 | 	urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6
 9 | 	2003-12-13T18:30:02Z
10 | 	
11 | 		John Doe
12 | 		johndoe@example.com
13 | 	
14 | 
15 | 	
16 | 		Atom-Powered Robots Run Amok
17 | 		
18 | 		
19 | 		
20 | 		urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a
21 | 		2003-12-13T18:30:02Z
22 | 		Some text.
23 | 	
24 | 
25 | 
26 | 


--------------------------------------------------------------------------------
/test/doctest/.resources/footer.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 

Download

4 |

5 | You can download this project in either 6 | zip or 7 | tar formats. 8 |

9 | 10 |

You can also clone the project with Git 11 | by running: 12 |

$ git clone git://github.com/ianb/doctestjs
13 |

14 | 15 | 19 | 20 | 21 | 22 | 26 | 31 | -------------------------------------------------------------------------------- /test/doctest/.resources/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | __TITLE__ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Fork me on GitHub 22 | 23 |
24 |
25 |

Doctest.js:

26 | 27 | 36 | 37 |
38 |
39 | 40 |
41 |
42 | 43 | 44 | 45 | 46 | __BODY__ 47 | 48 | 49 | 50 |
51 | 52 |
53 | 54 | 60 | 61 | 62 | 65 | 66 | 67 | 68 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /test/doctest/.resources/include-scripts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | base="$(python -c 'import sys, os; print os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[1])))' "$BASH_SOURCE")" 6 | 7 | if [ ! -f $base/doctest.js ] ; then 8 | echo "Could not find $base/doctest.js" 9 | exit 1 10 | fi 11 | 12 | if [ ! -d $base/.resources/jshint ] ; then 13 | echo "Could not find $base/.resources/jshint" 14 | echo "Try:" 15 | echo " git clone https://github.com/jshint/jshint.git .resources/jshint" 16 | exit 2 17 | fi 18 | 19 | if [ ! -d $base/esprima ] ; then 20 | echo "Could not find $base/.resources/esprima" 21 | echo "Try:" 22 | echo " git clone https://github.com/ariya/esprima.git .resources/esprima" 23 | exit 3 24 | fi 25 | 26 | echo "Substituting $base/doctest.js" 27 | 28 | uglify_options="--no-copyright --max-line-len 200" 29 | 30 | python -c ' 31 | import os, sys, re 32 | os.chdir(sys.argv[1]) 33 | with open("doctest.js", "rb") as fp: 34 | content = fp.read() 35 | names = {} 36 | for arg in sys.argv[2:]: 37 | name, rest = arg.split("=", 1) 38 | names[name] = rest 39 | regex = re.compile(r"\/\*\s+INSERT\s+(.*?)\s+\*\/\s*\n(.*?)\/*\s+END\s+INSERT\s+\*\/", re.S) 40 | def repl(match): 41 | print "Replacing %s" % match.group(1) 42 | return "/* INSERT %s */\n%s\n/* END INSERT */" % ( 43 | match.group(1), names[match.group(1)]) 44 | new_content = regex.sub(repl, content) 45 | with open("doctest.js", "wb") as fp: 46 | fp.write(new_content) 47 | print "wrote doctest.js" 48 | ' "$base" esprima.js="$(uglifyjs $uglify_options < esprima/esprima.js)" jshint.js="$(uglifyjs $uglify_options < jshint/jshint.js)" 49 | -------------------------------------------------------------------------------- /test/doctest/.resources/retemplate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | 4 | section_re = re.compile(r''' 5 | (?P) 8 | (?P[^\000]*) 9 | (?P) 12 | ''', re.VERBOSE | re.MULTILINE) 13 | 14 | value_sub_re = re.compile(r'(?P<[^>]*")(?:__)(?P[A-Z_]+)(?:__)(?P"[^>]*>)') 15 | 16 | title_sub_re = re.compile(r'(.*?)', re.I) 17 | 18 | 19 | def get_variables(content, value_matches): 20 | vars = {} 21 | for match in section_re.finditer(content): 22 | vars[match.group('name')] = match.group('value') 23 | for name, value_match_start, value_match_end in value_matches: 24 | regex = re.escape(value_match_start) + '([^"]*)' + re.escape(value_match_end) 25 | regex = re.compile(regex) 26 | for match in regex.finditer(content): 27 | vars[name] = match.group(1) 28 | match = title_sub_re.search(content) 29 | if match: 30 | vars['PAGE_TITLE'] = match.group(1) 31 | else: 32 | print 'No found' 33 | return vars 34 | 35 | 36 | def get_value_matches(template): 37 | matches = [] 38 | for match in value_sub_re.finditer(template): 39 | matches.append(( 40 | match.group('name'), 41 | match.group('front'), 42 | match.group('back'))) 43 | return matches 44 | 45 | 46 | def sub_template(template, content): 47 | matches = get_value_matches(template) 48 | content_vars = get_variables(content, matches) 49 | 50 | def sub_section(match): 51 | if match.group('name') not in content_vars: 52 | # Failure, needs to be fixed 53 | raise Exception('Must have section <!-- %s -->' % match.group('name')) 54 | return ( 55 | match.group('front1') + match.group('name') + match.group('front2') 56 | + content_vars.get(match.group('name'), '') 57 | + match.group('back1') + '/' + match.group('name') + match.group('back2') 58 | ) 59 | 60 | new_content = section_re.sub(sub_section, template) 61 | 62 | def sub_variable(match): 63 | if match.group('name') not in content_vars: 64 | print 'Missing tag: __%s__' % match.group('name') 65 | return '<!-- ' + match.group(0) + ' -->' 66 | return ( 67 | match.group('front') 68 | + content_vars[match.group('name')] 69 | + match.group('back')) 70 | 71 | new_content = value_sub_re.sub(sub_variable, new_content) 72 | 73 | def sub_title(match): 74 | if 'PAGE_TITLE' in content_vars: 75 | return '<title>' + content_vars['PAGE_TITLE'] + '' 76 | else: 77 | return match.group(0) 78 | 79 | new_content = title_sub_re.sub(sub_title, new_content) 80 | 81 | return new_content 82 | 83 | 84 | def rewrite_page(page_name, template_name): 85 | with open(template_name) as fp: 86 | template = fp.read() 87 | with open(page_name) as fp: 88 | content = fp.read() 89 | try: 90 | new_content = sub_template(template, content) 91 | except: 92 | print 'Error in page:', page_name 93 | raise 94 | with open(page_name, 'w') as fp: 95 | fp.write(new_content) 96 | 97 | if __name__ == '__main__': 98 | import sys 99 | if len(sys.argv) < 3: 100 | print 'Usage: retemplate.py TEMPLATE_FILE CONTENT_FILE [...CONTENT_FILE2...]' 101 | sys.exit(2) 102 | template_name = sys.argv[1] 103 | for filename in sys.argv[2:]: 104 | rewrite_page(filename, template_name) 105 | -------------------------------------------------------------------------------- /test/doctest/.resources/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |

Doctest.js:

23 | 24 | 31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | __BODY__ 42 | 43 | 44 | 45 |
46 | 47 |
48 | 49 | 55 | 56 | 57 | 60 | 61 | 62 | 63 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /test/doctest/.resources/toc.js: -------------------------------------------------------------------------------- 1 | function contentsOnLoad() { 2 | if (contentsOnLoad.hasRun) { 3 | return; 4 | } 5 | contentsOnLoad.hasRun = true; 6 | var dest = document.getElementById('contents'); 7 | var toc = [document.createElement('ul')]; 8 | var generatedIds = []; 9 | dest.appendChild(toc[0]); 10 | var els = document.querySelectorAll('h3, h4, h5, h6'); 11 | for (var i=0; i= toc.length) { 21 | var ul = document.createElement('ul'); 22 | var container = document.createElement('li'); 23 | container.appendChild(ul); 24 | toc[toc.length-1].appendChild(container); 25 | toc.push(ul); 26 | } 27 | var name = el.getAttribute('id'); 28 | if (! name) { 29 | name = 'header-'+(i+1); 30 | generatedIds.push(name); 31 | el.setAttribute('id', name); 32 | } 33 | var li = document.createElement('li'); 34 | var anchor = document.createElement('a'); 35 | if (el.getAttribute('href')) { 36 | anchor.setAttribute('href', el.getAttribute('href')); 37 | el.style.display = 'none'; 38 | } else { 39 | anchor.setAttribute('href', '#'+name); 40 | } 41 | li.appendChild(anchor); 42 | anchor.innerHTML = el.innerHTML; 43 | toc[toc.length-1].appendChild(li); 44 | } 45 | // Re-scroll: 46 | if (location.hash && generatedIds.indexOf(location.hash.substr(1)) != -1) { 47 | location.hash = location.hash; 48 | } 49 | } 50 | 51 | document.addEventListener("DOMContentLoaded", contentsOnLoad, false); 52 | window.addEventListener("load", contentsOnLoad, false); 53 | -------------------------------------------------------------------------------- /test/doctest/.resources/try.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function () { 2 | var innerHTML = $('#display').html(); 3 | if (localStorage.editText) { 4 | $('#editor').val(localStorage.editText); 5 | } 6 | $('#editor').change(function () { 7 | localStorage.editText = $('#editor').val(); 8 | }); 9 | $('#testit').click(function () { 10 | $('#display').html(innerHTML); 11 | $('#editit').click(function () { 12 | $('#edit').show(); 13 | $('#display').hide(); 14 | $('#editor').focus(); 15 | $('#doctest-output').hide(); 16 | }); 17 | $('#test-location').addClass('commenttest').text($('#editor').val()); 18 | console.log($('#test-location').text()); 19 | var runner = new doctest.Runner(); 20 | var parser = new doctest.HTMLParser(runner, $('#display')[0], 'pre#test-location'); 21 | runner.init(); 22 | parser.parse(); 23 | runner.run(); 24 | }); 25 | }, false); 26 | -------------------------------------------------------------------------------- /test/doctest/.syncignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/doctest/README.md: -------------------------------------------------------------------------------- 1 | ## doctest.js 2 | 3 | For a more complete description please [read the main 4 | page](http://doctestjs.org). 5 | 6 | `doctest.js` is a test runner for Javascript, organized around *examples* and *expected result*. Tests look like this: 7 | 8 | ```javascript 9 | // Simple stuff: 10 | print(3 * 4); 11 | // => 12 12 | 13 | // Or complicated stuff: 14 | var complete = false; 15 | var savedResult = null; 16 | $.ajax({ 17 | url: "/test", 18 | dataType: "json", 19 | success: function (result) { 20 | complete = true; 21 | savedResult = result; 22 | } 23 | }); 24 | wait(function () {return complete;}); 25 | print(savedResult); 26 | // => {value1: "something", value2: true} 27 | ``` 28 | 29 | And a bunch more features: check out the [tutorial](http://doctestjs.org/tutorial.html) to get started, or read the [reference](http://doctestjs.org/reference.html) for more detail. 30 | 31 | ## License 32 | 33 | Doctest.js is released under an MIT-style license. 34 | 35 | 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: 36 | 37 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 38 | 39 | 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. 40 | -------------------------------------------------------------------------------- /test/doctest/doctest.css: -------------------------------------------------------------------------------- 1 | /* Basic styling: */ 2 | 3 | body { 4 | font-family: sans-serif; 5 | } 6 | 7 | pre { 8 | padding: 0.3em; 9 | } 10 | 11 | /* Test block formatting: */ 12 | 13 | pre.doctest, pre.commenttest { 14 | border: 1px solid #999; 15 | border-radius: 4px; 16 | } 17 | 18 | pre.doctest.doctest-some-failure, pre.commenttest.doctest-some-failure { 19 | border: 1px solid #f00; 20 | } 21 | 22 | /* FIXME: it would be nice if this was more obviously styled to show that there 23 | was more content */ 24 | pre.commenttest.expand-on-failure, pre.doctest.expand-on-failure { 25 | max-height: 3em; 26 | overflow-y: auto; 27 | } 28 | 29 | pre.commenttest.expand-on-failure.doctest-some-failure, pre.doctest.expand-on-failure.doctest-some-failure { 30 | max-height: none; 31 | } 32 | 33 | /* Individual example formatting: */ 34 | 35 | .doctest-example:target { 36 | border-left: 4px solid #f00; 37 | padding-left: 4px; 38 | } 39 | 40 | .doctest-example { 41 | } 42 | 43 | .doctest-example.doctest-success { 44 | color: #060; 45 | } 46 | 47 | .doctest-example.doctest-failure { 48 | color: #900; 49 | } 50 | 51 | .doctest-example .doctest-actual-output { 52 | color: #066; 53 | /*padding-left: 1em;*/ 54 | } 55 | 56 | .doctest-example.doctest-failure .doctest-output { 57 | padding-left: 1em; 58 | } 59 | 60 | .doctest-example .doctest-output { 61 | font-weight: bold; 62 | } 63 | 64 | .doctest-example .doctest-description { 65 | color: #000; 66 | font-weight: bold; 67 | } 68 | 69 | .doctest-example .doctest-console { 70 | color: #009; 71 | padding-left: 1em; 72 | } 73 | 74 | /* Reporter formatting: */ 75 | 76 | #doctest-success-count.doctest-nonzero { 77 | color: #0f0; 78 | } 79 | 80 | #doctest-failure-count.doctest-nonzero { 81 | color: #f00; 82 | } 83 | 84 | .doctest-report-table th { 85 | font-weight: normal; 86 | text-align: left; 87 | } 88 | 89 | .doctest-report-table td { 90 | padding: 0 1em; 91 | } 92 | 93 | a.doctest-failure-link { 94 | color: #00f; 95 | text-decoration: none; 96 | padding: 0 1em 0 0; 97 | } 98 | 99 | a.doctest-failure-link:visited { 100 | color: #00f; 101 | } 102 | 103 | a.doctest-failure-link:hover { 104 | text-decoration: underline; 105 | } 106 | 107 | /* Comparison table */ 108 | 109 | .doctest-comparison-table { 110 | border: 1px solid #000; 111 | /* FIXME: not sure why this doesn't keep the table limited to 100% */ 112 | width: 100%; 113 | } 114 | 115 | .doctest-comparison-table td { 116 | overflow: auto; 117 | } 118 | 119 | .doctest-comparison-header th { 120 | background-color: #000; 121 | color: #fff; 122 | } 123 | 124 | .doctest-comparison-error td { 125 | background-color: #fdd; 126 | } 127 | 128 | td.doctest-comparison-got { 129 | padding-right: 1em; 130 | color: #060; 131 | } 132 | 133 | td.doctest-comparison-expected { 134 | color: #900; 135 | } 136 | -------------------------------------------------------------------------------- /test/doctest/esprima/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | - 0.9 6 | 7 | -------------------------------------------------------------------------------- /test/doctest/esprima/ChangeLog: -------------------------------------------------------------------------------- 1 | 2012-10-22: Version 1.0.0 2 | 3 | Initial release. 4 | -------------------------------------------------------------------------------- /test/doctest/esprima/LICENSE.BSD: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted provided that the following conditions are met: 3 | 4 | * Redistributions of source code must retain the above copyright 5 | notice, this list of conditions and the following disclaimer. 6 | * Redistributions in binary form must reproduce the above copyright 7 | notice, this list of conditions and the following disclaimer in the 8 | documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 11 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 12 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 13 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 14 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 15 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 16 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 17 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 18 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 19 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | -------------------------------------------------------------------------------- /test/doctest/esprima/assets/forkme_right_red_aa0000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/test/doctest/esprima/assets/forkme_right_red_aa0000.png -------------------------------------------------------------------------------- /test/doctest/esprima/assets/prettify/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} -------------------------------------------------------------------------------- /test/doctest/esprima/assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #ffffff; 3 | min-width: 960px; 4 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | font-size: 13px; 6 | font-weight: normal; 7 | line-height: 18px; 8 | color: #444; 9 | margin: 0; 10 | } 11 | 12 | p { 13 | font-size: 13px; 14 | font-weight: normal; 15 | line-height: 18px; 16 | margin-bottom: 9px; 17 | } 18 | 19 | ul { 20 | padding-left: 20px; 21 | } 22 | 23 | li { 24 | font-size: 13px; 25 | font-weight: normal; 26 | line-height: 18px; 27 | margin-bottom: 4px; 28 | } 29 | 30 | pre { 31 | margin-left: 15px; 32 | font-family: Inconsolata, Menlo, 'Courier New', monospace; 33 | } 34 | 35 | h1 { 36 | margin-top: 2px; 37 | margin-bottom: 18px; 38 | font-size: 30px; 39 | line-height: 36px; 40 | font-weight: bold; 41 | color: #444; 42 | } 43 | 44 | h1 small { 45 | font-size: 18px; 46 | color: #ccc; 47 | } 48 | 49 | h3, h4 { 50 | font-size: 18px; 51 | font-weight: bold; 52 | color: #444; 53 | } 54 | 55 | h4 { 56 | font-size: 16px; 57 | } 58 | 59 | .code { 60 | border-left: 3px solid #ddd; 61 | margin: 5px 3px 8px 15px; 62 | padding: 0 0 0 8px; 63 | } 64 | 65 | .CodeMirror { 66 | padding: 0; 67 | border: 1px solid #ccc; 68 | } 69 | 70 | .CodeMirror-scroll { 71 | height: 200px; 72 | } 73 | 74 | .container { 75 | margin-left: auto; 76 | margin-right: auto; 77 | width: 960px; 78 | } 79 | 80 | .topbar { 81 | top: 0; 82 | margin: 0 0 0 480px; 83 | padding: 0; 84 | width: 300px; 85 | } 86 | 87 | .topbar ul { 88 | padding: 5px 20px 5px 10px; 89 | background-color: #222; 90 | margin: 0 10px 0 0; 91 | } 92 | 93 | .topbar a { 94 | color: #ddd; 95 | text-decoration: none; 96 | } 97 | 98 | .topbar a:hover { 99 | color: #fff; 100 | } 101 | 102 | .topbar .nav li { 103 | background-color: #222; 104 | display: inline; 105 | padding: 4px 10px 4px 0px; 106 | margin: 0; 107 | } 108 | 109 | .container .main { 110 | width: 540px; 111 | display: inline; 112 | float: left; 113 | margin-left: 10px; 114 | margin-right: 10px; 115 | } 116 | 117 | .container .sidebar { 118 | width: 350px; 119 | display: inline; 120 | float: left; 121 | margin-left: 30px; 122 | margin-right: 10px; 123 | } 124 | 125 | .footer { 126 | margin-top: 25px; 127 | margin-bottom: 25px; 128 | color: #555; 129 | text-align: center; 130 | } 131 | 132 | textarea { 133 | font-family: Inconsolata, Monaco, Consolas, "Lucida Console", monospace; 134 | font-size: 13px; 135 | color: #555; 136 | width: 80%; 137 | padding: 7px; 138 | overflow: auto; 139 | } 140 | 141 | table { 142 | width: 80%; 143 | border: 1px solid #ccc; 144 | border-spacing: 0; 145 | } 146 | 147 | thead { 148 | font-weight: bold; 149 | } 150 | 151 | table th, table td { 152 | border-left: 1px solid #ccc; 153 | padding: 3px 3px 10px 3px; 154 | text-align: center; 155 | } 156 | 157 | table td { 158 | border-top: 1px solid #ccc; 159 | } 160 | 161 | tbody tr:nth-child(odd) td { 162 | background-color: #eee; 163 | } 164 | 165 | -------------------------------------------------------------------------------- /test/doctest/esprima/assets/yui/treeview-sprite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfrazee/local/becf1f1eafee6eed0f38034f37cc28b13cfd5724/test/doctest/esprima/assets/yui/treeview-sprite.gif -------------------------------------------------------------------------------- /test/doctest/esprima/assets/yui/treeview.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Yahoo! Inc. All rights reserved. 3 | Code licensed under the BSD License: 4 | http://developer.yahoo.com/yui/license.html 5 | version: 2.9.0 6 | */ 7 | table.ygtvtable{margin-bottom:0;border:0;border-collapse:collapse}td.ygtvcell{border:0;padding:0}a.ygtvspacer{text-decoration:none;outline-style:none;display:block}.ygtvtn{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -5600px no-repeat;cursor:pointer}.ygtvtm{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -4000px no-repeat}.ygtvtmh,.ygtvtmhh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -4800px no-repeat}.ygtvtp{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -6400px no-repeat}.ygtvtph,.ygtvtphh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -7200px no-repeat}.ygtvln{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -1600px no-repeat;cursor:pointer}.ygtvlm{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 0 no-repeat}.ygtvlmh,.ygtvlmhh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -800px no-repeat}.ygtvlp{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -2400px no-repeat}.ygtvlph,.ygtvlphh{width:18px;height:22px;cursor:pointer;background:url(treeview-sprite.gif) 0 -3200px no-repeat;cursor:pointer}.ygtvloading{width:18px;height:22px;background:url(treeview-loading.gif) 0 0 no-repeat}.ygtvdepthcell{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -8000px no-repeat}.ygtvblankdepthcell{width:18px;height:22px}* html .ygtvchildren{height:2%}.ygtvlabel,.ygtvlabel:link,.ygtvlabel:visited,.ygtvlabel:hover{margin-left:2px;text-decoration:none;background-color:white;cursor:pointer}.ygtvcontent{cursor:default}.ygtvspacer{height:22px;width:18px}.ygtvfocus{background-color:#c0e0e0;border:0}.ygtvfocus .ygtvlabel,.ygtvfocus .ygtvlabel:link,.ygtvfocus .ygtvlabel:visited,.ygtvfocus .ygtvlabel:hover{background-color:#c0e0e0}.ygtvfocus a{outline-style:none}.ygtvok{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -8800px no-repeat}.ygtvok:hover{background:url(treeview-sprite.gif) 0 -8844px no-repeat}.ygtvcancel{width:18px;height:22px;background:url(treeview-sprite.gif) 0 -8822px no-repeat}.ygtvcancel:hover{background:url(treeview-sprite.gif) 0 -8866px no-repeat}.ygtv-label-editor{background-color:#f2f2f2;border:1px solid silver;position:absolute;display:none;overflow:hidden;margin:auto;z-index:9000}.ygtv-edit-TextNode{width:190px}.ygtv-edit-TextNode .ygtvcancel,.ygtv-edit-TextNode .ygtvok{border:0}.ygtv-edit-TextNode .ygtv-button-container{float:right}.ygtv-edit-TextNode .ygtv-input input{width:140px}.ygtv-edit-DateNode .ygtvcancel{border:0}.ygtv-edit-DateNode .ygtvok{display:none}.ygtv-edit-DateNode .ygtv-button-container{text-align:right;margin:auto}.ygtv-highlight .ygtv-highlight1,.ygtv-highlight .ygtv-highlight1 .ygtvlabel{background-color:blue;color:white}.ygtv-highlight .ygtv-highlight2,.ygtv-highlight .ygtv-highlight2 .ygtvlabel{background-color:silver}.ygtv-highlight .ygtv-highlight0 .ygtvfocus .ygtvlabel,.ygtv-highlight .ygtv-highlight1 .ygtvfocus .ygtvlabel,.ygtv-highlight .ygtv-highlight2 .ygtvfocus .ygtvlabel{background-color:#c0e0e0}.ygtv-highlight .ygtvcontent{padding-right:1em}.ygtv-checkbox .ygtv-highlight0 .ygtvcontent{padding-left:1em;background:url(check0.gif) no-repeat}.ygtv-checkbox .ygtv-highlight0 .ygtvfocus.ygtvcontent,.ygtv-checkbox .ygtv-highlight1 .ygtvfocus.ygtvcontent,.ygtv-checkbox .ygtv-highlight2 .ygtvfocus.ygtvcontent{background-color:#c0e0e0}.ygtv-checkbox .ygtv-highlight1 .ygtvcontent{padding-left:1em;background:url(check1.gif) no-repeat}.ygtv-checkbox .ygtv-highlight2 .ygtvcontent{padding-left:1em;background:url(check2.gif) no-repeat} 8 | -------------------------------------------------------------------------------- /test/doctest/esprima/bin/esparse.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright (C) 2011 Ariya Hidayat 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | /*jslint sloppy:true node:true */ 27 | 28 | var fs = require('fs'), 29 | esprima = require('esprima'), 30 | files = process.argv.splice(2); 31 | 32 | if (files.length === 0) { 33 | console.log('Usage:'); 34 | console.log(' esparse file.js'); 35 | process.exit(1); 36 | } 37 | 38 | files.forEach(function (filename) { 39 | var content = fs.readFileSync(filename, 'utf-8'); 40 | console.log(JSON.stringify(esprima.parse(content), null, 4)); 41 | }); 42 | /* vim: set sw=4 ts=4 et tw=80 : */ 43 | -------------------------------------------------------------------------------- /test/doctest/esprima/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esprima", 3 | "version": "1.0.0-dev", 4 | "main": "./esprima.js", 5 | "dependencies": {}, 6 | "repository": { 7 | "type": "git", 8 | "url": "http://github.com/ariya/esprima.git" 9 | }, 10 | "licenses": [{ 11 | "type": "BSD", 12 | "url": "http://github.com/ariya/esprima/raw/master/LICENSE.BSD" 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /test/doctest/esprima/demo/checkenv.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 Ariya Hidayat 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | // Unfortunately we have to use User Agent detection to blacklist the browsers. 26 | /*jslint browser:true */ 27 | (function (window) { 28 | 'use strict'; 29 | 30 | var majorVersion = parseInt(window.platform.version.split('.')[0], 10); 31 | 32 | window.checkEnv = function () { 33 | if (window.platform.name === 'Safari' && majorVersion <= 3) { 34 | throw new Error('CodeMirror does not support Safari <= 3'); 35 | } 36 | if (window.platform.name === 'Opera' && majorVersion <= 8) { 37 | throw new Error('CodeMirror does not support Opera <= 8'); 38 | } 39 | }; 40 | 41 | }(window)); 42 | /* vim: set sw=4 ts=4 et tw=80 : */ 43 | -------------------------------------------------------------------------------- /test/doctest/esprima/demo/collector.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Regex Collector Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 25 |
26 | 27 |

Regular Expression Collector uncovers all your secrets

28 | 29 |

Note: Only regular expression literals and objects created with RegExp are considered.

30 |

Type ECMAScript code:

31 |
43 |

The above code editor is based on CodeMirror.

44 | 45 |

Using Esprima version .

46 | 47 |
48 |
49 | 50 | 53 | 54 | 55 |
56 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /test/doctest/esprima/demo/functiontrace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Function Trace Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 |
23 | 24 |
25 | 31 |
32 | 33 |

Function Trace reveals what is being called

34 | 35 |

Type ECMAScript code:

36 |
59 |

The above code editor is based on CodeMirror.

60 | 61 |

62 |   63 |

64 | 65 |

Instrumentation for each function will be added with the Insert tracing button. Pressing the Run button will execute the code (using JavaScript eval) with the instrumentation, and then call counts for each function will be displayed (sorted by the number of calls). Warning: do not run untrusted code!

66 | 67 |

No result yet.

68 | 69 |

Using Esprima version .

70 | 73 | 74 | 75 |
76 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /test/doctest/esprima/demo/highlight.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Identifier Highlight 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 28 | 29 | 30 |
31 | 32 |
33 | 39 |
40 | 41 |

Identifier Highlight to spot everything

42 | 43 |

Type ECMAScript code and place the cursor in an identifier.
44 | Note: Scope is not handled properly yet, see issue 98.

45 | 46 |
59 | 60 |

The above code editor is based on CodeMirror.

61 | 62 |

63 | 64 |

Using Esprima version .

65 | 66 | 69 | 70 | 71 |
72 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /test/doctest/esprima/demo/minify.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Code Minify Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 |
23 | 24 |
25 | 31 |
32 | 33 |

Minify compacts everything

34 | 35 |

Type ECMAScript code in the editor. Press Minify button to shorten the code 36 | using Escodegen project.

37 | 38 |
56 | 57 |

58 | 59 |

The above code editor is based on CodeMirror.

60 | 61 |

62 | 63 |

64 | 65 | 68 | 69 | 70 |
71 | 72 | 73 | -------------------------------------------------------------------------------- /test/doctest/esprima/demo/parse.css: -------------------------------------------------------------------------------- 1 | .tabs textarea { 2 | padding: 7px 0px 7px 7px; 3 | } 4 | 5 | #url { 6 | font-family: Inconsolata, Monaco, Consolas, "Lucida Console", monospace; 7 | font-size: 13px; 8 | color: #555; 9 | width: 80%; 10 | border-width: 1px; 11 | border-style: solid; 12 | border-color: #aaa; 13 | overflow: auto; 14 | display: block; 15 | } 16 | 17 | .tabs { 18 | position: relative; 19 | display: block; 20 | margin-top: 30px; 21 | height: 1px; 22 | } 23 | 24 | .tabs ul, .tabs li { 25 | list-style-type: none; 26 | margin: 0; 27 | line-height: 0px; 28 | } 29 | 30 | .tabs h3 { 31 | float: left; 32 | position: relative; 33 | margin: 0 6px 0 0; 34 | border: 1px solid #bbb; 35 | background-color: #eee; 36 | border-bottom: none; 37 | cursor: pointer; 38 | z-index: 0; 39 | -moz-border-radius-topleft: 6px; 40 | -webkit-border-top-left-radius: 6px; 41 | -ms-border-top-left-radius: 6px; 42 | -o-border-top-left-radius: 6px; 43 | border-top-left-radius: 6px; 44 | -moz-border-radius-topright: 6px; 45 | -webkit-border-top-right-radius: 6px; 46 | -ms-border-top-right-radius: 6px; 47 | -o-border-top-right-radius: 6px; 48 | border-top-right-radius: 6px 49 | } 50 | 51 | .tabs .active h3 { 52 | background-color: #fff; 53 | border-bottom-color: #fff; 54 | z-index: 2 55 | } 56 | 57 | .tabs h3 a { 58 | padding: 0 10px; 59 | line-height: 29px; 60 | font-size: 13px; 61 | font-weight: normal; 62 | } 63 | 64 | .tabs a { 65 | color: black; 66 | text-decoration: none; 67 | } 68 | 69 | .tabs a:visited { 70 | color: black; 71 | } 72 | 73 | .tabs .tab { 74 | position: absolute; 75 | display: none; 76 | left: 0; 77 | top: 29px; 78 | right: 0; 79 | border-top: 1px solid #bbb; 80 | z-index: 1; 81 | padding: 25px 60px 50px 5px; 82 | } 83 | 84 | .pages div { 85 | display: none; 86 | } 87 | 88 | .pages div.active { 89 | display: block; 90 | } 91 | 92 | .tabs .active .tab { 93 | display: block; 94 | } 95 | -------------------------------------------------------------------------------- /test/doctest/esprima/demo/rewrite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Full Source Rewrite Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 |
23 | 24 |
25 | 31 |
32 | 33 |

Source Rewrite cleans up and reformats everything

34 | 35 |

Type ECMAScript code in the editor. Press Rewrite button to get the code 36 | rewritten using Escodegen project.

37 | 38 |
58 | 59 |

Indent with: 60 | 61 | 62 | 63 |

64 |

String literal quotes: 65 | 66 | 67 | 68 |

69 | 70 | 71 |

72 | 73 |

The above code editor is based on CodeMirror.

74 | 75 |

76 | 77 |

Notes:

78 |
    79 |
  • Only valid syntax is accepted.
  • 80 |
  • Comments are not yet preserved (see issue #197).
  • 81 |
82 | 83 |

84 | 85 | 88 | 89 | 90 |
91 | 92 | 93 | -------------------------------------------------------------------------------- /test/doctest/esprima/demo/validate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Syntax Validator 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 27 | 28 | 29 |
30 | 31 |
32 | 38 |
39 | 40 |

Syntax Validator checks for mistakes and errors

41 | 42 |

Esprima version .

43 | 44 |

Type ECMAScript code:

45 |
62 |

The above code editor is based on CodeMirror.

63 | 64 |

No result yet.

65 | 66 | 69 | 70 |
71 |
72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /test/doctest/esprima/demo/validate.js: -------------------------------------------------------------------------------- 1 | /*jslint sloppy:true browser:true */ 2 | /*global esprima:true, CodeMirror:true */ 3 | var validateId; 4 | 5 | function validate(delay) { 6 | if (validateId) { 7 | window.clearTimeout(validateId); 8 | } 9 | 10 | validateId = window.setTimeout(function () { 11 | var code, result, i, str; 12 | 13 | if (typeof window.editor === 'undefined') { 14 | code = document.getElementById('code').value; 15 | } else { 16 | code = window.editor.getValue(); 17 | } 18 | 19 | try { 20 | result = esprima.parse(code, { tolerant: true, loc: true }).errors; 21 | if (result.length > 0) { 22 | str = '

Found ' + result.length + ':

'; 23 | for (i = 0; i < result.length; i += 1) { 24 | str += '

' + result[i].message + '

'; 25 | } 26 | } else { 27 | str = '

No syntax error.

'; 28 | } 29 | } catch (e) { 30 | str = e.name + ': ' + e.message; 31 | } 32 | document.getElementById('result').innerHTML = str; 33 | 34 | validateId = undefined; 35 | }, delay || 811); 36 | } 37 | 38 | window.onload = function () { 39 | var id, el; 40 | 41 | id = function (i) { 42 | return document.getElementById(i); 43 | }; 44 | 45 | el = id('version'); 46 | if (typeof el.innerText === 'string') { 47 | el.innerText = esprima.version; 48 | } else { 49 | el.textContent = esprima.version; 50 | } 51 | try { 52 | validate(1); 53 | } catch (e) { } 54 | }; 55 | 56 | try { 57 | window.checkEnv(); 58 | 59 | // This is just testing, to detect whether CodeMirror would fail or not 60 | window.editor = CodeMirror.fromTextArea(document.getElementById("test")); 61 | 62 | window.editor = CodeMirror.fromTextArea(document.getElementById("code"), { 63 | lineNumbers: true, 64 | matchBrackets: true, 65 | onChange: validate 66 | }); 67 | } catch (e) { 68 | // CodeMirror failed to initialize, possible in e.g. old IE. 69 | document.getElementById('codemirror').innerHTML = ''; 70 | document.getElementById('code').onchange = validate; 71 | document.getElementById('code').onkeydown = validate; 72 | } finally { 73 | document.getElementById('testbox').parentNode.removeChild(document.getElementById('testbox')); 74 | } 75 | -------------------------------------------------------------------------------- /test/doctest/esprima/examples/detectnestedternary.js: -------------------------------------------------------------------------------- 1 | // Usage: node detectnestedternary.js /path/to/some/directory 2 | // For more details, please read http://esprima.org/doc/#nestedternary 3 | 4 | /*jslint node:true sloppy:true plusplus:true */ 5 | 6 | var fs = require('fs'), 7 | esprima = require('../esprima'), 8 | dirname = process.argv[2]; 9 | 10 | 11 | // Executes visitor on the object and its children (recursively). 12 | function traverse(object, visitor) { 13 | var key, child; 14 | 15 | visitor.call(null, object); 16 | for (key in object) { 17 | if (object.hasOwnProperty(key)) { 18 | child = object[key]; 19 | if (typeof child === 'object' && child !== null) { 20 | traverse(child, visitor); 21 | } 22 | } 23 | } 24 | } 25 | 26 | // http://stackoverflow.com/q/5827612/ 27 | function walk(dir, done) { 28 | var results = []; 29 | fs.readdir(dir, function (err, list) { 30 | if (err) { 31 | return done(err); 32 | } 33 | var i = 0; 34 | (function next() { 35 | var file = list[i++]; 36 | if (!file) { 37 | return done(null, results); 38 | } 39 | file = dir + '/' + file; 40 | fs.stat(file, function (err, stat) { 41 | if (stat && stat.isDirectory()) { 42 | walk(file, function (err, res) { 43 | results = results.concat(res); 44 | next(); 45 | }); 46 | } else { 47 | results.push(file); 48 | next(); 49 | } 50 | }); 51 | }()); 52 | }); 53 | } 54 | 55 | walk(dirname, function (err, results) { 56 | if (err) { 57 | console.log('Error', err); 58 | return; 59 | } 60 | 61 | results.forEach(function (filename) { 62 | var shortname, first, content, syntax; 63 | 64 | shortname = filename; 65 | first = true; 66 | 67 | if (shortname.substr(0, dirname.length) === dirname) { 68 | shortname = shortname.substr(dirname.length + 1, shortname.length); 69 | } 70 | 71 | function report(node, problem) { 72 | if (first === true) { 73 | console.log(shortname + ': '); 74 | first = false; 75 | } 76 | console.log(' Line', node.loc.start.line, ':', problem); 77 | } 78 | 79 | function checkConditional(node) { 80 | var condition; 81 | 82 | if (node.consequent.type === 'ConditionalExpression' || 83 | node.alternate.type === 'ConditionalExpression') { 84 | 85 | condition = content.substring(node.test.range[0], node.test.range[1]); 86 | if (condition.length > 20) { 87 | condition = condition.substring(0, 20) + '...'; 88 | } 89 | condition = '"' + condition + '"'; 90 | report(node, 'Nested ternary for ' + condition); 91 | } 92 | } 93 | 94 | try { 95 | content = fs.readFileSync(filename, 'utf-8'); 96 | syntax = esprima.parse(content, { tolerant: true, loc: true, range: true }); 97 | traverse(syntax, function (node) { 98 | if (node.type === 'ConditionalExpression') { 99 | checkConditional(node); 100 | } 101 | }); 102 | } catch (e) { 103 | } 104 | 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /test/doctest/esprima/examples/tokendist.js: -------------------------------------------------------------------------------- 1 | /*jslint node:true */ 2 | var fs = require('fs'), 3 | esprima = require('../esprima'), 4 | files = process.argv.splice(2), 5 | histogram, 6 | type; 7 | 8 | histogram = { 9 | Boolean: 0, 10 | Identifier: 0, 11 | Keyword: 0, 12 | Null: 0, 13 | Numeric: 0, 14 | Punctuator: 0, 15 | RegularExpression: 0, 16 | String: 0 17 | }; 18 | 19 | files.forEach(function (filename) { 20 | 'use strict'; 21 | var content = fs.readFileSync(filename, 'utf-8'), 22 | tokens = esprima.parse(content, { tokens: true }).tokens; 23 | 24 | tokens.forEach(function (token) { 25 | histogram[token.type] += 1; 26 | }); 27 | }); 28 | 29 | for (type in histogram) { 30 | if (histogram.hasOwnProperty(type)) { 31 | console.log(type, histogram[type]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/doctest/esprima/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esprima", 3 | "description": "ECMAScript parsing infrastructure for multipurpose analysis", 4 | "homepage": "http://esprima.org", 5 | "main": "esprima.js", 6 | "bin": { 7 | "esparse": "./bin/esparse.js" 8 | }, 9 | "version": "1.1.0-dev", 10 | "engines": { 11 | "node": ">=0.4.0" 12 | }, 13 | "maintainers": [{ 14 | "name": "Ariya Hidayat", 15 | "email": "ariya.hidayat@gmail.com", 16 | "web": "http://ariya.ofilabs.com" 17 | }], 18 | "repository": { 19 | "type": "git", 20 | "url": "http://github.com/ariya/esprima.git" 21 | }, 22 | "licenses": [{ 23 | "type": "BSD", 24 | "url": "http://github.com/ariya/esprima/raw/master/LICENSE.BSD" 25 | }], 26 | "scripts": { 27 | "test": "node test/run.js", 28 | "benchmark": "node test/benchmarks.js", 29 | "benchmark-quick": "node test/benchmarks.js quick" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/doctest/esprima/test/benchmarks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Benchmarks 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 31 |
32 | 33 |

Benchmarks show the speed

34 | 35 |

Time measurement is carried out using Benchmark.js.

36 | 37 |

Esprima version .

38 | 39 |

Please wait...

40 | 41 |

42 | 43 | 44 |

45 | 46 |

47 | 48 |

Note: On a modern machine and up-to-date web browsers, 49 | running full benchmarks suite takes around 1 minute.
50 | For the quick benchmarks, the running time is only about 15 seconds.

51 | 52 | 55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /test/doctest/esprima/test/compare.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Speed Comparison 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 31 |
32 | 33 |

Compare with other parsers

34 | 35 |

Time measurement is carried out using Benchmark.js.

36 | 37 |

Esprima version .

38 | 39 |

Please wait... 40 |

41 | 42 |

43 | 44 | 45 |

Warning: Since each parser may have a different format for the syntax tree, the speed is not fully comparable (the cost of constructing different result is not taken into account). These tests exist only to ensure that Esprima parser is not ridiculously slow compare to other parsers.

46 | 47 |

parse-js is the parser used in UglifyJS v1. It's a JavaScript port of the Common LISP version. This test uses parse-js from UglifyJS version 1.3.2 (June 26 2012).

48 | 49 |

Acorn is a compact stand-alone JavaScript parser. This test uses Acorn revision 0590d122 (dated Oct 3 2012).

50 | 51 |

More comparison variants will be added in the near future.

52 | 53 | 56 |
57 | 58 | 59 | 60 | 61 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/doctest/esprima/test/compat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Compatibility Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 22 |
23 | 24 |

Compatibility keeps everyone happy

25 |

Esprima version .

26 |

The following tests are to ensure that the syntax tree is compatible to 27 | that produced by Mozilla SpiderMonkey 28 | Parser reflection.

29 |

Please wait...

30 |
31 | 34 |
35 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/doctest/esprima/test/coverage.footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/doctest/esprima/test/coverage.header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Coverage Analysis Report 6 | 7 | 8 | 20 | 21 |
22 | 23 |
24 | 30 |
31 | 32 |

Coverage Analysis ensures systematic exercise of the parser

33 | 34 |

Note: This is not a live (in-browser) code coverage report. 35 | The analysis is performed offline 36 | (using node-cover).
37 | 38 | -------------------------------------------------------------------------------- /test/doctest/esprima/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Unit Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

14 | 15 |
16 | 22 |
23 | 24 |

Unit Tests ensures the correct implementation

25 |

Esprima version .

26 |

Please wait...

27 |
28 | 31 |
32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/doctest/esprima/test/module.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Esprima: Module Loading Test 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 19 |
20 | 21 |

Module Loading works also with web browsers

22 | 23 |

The following tests are to ensure that Esprima can be loaded using AMD (Asynchronous Module Definition) loader such as RequireJS.

24 |

25 |
26 | 29 |
30 | 31 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/doctest/esprima/test/run.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2012 Yusuke Suzuki 3 | Copyright (C) 2012 Ariya Hidayat 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | /*jslint node:true */ 27 | 28 | (function () { 29 | 'use strict'; 30 | 31 | var child = require('child_process'), 32 | nodejs = '"' + process.execPath + '"', 33 | ret = 0, 34 | suites, 35 | index; 36 | 37 | suites = [ 38 | 'runner', 39 | 'compat' 40 | ]; 41 | 42 | function nextTest() { 43 | var suite = suites[index]; 44 | 45 | if (index < suites.length) { 46 | child.exec(nodejs + ' ./test/' + suite + '.js', function (err, stdout, stderr) { 47 | if (stdout) { 48 | process.stdout.write(suite + ': ' + stdout); 49 | } 50 | if (stderr) { 51 | process.stderr.write(suite + ': ' + stderr); 52 | } 53 | if (err) { 54 | ret = err.code; 55 | } 56 | index += 1; 57 | nextTest(); 58 | }); 59 | } else { 60 | process.exit(ret); 61 | } 62 | } 63 | 64 | index = 0; 65 | nextTest(); 66 | }()); 67 | -------------------------------------------------------------------------------- /test/doctest/esprima/tools/update-coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ `command -v cover` ]; then 4 | 5 | REVISION=`git log -1 --pretty=%h` 6 | DATE=`git log -1 --pretty=%cD | cut -c 6-16` 7 | 8 | echo "Running coverage analysis..." 9 | rm -rf .coverage_data 10 | rm -rf cover_index 11 | 12 | cover run test/runner.js 13 | cover report html 14 | 15 | cat test/coverage.header.html > test/coverage.html 16 | echo "Tested revision: ${REVISION}" >> test/coverage.html 17 | echo " (dated ${DATE}).

" >> test/coverage.html 18 | 19 | echo '
' >> test/coverage.html
20 |     grep -h '^> test/coverage.html
21 |     echo '
' >> test/coverage.html 22 | cat test/coverage.footer.html >> test/coverage.html 23 | 24 | rm -rf cover_html 25 | rm -rf .coverage_data 26 | else 27 | echo "Please install node-cover first!" 28 | fi 29 | -------------------------------------------------------------------------------- /test/doctest/examples/examples-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | $ print('hey you')
18 | hey you
19 | $ print({'whatever': 'example', something: [1, 2, 3]});
20 | {something: [1, 2, 3], whatever: "example"}
21 | $ print(1/0)
22 | NaN
23 | $ function foo() {
24 | >   console.log({hey: 'you'});
25 | >   throw 'whatever';
26 | > }
27 | > foo();
28 | Error: whatever
29 | 
30 | 31 |
32 | print(1+2)
33 | /* =>
34 |    3
35 | */
36 | 
37 | function testme() {
38 |   setTimeout(function () {
39 |     print('hey you!');
40 |   }, 10);
41 | }
42 | testme();
43 | wait(200);
44 | /* => hey you! */
45 | 
46 | 47 |

48 | 
49 | 
50 | function stop() {
51 |   throw Abort();
52 | }
53 | 
54 | print(1+2);
55 | /* => 3 */
56 | 
57 | stop();
58 | /* => something */
59 | 
60 | print(5+5);
61 | /* => 12 */
62 | 
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/doctest/examples/examples-2.js: -------------------------------------------------------------------------------- 1 | function factorial(n) { 2 | if (n == 1) { 3 | return n; 4 | } 5 | return n * factorial(n-1); 6 | } 7 | 8 | print(factorial(4)); 9 | /* => 24 */ 10 | print(factorial(3)); 11 | /* => 12 | 20 13 | */ -------------------------------------------------------------------------------- /test/doctest/examples/node-example.js: -------------------------------------------------------------------------------- 1 | var doctest = require('../doctest.js'); 2 | 3 | var runner = new doctest.Runner({Reporter: doctest.ConsoleReporter}); 4 | var parser = new doctest.TextParser.fromFile(runner, './examples-2.js'); 5 | parser.parse(); 6 | runner.run(); 7 | var reporter = runner.reporter; 8 | console.log('Successes:', reporter.successes); 9 | console.log('Failures:', reporter.failures); 10 | if ((! reporter.successes) || reporter.failures) { 11 | process.exit(1); 12 | } 13 | -------------------------------------------------------------------------------- /test/doctest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doctest", 3 | "version": "0.3", 4 | "main": "doctest", 5 | "description": "Example-based testing framework", 6 | "keywords": [], 7 | "author": { 8 | "name": "Ian Bicking", 9 | "email": "ian@ianbicking.org", 10 | "web": "http://ianbicking.org", 11 | "twitter": "ianbicking" 12 | }, 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/ianb/doctestjs.git" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/doctest/try.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Doctest.js: Try It! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 |

Doctest.js: Try It!

48 | 49 | 56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 | 64 | 65 | 66 |
67 | 68 | 69 | 70 | 82 | 87 | 88 |
71 | 79 |
80 | 81 |
83 |
84 |

 85 |       
86 |
89 | 90 | 91 | 92 |
93 | 94 |
95 | 96 | 102 | 103 | 104 | 107 | 108 | 109 | 110 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | 2 | // test runner helpers 3 | 4 | var done; 5 | var startTime; 6 | function printSuccess(res) { 7 | print('success'); 8 | print(res); 9 | return res; 10 | } 11 | function printError(res) { 12 | print('error'); 13 | print(res); 14 | throw res; 15 | } 16 | function finishTest() { 17 | console.log('Test Duration:', Date.now() - startTime, 'ms'); 18 | done = true; 19 | } 20 | function printSuccessAndFinish(res) { printSuccess(res); finishTest(); } 21 | function printErrorAndFinish(err) { print('error'); print(err); finishTest(); } -------------------------------------------------------------------------------- /test/rtcpeer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Local WebRTC Peer Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Local WebRTC Peer Test Suite

16 |

brought to you by doctest.js

17 | 18 | 19 | 20 |

21 | 
22 |   
23 | 
24 | 


--------------------------------------------------------------------------------
/test/web.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 	
 5 | 		
 6 | 		Local HTTP/L Tests
 7 | 
 8 | 		
 9 | 		
10 | 		
11 | 		
12 | 	
13 | 	
14 | 
15 | 		

Local HTTP/L Test Suite

16 |

brought to you by doctest.js

17 |

Note: test-server.js must be running on port 8080. `node test-server.js`

18 | 19 | 20 | 21 | 22 |

23 | 		

24 | 		

25 | 		

26 | 
27 |   
28 | 


--------------------------------------------------------------------------------
/test/web/_worker.js:
--------------------------------------------------------------------------------
 1 | importScripts('../../local.js');
 2 | local.worker.setServer(function(request, response) {
 3 | 	var foos = ['bar', 'baz', 'blah'];
 4 | 	var payload = null, linkHeader;
 5 | 	if (request.path == '/') {
 6 | 		if (request.method === 'GET') {
 7 | 			payload = 'service resource';
 8 | 		}
 9 | 		if (Object.keys(request.query).length > 0) {
10 | 			payload += ' '+JSON.stringify(request.query);
11 | 		}
12 | 		linkHeader = [
13 | 			{ rel:'self current', href:'/' },
14 | 			{ rel:'collection', href:'/events', id:'events' },
15 | 			{ rel:'collection', href:'/foo', id:'foo' },
16 | 			{ rel:'collection', href:'/{id}' }
17 | 		];
18 | 		response.writeHead(200, 'ok', { 'content-type':'text/plain', 'link':linkHeader });
19 | 		response.end(payload);
20 | 	}
21 | 	else if (request.path == '/foo') {
22 | 		if (request.method === 'GET') {
23 | 			payload = foos;
24 | 		}
25 | 		linkHeader = [
26 | 			{ rel:'up via service', href:'/' },
27 | 			{ rel:'self current', href:'/foo' },
28 | 			{ rel:'item', href:'/foo/{id}' }
29 | 		];
30 | 		response.writeHead(200, 'ok', { 'content-type':'application/json', 'link':linkHeader });
31 | 		// so we can experiment with streaming, write the json in bits:
32 | 		if (payload) {
33 | 			response.write('[');
34 | 			payload.forEach(function(p, i) { response.write((i!==0?',':'')+'"'+p+'"'); });
35 | 			response.write(']');
36 | 		}
37 | 		response.end();
38 | 	}
39 | 	else if (/^\/foo\/([A-z]*)$/.test(request.path)) {
40 | 		var match = /^\/foo\/([A-z]*)$/.exec(request.path);
41 | 		var itemName = match[1];
42 | 		var itemIndex = foos.indexOf(itemName);
43 | 		if (itemIndex === -1) {
44 | 			response.writeHead(404, 'not found');
45 | 			response.end();
46 | 			return;
47 | 		}
48 | 		if (request.method === 'GET') {
49 | 			payload = itemName;
50 | 		}
51 | 		linkHeader = [
52 | 			{ rel:'via service', href:'/' },
53 | 			{ rel:'up collection index', href:'/foo' },
54 | 			{ rel:'self current', href:'/foo/'+itemName },
55 | 			{ rel:'first', href:'/foo/'+foos[0] },
56 | 			{ rel:'last', href:'/foo/'+foos[foos.length - 1] }
57 | 		];
58 | 		if (itemIndex !== 0) {
59 | 			linkHeader.push({ rel:'prev', href:'/foo/'+foos[itemIndex - 1] });
60 | 		}
61 | 		if (itemIndex !== foos.length - 1) {
62 | 			linkHeader.push({ rel:'next', href:'/foo/'+foos[itemIndex + 1] });
63 | 		}
64 | 		response.writeHead(200, 'ok', { 'content-type':'application/json', 'link':linkHeader });
65 | 		response.end('"'+payload+'"');
66 | 	}
67 | 	else if (request.path == '/events') {
68 | 		response.writeHead(200, 'ok', { 'content-type':'text/event-stream' });
69 | 		response.write({ event:'foo', data:{ c:1 }});
70 | 		response.write({ event:'foo', data:{ c:2 }});
71 | 		response.write({ event:'bar', data:{ c:3 }});
72 | 		response.write('event: foo\r\n');
73 | 		setTimeout(function() { // break up the event to make sure the client waits for the whole thing
74 | 			response.write('data: { "c": 4 }\r\n\r\n');
75 | 			response.end({ event:'foo', data:{ c:5 }});
76 | 		}, 50);
77 | 	}
78 | 	else if (request.path == '/pipe') {
79 | 		var headerUpdate = function(headers) {
80 | 			headers['content-type'] = 'text/piped+plain';
81 | 			return headers;
82 | 		};
83 | 		var bodyUpdate = function(body) {
84 | 			return body.toUpperCase();
85 | 		};
86 | 		local.pipe(response, local.dispatch({ method:'get', url:'local://test.com/' }), headerUpdate, bodyUpdate);
87 | 	}
88 | 	else if (request.path == '/unserializable-response') {
89 | 		response.writeHead(200, 'ok', { 'content-type':'text/faketype' });
90 | 		response.end({ unserializable: 'response' });
91 | 	}
92 | 	else {
93 | 		response.writeHead(404, 'not found');
94 | 		response.end();
95 | 	}
96 | });


--------------------------------------------------------------------------------
/test/web/events.js:
--------------------------------------------------------------------------------
 1 | 
 2 | // == SECTION events
 3 | 
 4 | // document local event server
 5 | 
 6 | done = false;
 7 | startTime = Date.now();
 8 | var stream = local.subscribe({ url:'local://test.com/events' });
 9 | stream.on('message', function(m) { print(m); });
10 | stream.on('foo', function(m) { print('foo', m.data); });
11 | stream.on('bar', function(m) { print('bar', m.data); });
12 | stream.on('close', function(e) {
13 | 	print('close');
14 | 	console.log(Date.now() - startTime, 'ms');
15 | 	done = true;
16 | });
17 | wait(function () { return done; });
18 | 
19 | /* =>
20 | {data: {c: 1}, event: "foo"}
21 | foo {c: 1}
22 | {data: {c: 2}, event: "foo"}
23 | foo {c: 2}
24 | {data: {c: 3}, event: "bar"}
25 | bar {c: 3}
26 | {data: {c: 4}, event: "foo"}
27 | foo {c: 4}
28 | {data: {c: 5}, event: "foo"}
29 | foo {c: 5}
30 | close
31 | */
32 | 
33 | // worker local event server
34 | 
35 | done = false;
36 | startTime = Date.now();
37 | var stream = local.subscribe({ url:'local://dev.grimwire.com(test/web/_worker.js)/events' });
38 | stream.on('message', function(m) { print(m); });
39 | stream.on('foo', function(m) { print('foo', m.data); });
40 | stream.on('bar', function(m) { print('bar', m.data); });
41 | stream.on('close', function(e) {
42 | 	print('close');
43 | 	console.log(Date.now() - startTime, 'ms');
44 | 	done = true;
45 | });
46 | wait(function () { return done; });
47 | 
48 | /* =>
49 | {data: {c: 1}, event: "foo"}
50 | foo {c: 1}
51 | {data: {c: 2}, event: "foo"}
52 | foo {c: 2}
53 | {data: {c: 3}, event: "bar"}
54 | bar {c: 3}
55 | {data: {c: 4}, event: "foo"}
56 | foo {c: 4}
57 | {data: {c: 5}, event: "foo"}
58 | foo {c: 5}
59 | close
60 | */
61 | 
62 | // remote event server
63 | 
64 | done = false;
65 | startTime = Date.now();
66 | var stream2 = local.subscribe({ url:'http://grimwire.com:8080/events' });
67 | stream2.on('message', function(m) { print(m); });
68 | stream2.on('foo', function(m) { print('foo', m.data); });
69 | stream2.on('bar', function(m) { print('bar', m.data); });
70 | stream2.on('close', function(e) {
71 | 	print('close');
72 | 	console.log(Date.now() - startTime, 'ms');
73 | 	done = true;
74 | });
75 | wait(function () { return done; });
76 | 
77 | /* =>
78 | {data: {c: 1}, event: "foo"}
79 | foo {c: 1}
80 | {data: {c: 2}, event: "foo"}
81 | foo {c: 2}
82 | {data: {c: 3}, event: "bar"}
83 | bar {c: 3}
84 | {data: {c: 4}, event: "foo"}
85 | foo {c: 4}
86 | {data: {c: 5}, event: "foo"}
87 | foo {c: 5}
88 | close
89 | */


--------------------------------------------------------------------------------
/test/worker.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 	
 5 | 		
 6 | 		Local Worker Tests
 7 | 
 8 | 		
 9 | 		
10 | 		
11 | 		
12 | 	
13 | 	
14 | 
15 | 		

Local Worker Test Suite

16 |

brought to you by doctest.js

17 | 18 | 19 | 20 |

21 | 
22 |   
23 | 
24 | 


--------------------------------------------------------------------------------
/test/worker/messaging.js:
--------------------------------------------------------------------------------
  1 | // load worker
  2 | local.spawnWorkerServer('/test/worker/worker1.js', { myname: 'alice' }, function(req, res, me) {
  3 | 	console.log(me.config.domain);
  4 | 	res.writeHead(200, 'ok', { 'content-type': 'text/plain' }).end('yes, hello '+req.query.foo+' '+req.query.bar);
  5 | });
  6 | // local.spawnWorkerServer('/test/worker/worker2.js', { myname: 'bob' });
  7 | local.addServer('worker-bridge', function(req, res, worker) {
  8 | 	console.log(worker.config.domain);
  9 | 	res.writeHead(200, 'ok', { 'content-type': 'text/plain' }).end('no, bye '+req.query.foo+' '+req.query.bar);
 10 | });
 11 | 
 12 | // GET tests
 13 | done = false;
 14 | startTime = Date.now();
 15 | var worker1API = local.agent('local://dev.grimwire.com(test/worker/worker1.js)');
 16 | var worker2API = local.agent('local://dev.grimwire.com(test/worker/worker2.js)');
 17 | var responses_ = [];
 18 | for (var i = 0; i < 10; i++) {
 19 | 	responses_.push(worker1API.dispatch());
 20 | 	responses_.push(worker2API.dispatch());
 21 | }
 22 | 
 23 | local.promise.bundle(responses_)
 24 | 	.always(function(responses) {
 25 | 		responses.forEach(function(res) {
 26 | 			print(res.status + ' ' + res.body);
 27 | 			console.log(res.latency+' ms');
 28 | 		});
 29 | 		finishTest();
 30 | 	});
 31 | wait(function () { return done; });
 32 | 
 33 | /* =>
 34 | 200 0
 35 | 200 100
 36 | 200 1
 37 | 200 99
 38 | 200 2
 39 | 200 98
 40 | 200 3
 41 | 200 97
 42 | 200 4
 43 | 200 96
 44 | 200 5
 45 | 200 95
 46 | 200 6
 47 | 200 94
 48 | 200 7
 49 | 200 93
 50 | 200 8
 51 | 200 92
 52 | 200 9
 53 | 200 91
 54 | */
 55 | 
 56 | done = false;
 57 | startTime = Date.now();
 58 | var worker1API = local.agent('local://dev.grimwire.com(test/worker/worker1.js)');
 59 | var worker2API = local.agent('local://dev.grimwire.com(test/worker/worker2.js)');
 60 | var responses_ = [];
 61 | for (var i = 0; i < 10; i++) {
 62 | 	responses_.push(worker1API.post('FooBar'));
 63 | 	responses_.push(worker2API.post('FooBar'));
 64 | }
 65 | 
 66 | local.promise.bundle(responses_)
 67 | 	.always(function(responses) {
 68 | 		responses.forEach(function(res) {
 69 | 			print(res.status + ' ' + res.body);
 70 | 			console.log(res.latency+' ms');
 71 | 		});
 72 | 		finishTest();
 73 | 	});
 74 | wait(function () { return done; });
 75 | 
 76 | /* =>
 77 | 200 FOOBAR
 78 | 200 foobar
 79 | 200 FOOBAR
 80 | 200 foobar
 81 | 200 FOOBAR
 82 | 200 foobar
 83 | 200 FOOBAR
 84 | 200 foobar
 85 | 200 FOOBAR
 86 | 200 foobar
 87 | 200 FOOBAR
 88 | 200 foobar
 89 | 200 FOOBAR
 90 | 200 foobar
 91 | 200 FOOBAR
 92 | 200 foobar
 93 | 200 FOOBAR
 94 | 200 foobar
 95 | 200 FOOBAR
 96 | 200 foobar
 97 | */
 98 | 
 99 | done = false;
100 | startTime = Date.now();
101 | var worker1API = local.agent('local://dev.grimwire.com(test/worker/worker1.js)');
102 | var worker2API = local.agent('local://dev.grimwire.com(test/worker/worker2.js)');
103 | var responses_ = [
104 | 	worker1API.dispatch({ method: 'bounce' }),
105 | 	worker2API.dispatch({ method: 'bounce' })
106 | ];
107 | 
108 | local.promise.bundle(responses_)
109 | 	.always(function(responses) {
110 | 		responses.forEach(function(res) {
111 | 			print(res.status + ' ' + res.body);
112 | 			console.log(res.latency+' ms');
113 | 		});
114 | 		finishTest();
115 | 	});
116 | wait(function () { return done; });
117 | 
118 | /* =>
119 | 200 yes, hello alice bazz
120 | 200 no, bye bob buzz
121 | */
122 | 
123 | // importScripts() disabling test
124 | done = false;
125 | startTime = Date.now();
126 | var worker1API = local.agent('local://dev.grimwire.com(test/worker/worker1.js)');
127 | worker1API.dispatch({ method: 'IMPORT' })
128 | 	.always(function(res) {
129 | 		print(res.status + ' ' + res.body);
130 | 		finishTest();
131 | 	});
132 | wait(function () { return done; });
133 | 
134 | /* =>
135 | 200 Local.js - Imports disabled after initial load to prevent data-leaking
136 | */


--------------------------------------------------------------------------------
/test/worker/worker1.js:
--------------------------------------------------------------------------------
 1 | importScripts('../../local.js');
 2 | var counter = 0;
 3 | local.worker.setServer(function(req, res, page) {
 4 | 	if (req.path == '/' && req.method == 'GET') {
 5 | 		res.writeHead(200, 'ok', { 'content-type': 'text/plain' });
 6 | 		res.end(''+counter++);
 7 | 		return;
 8 | 	}
 9 | 	if (req.path == '/' && req.method == 'POST') {
10 | 		req.body_.then(function(body) {
11 | 			res.writeHead(200, 'ok', { 'content-type': 'text/plain' });
12 | 			res.end(body.toUpperCase());
13 | 		});
14 | 		return;
15 | 	}
16 | 	if (req.path == '/' && req.method == 'BOUNCE') {
17 | 		local.dispatch({ method: 'GET', url: 'local://host.page?foo='+local.worker.config.myname, query: { bar: 'bazz' } })
18 | 			.always(function(res2) {
19 | 				res.writeHead(200, 'ok', { 'content-type': 'text/plain' });
20 | 				res.end(res2.body);
21 | 			});
22 | 		return;
23 | 	}
24 | 	if (req.path == '/' && req.method == 'IMPORT') {
25 | 		try {
26 | 			importScripts('../../local.js');
27 | 			res.writeHead(500, 'Lib Error').end('Error: import was allowed');
28 | 		} catch(e) {
29 | 			res.writeHead(200, 'OK').end(e.toString());
30 | 		}
31 | 		return;
32 | 	}
33 | 	res.writeHead(404, 'not found').end();
34 | });


--------------------------------------------------------------------------------
/test/worker/worker2.js:
--------------------------------------------------------------------------------
 1 | importScripts('../../local.js');
 2 | var counter = 100;
 3 | local.worker.setServer(function(req, res, page) {
 4 | 	if (req.path == '/' && req.method == 'GET') {
 5 | 		res.writeHead(200, 'ok', { 'content-type': 'text/plain' });
 6 | 		res.end(counter--);
 7 | 		return;
 8 | 	}
 9 | 	if (req.path == '/' && req.method == 'POST') {
10 | 		req.body_.then(function(body) {
11 | 			res.writeHead(200, 'ok', { 'content-type': 'text/plain' });
12 | 			res.end(body.toLowerCase());
13 | 		});
14 | 		return;
15 | 	}
16 | 	if (req.path == '/' && req.method == 'BOUNCE') {
17 | 		local.dispatch({ method: 'GET', url: 'local://0.page?foo=bob', query: { bar: 'buzz' } })
18 | 			.always(function(res2) {
19 | 				res.writeHead(200, 'ok', { 'content-type': 'text/plain' });
20 | 				res.end(res2.body);
21 | 			});
22 | 		return;
23 | 	}
24 | 	res.writeHead(404, 'not found').end();
25 | });


--------------------------------------------------------------------------------
/test/xhrpatch.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 	
 5 | 		
 6 | 		Local XHR Patch Tests
 7 | 
 8 | 		
 9 | 		
10 | 		
11 | 		
12 | 	
13 | 	
14 | 
15 | 		

Local XHR Patch Test Suite

16 |

brought to you by doctest.js

17 |

Note: test-server.js must be running on port 8080. `node test-server.js`

18 | 19 | 20 | 21 | 22 |

23 | 
24 |   
25 | 


--------------------------------------------------------------------------------
/test/xhrpatch/_setup.js:
--------------------------------------------------------------------------------
 1 | local.patchXHR();
 2 | 
 3 | local.addServer('localserver', function(request, response) {
 4 | 	var foos = ['bar', 'baz', 'blah'];
 5 | 	var payload = null, linkHeader;
 6 | 	if (request.path == '/') {
 7 | 		if (request.method === 'GET') {
 8 | 			payload = 'service resource';
 9 | 		}
10 | 		linkHeader = [
11 | 			{ rel:'self current http://grimwire.com/rel/test grimwire.com/rel/test grimwire.com', href:'/' },
12 | 			{ rel:'collection', href:'/events', id:'events' },
13 | 			{ rel:'collection', href:'/foo', id:'foo' },
14 | 			{ rel:'collection', href:'/{id}' }
15 | 		];
16 | 		response.writeHead(200, 'ok', { 'content-type':'text/plain', 'link':linkHeader });
17 | 		response.end(payload);
18 | 	}
19 | 	else if (request.path == '/foo') {
20 | 		if (request.method != 'HEAD') {
21 | 			payload = foos;
22 | 		}
23 | 		if (request.method == 'POST') {
24 | 			return request.body_
25 | 				.then(function(body) {
26 | 					response.writeHead(200, 'ok', { 'content-type': request.headers['content-type'] });
27 | 					response.end(body);
28 | 				});
29 | 		}
30 | 		linkHeader = [
31 | 			{ rel:'up via service', href:'/' },
32 | 			{ rel:'self current', href:'/foo' },
33 | 			{ rel:'item', href:'/foo/{id}' }
34 | 		];
35 | 		response.writeHead(200, 'ok', { 'content-type':'application/json', 'link':linkHeader });
36 | 		// so we can experiment with streaming, write the json in bits:
37 | 		if (payload) {
38 | 			response.write('[');
39 | 			payload.forEach(function(p, i) { response.write((i!==0?',':'')+'"'+p+'"'); });
40 | 			response.write(']');
41 | 		}
42 | 		response.end();
43 | 	}
44 | 	else {
45 | 		response.writeHead(404, 'not found');
46 | 		response.end();
47 | 	}
48 | });


--------------------------------------------------------------------------------
/test/xhrpatch/requests.js:
--------------------------------------------------------------------------------
  1 | // == SECTION xhr local requests
  2 | 
  3 | // local request 1
  4 | 
  5 | done = false;
  6 | startTime = Date.now();
  7 | var xhr = new XMLHttpRequest();
  8 | xhr.open('GET', 'local://localserver');
  9 | xhr.onreadystatechange = function() {
 10 | 	print(xhr.readyState);
 11 | 	print(xhr.responseText);
 12 | 	if (xhr.readyState == 4) {
 13 | 		print('done');
 14 | 		print(xhr.getResponseHeader('content-type'));
 15 | 		print(xhr.getAllResponseHeaders());
 16 | 		print(typeof xhr.response);
 17 | 		print(xhr.response);
 18 | 		done = true;
 19 | 	}
 20 | };
 21 | xhr.send();
 22 | wait(function () { return done; });
 23 | 
 24 | /* =>
 25 | 2
 26 | null
 27 | 3
 28 | service resource
 29 | 4
 30 | service resource
 31 | done
 32 | text/plain
 33 | {
 34 |   "content-type": "text/plain",
 35 |   link: "; rel=\"self current http://grimwire.com/rel/test grimwire.com/rel/test grimwire.com\", ; rel=\"collection\"; id=\"events\", ; rel=\"collection\"; id=\"foo\", ; rel=\"collection\""
 36 | }
 37 | string
 38 | service resource
 39 | */
 40 | 
 41 | // local request 2
 42 | 
 43 | done = false;
 44 | startTime = Date.now();
 45 | var xhr = new XMLHttpRequest();
 46 | xhr.open('GET', 'local://localserver/foo');
 47 | xhr.onreadystatechange = function() {
 48 | 	print(xhr.readyState);
 49 | 	print(xhr.responseText);
 50 | 	if (xhr.readyState == 4) {
 51 | 		print('done');
 52 | 		print(xhr.getResponseHeader('content-type'));
 53 | 		print(xhr.getAllResponseHeaders());
 54 | 		print(typeof xhr.response);
 55 | 		print(xhr.response);
 56 | 		done = true;
 57 | 	}
 58 | };
 59 | xhr.send();
 60 | wait(function () { return done; });
 61 | 
 62 | /* =>
 63 | 2
 64 | null
 65 | 3
 66 | [
 67 | 3
 68 | ["bar"
 69 | 3
 70 | ["bar","baz"
 71 | 3
 72 | ["bar","baz","blah"
 73 | 3
 74 | ["bar","baz","blah"]
 75 | 4
 76 | ["bar","baz","blah"]
 77 | done
 78 | application/json
 79 | {
 80 |   "content-type": "application/json",
 81 |   link: "; rel=\"up via service\", ; rel=\"self current\", ; rel=\"item\""
 82 | }
 83 | string
 84 | ["bar","baz","blah"]
 85 | */
 86 | 
 87 | // local request 3
 88 | 
 89 | done = false;
 90 | startTime = Date.now();
 91 | var xhr = new XMLHttpRequest();
 92 | xhr.open('GET', 'local://localserver/foo');
 93 | xhr.onreadystatechange = function() {
 94 | 	print(xhr.readyState);
 95 | 	print(xhr.responseText);
 96 | 	if (xhr.readyState == 4) {
 97 | 		print('done');
 98 | 		print(xhr.getResponseHeader('content-type'));
 99 | 		print(xhr.getAllResponseHeaders());
100 | 		print(typeof xhr.response);
101 | 		print(xhr.response);
102 | 		done = true;
103 | 	}
104 | };
105 | xhr.responseType = 'json';
106 | xhr.send();
107 | wait(function () { return done; });
108 | 
109 | /* =>
110 | 2
111 | null
112 | 3
113 | [
114 | 3
115 | ["bar"
116 | 3
117 | ["bar","baz"
118 | 3
119 | ["bar","baz","blah"
120 | 3
121 | ["bar","baz","blah"]
122 | 4
123 | ["bar","baz","blah"]
124 | done
125 | application/json
126 | {
127 |   "content-type": "application/json",
128 |   link: "; rel=\"up via service\", ; rel=\"self current\", ; rel=\"item\""
129 | }
130 | object
131 | ["bar", "baz", "blah"]
132 | */
133 | 
134 | // local request 4
135 | 
136 | done = false;
137 | startTime = Date.now();
138 | var xhr = new XMLHttpRequest();
139 | xhr.open('POST', 'local://localserver/foo');
140 | xhr.onreadystatechange = function() {
141 | 	print(xhr.readyState);
142 | 	print(xhr.responseText);
143 | 	if (xhr.readyState == 4) {
144 | 		print('done');
145 | 		print(xhr.getResponseHeader('content-type'));
146 | 		print(xhr.getAllResponseHeaders());
147 | 		print(typeof xhr.response);
148 | 		print(xhr.response);
149 | 		done = true;
150 | 	}
151 | };
152 | xhr.setRequestHeader('content-type', 'text/plain');
153 | xhr.send('hello, world');
154 | wait(function () { return done; });
155 | 
156 | /* =>
157 | 2
158 | null
159 | 3
160 | hello, world
161 | 4
162 | hello, world
163 | done
164 | text/plain
165 | {"content-type": "text/plain"}
166 | string
167 | hello, world
168 | */


--------------------------------------------------------------------------------