Skip to content
Browse files

0.3alpha5 release

  • Loading branch information...
1 parent 2e5691c commit 3eeb233562dc3032b082c2405e19dceb91b07412 Owen Barnes committed Mar 11, 2012
Showing with 1,775 additions and 636 deletions.
  1. +46 −0 HISTORY.md
  2. +1 −1 Makefile
  3. +20 −29 README.md
  4. +4 −4 TODO.md
  5. +4 −2 bin/socketstream
  6. +33 −0 check_version.js
  7. +70 −0 doc/guide/en/client_side_code.md
  8. +110 −0 doc/guide/en/defining_multiple_clients.md
  9. +5 −0 doc/guide/en/live_reload.md
  10. +34 −0 doc/guide/en/loading_assets_on_demand.md
  11. +2 −16 index.js
  12. +344 −0 lib/browser_client/browserify.js
  13. +31 −9 lib/browser_client/index.js
  14. +0 −98 lib/browser_client/init.js
  15. 0 src/browser_client/libs/event_emitter.js → lib/browser_client/system_modules/eventemitter2.js
  16. +72 −0 lib/browser_client/system_modules/socketstream.js
  17. +29 −19 lib/cli/generate.js
  18. +3 −3 lib/cli/index.js
  19. +21 −26 lib/client_asset_manager/asset.js
  20. +21 −15 lib/client_asset_manager/client.js
  21. +0 −13 lib/client_asset_manager/code_wrappers/module.js
  22. +0 −4 lib/client_asset_manager/code_wrappers/safety.js
  23. +25 −16 lib/client_asset_manager/index.js
  24. +1 −0 lib/client_asset_manager/magic_path.js
  25. +14 −5 lib/client_asset_manager/serve_live.js
  26. +1 −1 lib/request/middleware/internal.js
  27. +8 −4 lib/request/responders/events/client.js
  28. +6 −4 lib/request/responders/rpc/client.js
  29. +30 −19 lib/utils/copy.js
  30. +5 −5 lib/websocket/transports/socketio/index.js
  31. +34 −30 lib/websocket/transports/socketio/wrapper.js
  32. +6 −6 new_project/app.js
  33. +47 −0 new_project/client/code/app/demo.coffee
  34. +57 −0 new_project/client/code/app/demo.js
  35. +18 −0 new_project/client/code/app/entry.coffee
  36. +24 −0 new_project/client/code/app/entry.js
  37. +0 −46 new_project/client/code/main/demo.coffee
  38. +0 −14 new_project/client/code/modules/message.coffee
  39. +1 −2 new_project/package.json
  40. +1 −1 new_project/server/middleware/example.coffee
  41. +12 −0 new_project/server/middleware/example.js
  42. +3 −1 new_project/server/rpc/demo.coffee
  43. +22 −0 new_project/server/rpc/demo.js
  44. +2 −2 package.json
  45. +344 −0 src/browser_client/browserify.js
  46. +28 −14 src/browser_client/index.coffee
  47. +0 −80 src/browser_client/init.coffee
  48. 0 lib/browser_client/libs/event_emitter.js → src/browser_client/system_modules/eventemitter2.js
  49. +60 −0 src/browser_client/system_modules/socketstream.coffee
  50. +37 −18 src/cli/generate.coffee
  51. +3 −3 src/cli/index.coffee
  52. +29 −28 src/client_asset_manager/asset.coffee
  53. +24 −15 src/client_asset_manager/client.coffee
  54. +0 −9 src/client_asset_manager/code_wrappers/module.coffee
  55. +0 −4 src/client_asset_manager/code_wrappers/safety.coffee
  56. +19 −18 src/client_asset_manager/index.coffee
  57. +1 −0 src/client_asset_manager/magic_path.coffee
  58. +11 −8 src/client_asset_manager/serve_live.coffee
  59. +1 −1 src/request/middleware/internal.coffee
  60. +6 −4 src/request/responders/events/client.coffee
  61. +5 −3 src/request/responders/rpc/client.coffee
  62. +25 −17 src/utils/copy.coffee
  63. +4 −10 src/websocket/transports/socketio/index.coffee
  64. +11 −9 src/websocket/transports/socketio/wrapper.coffee
View
46 HISTORY.md
@@ -1,3 +1,49 @@
+0.3 alpha5 / 2012-03-11
+=======================
+
+##### Major improvements to Client-side Code
+
+Please read new documentation on [Client-side Code](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/client_side_code.md) then create a new project to see the updated file structure. Also see the alpha5 announcement on Google Groups if you want a step-by-step migration guide.
+
+Key changes from previous releases:
+
+* All client code files **not** in a directory called 'libs` are now modules by default
+* You can now `require()` modules in the browser in exactly the same way as on the server (thanks to code from Browserify)
+* Where you currently called `require('mymod')` you will now need to add a leading slash: `require('/mymod')`
+* You can now use relative paths such as `require('../../mymod')` as you would on the server
+* `ss.loadAsync()` is now `ss.load.code()` but essentially works the same way. See new On Demand Loading doc
+* No more mandatory `SocketStream` and `ss` global variables...
+* SocketStream is now a system module - make it any global you want or type `var ss = require('socketstream')` at the top of each file if you prefer
+* `SocketStream.event.on` is now `ss.server.on`. Event names have not changed
+* The `SocketStream` global is no longer needed and has been removed
+* An `/entry.js` (or `.coffee`) module is now created by default and must always be present in your app as this is the new single point of entry
+* The `ss.client.wrapCode()` command and code wrappers concept are now redundant and the code has been removed
+
+Note: The next release will see further improvements to client-side code and a lot of internal refactoring / cleaning up. At this stage no more breaking changes to your client or server-side code are anticipated.
+
+
+##### New project installer
+
+* Now creates example code in JavaScript by default
+* Install example code in CoffeeScript by passing the `-c` option
+* Further enhancements planned here
+
+
+##### New documentation
+
+* [Client-side Code](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/client_side_code.md)
+* [Defining multiple Single-Page Clients](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/defining_multiple_clients.md)
+* [Loading Assets On Demand](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/loading_assets_on_demand.md)
+
+
+##### Other changes
+
+* We are no longer bundling `ss-console` in new apps by default
+* You can now disable Live Reload altogether with `ss.client.set({liveReload: false})` in your `app.js` file
+* Upgraded deps: Socket.IO 0.9 and Connect 2.0.2
+
+
+
0.3 alpha4 / 2012-02-23
=======================
View
2 Makefile
@@ -3,7 +3,7 @@
# Compile all CoffeeScript files within /src into /lib and transfer over any pure JS files
# TODO: Find a better way to move .js files over from /src to /lib, maybe using mkdir -p if the dirs don't already exist
build:
- rm -fr lib; node_modules/coffee-script/bin/coffee --bare -o lib -c src; cp src/*.js lib; cp src/utils/*.js lib/utils; cp src/websocket/transports/socketio/*.js lib/websocket/transports/socketio; cp -R src/browser_client/libs lib/browser_client;
+ rm -fr lib; node_modules/coffee-script/bin/coffee --bare -o lib -c src; cp src/*.js lib; cp src/utils/*.js lib/utils; cp src/websocket/transports/socketio/*.js lib/websocket/transports/socketio; cp -R src/browser_client/libs lib/browser_client; cp -R src/browser_client/*.js lib/browser_client; cp -R src/browser_client/system_modules/*.js lib/browser_client/system_modules;
# Ignore files and directories prepended with 'testdata_'
TEST_FILES=`find test/* | grep -v '^test/testdata_*'`
View
49 README.md
@@ -2,7 +2,7 @@
# SocketStream
-Latest release: 0.3.0alpha4 ([view changelog](https://github.com/socketstream/socketstream/blob/master/HISTORY.md))
+Latest release: 0.3.0alpha5 ([view changelog](https://github.com/socketstream/socketstream/blob/master/HISTORY.md))
[![build status](https://secure.travis-ci.org/socketstream/socketstream.png)](http://travis-ci.org/socketstream/socketstream)
Twitter: [@socketstream](http://twitter.com/#!/socketstream)
@@ -46,7 +46,7 @@ SocketStream is continually evolving but you can join a [growing community](http
* Easily share code between the client and server. Ideal for business logic and model validation (see Questions below)
* **NEW** Request Middleware - enabling session access, authentication, logging, distributed requests and more
* Effortless, scalable, pub/sub baked right in - including Private Channels
-* **NEW** Easy authentication - use a backend databases or authenticate against Facebook Connect, Twitter, etc using [Everyauth](https://github.com/bnoguchi/everyauth)
+* **NEW** Easy authentication - use a backend database or authenticate against Facebook Connect, Twitter, etc using [Everyauth](https://github.com/bnoguchi/everyauth)
* **NEW** Share sessions between HTTP and Websocket Requests using Connect Session Stores
* Optionally use [Redis](http://www.redis.io) for fast session retrieval, pub/sub, list of users online, and any other data your app needs instantly
* **NEW** Modular transport design allow alternative websocket or back-end event transports to be used
@@ -58,17 +58,17 @@ SocketStream is continually evolving but you can join a [growing community](http
* Works great with Chrome, Safari __and now Firefox 6__ using native websockets
* Compatible with older versions of Firefox and IE thanks to configurable fallback transports provided by Socket.IO
+* **NEW** Use `require()` and `exports` in your client-side code as you would on the server
* **NEW** Define multiple single-page clients by choosing the CSS, JS and client-side templates you wish to serve
* Integrated asset manager - automatically packages and [minifies](https://github.com/mishoo/UglifyJS) all client-side assets
-* **NEW** Live Reload - automatically reloads the browser when HTML, CSS or JS client files change
+* **NEW** Live Reload - automatically reloads the browser when an HTML, CSS or JS client file changes (in development)
* Works with iPads and iPhones using Mobile Safari (iOS 4.2 and above), even over 3G. Send a smaller, lighter client if desired
* **NEW** Use optional code formatters (e.g. CoffeeScript, Jade, Stylus, Less, etc) by easily installing wrapper modules
* Multiple clients work seamlessly with HTML Push State 'mock routing' so you can use [Backbone Router](http://documentcloud.github.com/backbone/#Router), [Davis JS](http://davisjs.com) and more
* **NEW** Supports many client-side template languages (Hogan/Mustache/CoffeeKup/jQuery), pre-compiling them for speed
* **NEW** Works great with [Ember.js](http://emberjs.com) for 'reactive templates' which automatically update when data changes
* Bundled with jQuery - though not dependent on it. Will work great with Zepto and other libraries
* Easily add additional client libraries such as [Underscore.js](http://documentcloud.github.com/underscore/)
-* Highly Experimental: Designate client-side code files as modules and require() them as you would server-side code
#### Optional Modules (officially maintained and supported)
@@ -80,7 +80,7 @@ SocketStream is continually evolving but you can join a [growing community](http
### How does it work?
-SocketStream automatically compresses and minifies all the static HTML, CSS and client-side code your app needs and sends this through the first time a user visits your site.
+SocketStream automatically compresses and minifies the static HTML, CSS and client-side code your app needs and sends this through the first time a user visits your site.
From then on all application data is sent and received via the websocket (or Socket.IO fallbacks), instantly established when the client connects and automatically re-established if broken. Normally this will be in [JSON RPC](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/rpc_responder.md) format, but SocketStream 0.3 allows you to use different message responders depending upon the task at hand.
@@ -114,13 +114,11 @@ If all goes well you'll see the SocketStream banner coming up, then you're ready
http://localhost:3000
-Note: We are aware there is a strange mix of JavaScript/CoffeeScript created at the moment. This will be fixed shortly. The plan is to create vanilla JS files by default with the ability to type `socketstream new -c <name_of_your_project>` if you prefer to use CoffeeScript.
-
### What can I create with it?
-SocketStream is a perfect fit for all manner of modern applications which require real-time data (chat, stock trading, location monitoring, analytics, etc). It's also a great platform for building real-time HTML5 games. However, right now it would make a poor choice for a blog or other content-rich site which requires unique URLs for search engine optimization.
+SocketStream is a perfect fit for all manner of modern applications which require realtime data (chat, stock trading, location monitoring, analytics, etc). It's also a great platform for building realtime HTML5 games. However, right now it would make a poor choice for a blog or other content-rich site which requires unique URLs for search engine optimization.
### Demo Apps
@@ -132,7 +130,7 @@ SocketStream 0.3 example apps coming soon!
SocketStream 0.3 supports multiple ways to send messages to and from the server. The most common of which, JSON-over-RPC, is included by default. An RPC call can be invoked on the server by calling `ss.rpc()` in the browser.
-For example, let's write a simple server-side function which squares a number. Make a file called `server/rpc/app.js` and put this in it:
+For example, let's write a simple server-side function which squares a number:
``` javascript
// server/rpc/app.js
@@ -157,25 +155,22 @@ ss.rpc('app.square', 25)
You'll see the answer `625` logged to the console by default. The eagle-eyed among you will notice `ss.rpc('app.square', 25)` actually returned `undefined`. That's fine. We're only interested in the asynchronous response sent from the server once it has processed your request.
-Now let's write some code in the client to do more with this response. Make a file called `client/code/main/app.js` and put this in it:
+You may be wondering why `app.square`? Because we're invoking the `square` action/function in the `app.js` file. If you had written a `resize` action in `/server/rpc/image/processor.js` you'd call it with `ss.rpc('image.processor.resize')`. Create as many sub-directories as you wish to organize your code.
+
+Now let's write some code in the client to do more with this response:
``` javascript
-// client/code/main/app.js
+// client/code/app/demo.js
// define the number to square (vars are local to this file by default)
var number = 25;
-// ensure SocketStream has connected to the server
-SocketStream.event.on('ready', function(){
-
- ss.rpc('app.square', number, function(response){
- alert(number + ' squared is ' + response);
- });
-
+ss.rpc('app.square', number, function(response){
+ alert(number + ' squared is ' + response);
});
```
-Refresh the page and you'll see an alert box popup with the following:
+Once you save the file, the browser will automatically reload and you'll see an alert box popup with the following:
25 squared is 625
@@ -186,11 +181,14 @@ More examples coming soon!
Please start with http://www.socketstream.org/tour which walks you through the key features and shows you the code.
-We've made a start on documentation for 0.3. Right now the following sections have been written in /doc/guide/en:
+We've made a start on documentation for 0.3. Right now the following sections have been written in `/doc/guide/en`:
##### Developing (Client-side)
+* [Client-side Code](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/client_side_code.md)
* [Client-side Templates](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/client_side_templates.md)
+* [Defining multiple Single-Page Clients](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/defining_multiple_clients.md)
+* [Loading Assets On Demand](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/loading_assets_on_demand.md)
* [Live Reload](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/live_reload.md)
##### Developing (Server-side)
@@ -235,9 +233,7 @@ Yes. We have several users running SocketStream on Windows without problems. Ple
##### How can I share code between client and server?
-After much thought we decided to abandon the app/shared directory concept in SocketStream 0.2. Thankfully now that we support client-side modules there is a cleaner alternative:
-
-Make the code you wish to share a module (by exporting functions and vars) and save it in `client/code/modules`. You can then `require()` the module in both your client-side and server-side code.
+Simply `require()` one of your client-side modules in your server-side code.
##### Does SocketStream support models?
@@ -268,11 +264,6 @@ Front end scaling can be achieved using a combination of different websocket tra
Back end scaling has yet to be properly documented, but we're keen to continue looking into ways to use ZeroMQ and also Hook IO. We will make sure back end scaling is as easy and flexible as possible, but it will no longer be a feature of the framework itself.
-##### Why not use Require.js, AMD or Browserify?
-
-Right now we're starting off with a much more lightweight solution (about 5 lines of code) which behaves identically to the require() command server-side (at least it will once we implement relative ./ ../ paths). It is not a perfect solution yet but it feels like the right direction to go in given SocketStream already takes care of all the bundling. We will fully document client-side module loading soon.
-
-
### Developing on the SocketStream core
SocketStream is primarily written in CoffeeScript which is 'pre-compiled' into JavaScript using `make build`. If you're actively developing on the code make sure you install the dev dependencies first (just clone the project and type `sudo npm link`).
@@ -281,7 +272,7 @@ To avoid having to continually type `make build` every time you make a change, p
$ SS_DEV=1 node app.js
-Your app will then directly read code from your local SocketStream repository's /src directory. This means when you make changes to the SocketStream core, you only need to restart your app to see them.
+This instructs your app to run the CoffeeScript code in `<socketstream_root>/src` directly, so you only need to restart the server to see your changes.
### Testing
View
8 TODO.md
@@ -3,10 +3,10 @@ TODO
#### WORK TO DO BEFORE 0.3.0 CAN BE RELEASED
-* Build interactive 'socketstream new' asking if you want to use our recommended stack + demo or a vanilla/minimal install
-* Much more error checking around sending bad RPC calls (e.g if a module or function does not exist)
-* Websocket responders need to take a config object. It should also be able to send config to client-side code (hard)
-* Relook at client-side code / modules. Make require() in client-code take relative paths (./ and ../) like Node's require() can
+* Serious refactoring of internal client_asset_manager code now we know what we're doing here
+* Make 'socketstream new' install recommended stack of optional modules by default, but add a minimal install option
+* More error checking around sending bad RPC calls (e.g if a module or function does not exist)
+* Websocket responders need to take a config object. It should also be able to send config to client-side code (
* Look into using Engine.IO instead of Socket.IO
* SocketStream should pass its version number and other meta info to wrapper modules
* Look into ways we can use multi-core cluster features of Node 0.6, if at all (maybe best at app level?)
View
6 bin/socketstream
@@ -4,8 +4,10 @@ var ss = require('../')
, program = require('commander');
program
+ .usage('new <name_of_your_project>')
.version(ss.version)
- //.option('-c, --coffee', 'use CoffeeScript') # coming soon!
+ .option('-j, --javascript', 'use Javascript (default)')
+ .option('-c, --coffee', 'use CoffeeScript')
.parse(process.argv);
-require(__dirname + '/../lib/cli').process(program.args);
+require(__dirname + '/../lib/cli').process(program);
View
33 check_version.js
@@ -0,0 +1,33 @@
+// Temporary code to update existing 0.3 alpha projects
+// REMOVE_BEFORE_0.3.0
+
+require('colors');
+
+var pathlib = require('path')
+ , bar = "\n\n*************************************************************************\n\n".cyan;
+
+// Upgrade project to new directory format introduced in 0.3 alpha3
+if (pathlib.existsSync('server/rpc/middleware')) {
+ console.log(bar +
+ "Thanks for upgrading to the latest SocketStream 0.3 alpha.\nWe've decided to move the following directories\n\n" +
+ " /server/rpc/middleware to /server/middleware\n\n" +
+ " /server/rpc/actions to /server/rpc\n\n" +
+ "so that websocket middleware can be used by other websocket responders (including forthcoming models).\n" +
+ "Please make this change to your existing project now then restart the server. Pasting this line into a UNIX-based shell should do the trick:\n\n" +
+ " mv server/rpc/middleware server && mv server/rpc/actions/* server/rpc/ && rm -fr server/rpc/actions\n" + bar);
+ throw new Error("Please paste the line above into the shell then restart the server");
+}
+
+// Delete 'console.js' file if it exists (no longer needed from 0.3 alpha4)
+if (pathlib.existsSync('console.js')) require('fs').unlinkSync('console.js');
+
+// Notify of changes to client code when upgrading to 0.3 alpha5
+if (pathlib.existsSync('client/code/modules')) {
+ console.log(bar +
+ "Thanks for upgrading to the latest SocketStream 0.3 alpha.\n\n" +
+ "We've made some major improvements to client-side code which will require\na few changes on your part. Please read:\n\n" +
+ "https://github.com/socketstream/socketstream/blob/master/doc/guide/en/client_side_code.md\n\n".yellow +
+ "then generate a new project to see the new format.\n\n" +
+ "This message will go away when /client/code/modules has been renamed to\nsomething else (we suggest 'app') so we know you've upgraded your code" + bar);
+ throw new Error("Please update your /client/code with the latest changes (see above)");
+}
View
70 doc/guide/en/client_side_code.md
@@ -0,0 +1,70 @@
+# Client-Side Code
+
+SocketStream allows you to write and structure client-side Javascript in exactly the same way as server-side code, allowing you to easily share modules between both.
+
+
+### How to use Modules
+
+All files which aren't `libs` (see below) are treated as modules. You have exactly the same ability to export functions, `require()` other modules, and cache values within modules as you do when writing server-side code in Node.js.
+
+Client-side code lives in `/client/code`. Create as many subdirectories as you wish. Reference your modules relatively, e.g. `require('../../image/processor')`, or absolutely `require('/path/to/my/module.js')`. It all work as you would expect, just bear in mind a module will never be executed unless it is explicitly `require()`'d.
+
+Top tip: Type `require.modules` in the browser console to see a list of all modules you can `require()` in your app
+
+
+### Special Exceptions
+
+While we try to keep the experience between browser and server as similar as possible, there are a few special cases to be aware of:
+
+
+#### 'libs' - Legacy (non Common JS) Libraries
+
+Any file which lives in a directory called 'libs' will NOT be served as a module. Instead these files will be sent as-is without any modification. Typically you'll want to ensure jQuery and other libraries which use the `window` variable are always placed in a `/client/code` directory called 'libs'.
+
+As load order is critically important for non Common JS libraries **either** name your files alphanumerically within the `libs` directory **or** list each file explicitly in your `ss.client.define()` command - your choice. Directories called 'libs' may not contain sub-directories, though you may serve multiple 'libs' directories to the same client in any order you choose.
+
+
+#### 'system' - System Modules
+
+System modules are similar to regular modules but with one important difference: they are accessed without a leading slash - just like you would `require('url')` or `require('querystring')` in Node.js.
+
+So why do we need this distinction? Because some libraries such as Backbone.js (when used as a module, rather than in a 'libs' directory) depend upon other system modules. In this case Backbone calls `require('underscore')` internally, therefore both `backbone.js` and `underscore.js` must live in a `system` directory.
+
+As SocketStream uses code from [Browserify](https://github.com/substack/node-browserify), the 'system' directory also allows you to use one of Node's inbuilt modules in the browser. Just head over to https://github.com/substack/node-browserify/tree/master/builtins and copy the libraries you need into any directory within `/client/code` called `system`.
+
+Note that, as with 'libs', 'system' directories may not contain sub-directories, though you may serve multiple 'system' directories to the same client in any order you choose (just watch out for conflicting module names!).
+
+
+#### '/entry.js' - A single point of entry
+
+The `/entry` module is a regular module with a special distinction: it is the only module to be required automatically once all files have been sent to the browser.
+
+The `entry.js` (or `entry.coffee`) file is created for you by default when you make a new project. It contains a small amount of boiler-plate code which you may modify to handle the websocket connection going down, reconnecting, and (critically), what module to `require()` next once the websocket connection is established.
+
+
+### Should I put library X in 'libs' or 'system'?
+
+It depends if it needs access to the `window` variable. For example, Backbone.js works great as a `system` module unless you're using Backbone.history as this requires access to `window`.
+
+
+### What happens if I try to require a module which doesn't exist?
+
+You'll see an error in the browser's console. In the future SocketStream will be able to catch these problems before they arise.
+
+
+### Loading modules on demand
+
+You don't necessarily have to send all modules to the browser at once, you can also [load them on demand](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/loading_assets_on_demand.md).
+
+
+### Background info
+
+Getting client-code right was a major goal for SocketStream from the beginning.
+
+For too long web developers have had to wade through a mess of unstructured JavaScript files without anyway to manage namespacing or dependencies.
+
+Solutions such as [Require.js](http://requirejs.org) and other AMD approaches have successfully brought order to chaos, but put the onus on the developer to manually track and list dependencies. What's more, they use a different syntax to `require()` files - instantly killing all hopes of sharing the same file between the client and server.
+
+We wanted to do much better with SocketStream. After all, we are in the unique position of managing both the client and server stack. The solution came in the form of [Browserify](https://github.com/substack/node-browserify) - an awesome, lightweight, library which solves all these problems once and for all.
+
+SocketStream doesn't depend upon the Browserify module (as it contains code we don't need), but we use major components from it (including the critical code which performs all the `require()` magic). Our thanks go to Substack for coming up with a clean solution to a very tricky problem.
View
110 doc/guide/en/defining_multiple_clients.md
@@ -0,0 +1,110 @@
+# Defining Multiple Single-Page Clients
+
+SocketStream is exclusively a single-page framework; however we make it easy to define and send multiple single-page clients to different devices (e.g. desktop browsers and iPhones), or serve them on different URLs (useful for providing an /admin interface).
+
+New clients are defined with the `ss.client.define()` function in your `app.js` file. You may define as many as you like, just be sure to give each one a unique name (passed to the first argument).
+
+
+### Default settings
+
+A single-page client consist of one HTML view and multiple CSS, client-side code and template files.
+
+When you create a new SocketStream project we define a `main` client for you with the following options:
+
+```javascript
+// in app.js
+ss.client.define('main', {
+ view: 'app.jade',
+ css: ['libs', 'app.styl'],
+ code: ['libs', 'app'],
+ tmpl: '*'
+});
+```
+
+For each type of asset you may provide one or more file names or directory names. When passing directory names, the contents of the directory (including any sub-directories) will be served alphanumerically.
+
+Let's look more closely at each type of asset:
+
+##### view
+Views are pages of HTML which provide the main layout and structure for your app. They are stored in `/client/views`. If you're using plain HTML instead of Jade you'll want to name this `app.html`. Due to the nature of a single-page application, you may only specify one view for each client you define.
+
+##### css
+CSS files live in `/client/css`. This option allows you to specify as many files or directories as you wish. As order is important why serving CSS, you'll need to make sure any CSS libraries (e.g. `reset.css`) are sent before your application stylesheets.
+
+Note: If you're using Stylus, you'll only need to specify the first `.styl` file here as any additional files can be imported using the `@import` command. If you're using plain CSS, you'll need to list each `.css` file individually or pass the name of a directory instead.
+
+##### code
+Client-side code lives in `/client/code`. This option allows you to specify as many files or directories as you wish to be sent to the client upon initial connection.
+
+Important: Modules are always mounted to the root (/) namespace, even if you specify nested directories. Hence if you specify `code: ['iphone/new_release']`, and have a module in here called `calendar.js` you will require this module as `require('/calendar')` in your app, not `require('/iphone/new_release/calendar')`.
+
+Not all code has to be sent upon initial connection - you may prefer to [load some modules asynchronously](https://github.com/socketstream/socketstream/blob/master/doc/guide/en/loading_assets_on_demand.md) into your application on demand.
+
+##### tmpl
+Client-side Templates live in `/client/templates`. This option allows you to specify multiple files or directories of templates to be sent to the client upon initial connection. Note the `*` means send everything.
+
+
+### Defining a new client
+
+For this example let's imagine we want to define a new single-page client to be sent to an iPhone:
+
+```javascript
+ss.client.define('iphone', {
+ view: 'iphone.jade',
+ css: ['libs', 'iphone.styl'],
+ code: ['libs', 'app'],
+ tmpl: 'iphone'
+});
+```
+
+Here we're not only specifying a different view (`/client/views/iphone.jade`) but we're also choosing to send different CSS and client-side templates, though in this example we've not changed the client-side code we're sending.
+
+
+### Serving different clients
+
+Once you've defined a single-page client you can choose to serve it on a particular URL:
+
+```javascript
+ss.http.router.on('/', function(req, res) {
+ res.serveClient('main');
+});
+
+ss.http.router.on('/iphone', function(req, res) {
+ res.serveClient('iphone');
+});
+```
+
+or depending upon the browser's UserAgent:
+
+```javascript
+ss.http.router.on('/', function(req, res) {
+ if (req.headers['user-agent'].match(/iPhone/))
+ res.serveClient('iphone');
+ else
+ res.serveClient('main');
+});
+```
+
+### Sharing common code
+
+Typically you'll want to divide all your assets into three sections: Those to be served to desktop clients, those for to be served to mobile clients, and those to be shared by both.
+
+Hence a typical desktop/iPhone config may look something like this:
+
+```javascript
+ss.client.define('desktop', {
+ view: 'desktop.jade',
+ css: ['common/libs', 'desktop/libs', 'desktop.styl'],
+ code: ['common/libs', 'desktop/libs', 'common/app', 'desktop/app'],
+ tmpl: ['common', 'desktop']
+});
+
+ss.client.define('iphone', {
+ view: 'iphone.jade',
+ css: ['common/libs', 'iphone/libs', 'iphone.styl'],
+ code: ['common/libs', 'iphone/libs', 'common/app', 'iphone/app'],
+ tmpl: ['common', 'iphone']
+});
+```
+
+As you can see, SocketStream gives you the flexibility and power to mix and match client-side assets for any possible combination of devices or pages without having to duplicate code.
View
5 doc/guide/en/live_reload.md
@@ -30,3 +30,8 @@ then run
If things still don't work as expected, please log an issue and be sure to mention which OS you're using.
+### Disabling
+
+To disable Live Reload, even in development mode, put the following in your `app.js` code:
+
+ ss.client.set({liveReload: false})
View
34 doc/guide/en/loading_assets_on_demand.md
@@ -0,0 +1,34 @@
+# Loading Assets On Demand
+
+If you're writing a small app you can safely ignore this section, as it's always better to pack all client assets into one file and send everything through together in one go if possible.
+
+But what if you're writing a large app, or an app with multiple distinct sections like iCloud.com?
+
+SocketStream allows you to load code (and other assets in the future) into your app asynchronously on demand.
+
+
+### Loading Code
+
+Sadly it's not possible to directly `require()` modules which haven't been loaded as the blocking nature of the `require` function means the browser would freeze until the module has been retrieved from the server - not good.
+
+However SocketStream allows you to load additional code modules from the server asynchronously using the built-in `ss.load.code()` command. Once all the code you've requested has been loaded, we execute a callback, allowing you to `require()` the new modules as normal without any fancy syntax.
+
+To try this out, create a new directory of application modules in `/client/code`. For the sake of this example, let's call our new directory `/client/code/mail`. We'll also assume this directory has a module in it called `search.js`.
+
+```javascript
+// in any client-side module
+ss.load.code('/mail', function(){
+
+ // all modules in /client/code/mail have now been loaded into
+ // the root namespace (/) and can be required in the normal way
+ var search = require('/search');
+
+});
+```
+
+Note: Regardless of the directory you load, the modules inside will always be loaded into the root (/) namespace by default. If you want to mount the new modules in a different namespace, just create one or more sub-directories in the folder you're loading.
+
+
+### Automatic Caching
+
+Modules are only ever retrieved from the server once. Subsequent requests for the same directory will be returned instantly without contacting the server.
View
18 index.js
@@ -11,22 +11,8 @@ if (process.env['SS_DEV']) {
dir = 'lib';
}
-// Temporary - REMOVE_BEFORE_0.3.0 - Upgrade project to new directory format introduced in 0.3 alpha3
-require('colors');
-var pathlib = require('path'), bar = "\n\n*************************************************************************\n\n".cyan;
-if (pathlib.existsSync('server/rpc/middleware')) {
- console.log(bar +
- "Thanks for upgrading to the latest SocketStream 0.3 alpha.\nWe've decided to move the following directories\n\n" +
- " /server/rpc/middleware to /server/middleware\n\n" +
- " /server/rpc/actions to /server/rpc\n\n" +
- "so that websocket middleware can be used by other websocket responders (including forthcoming models).\n" +
- "Please make this change to your existing project now then restart the server. Pasting this line into a UNIX-based shell should do the trick:\n\n" +
- " mv server/rpc/middleware server && mv server/rpc/actions/* server/rpc/ && rm -fr server/rpc/actions\n" + bar);
- throw new Error("Please paste the line above into the shell then restart the server");
-}
-
-// Temporary - REMOVE_BEFORE_0.3.0 - Delete 'console.js' file if it exists (no longer needed)
-if (pathlib.existsSync('console.js')) require('fs').unlinkSync('console.js');
+// Check to see if existing project must be upgraded
+require('./check_version.js');
// Load SocketStream core
module.exports = require('./' + dir + '/socketstream.js');
View
344 lib/browser_client/browserify.js
@@ -0,0 +1,344 @@
+// Module loading code from Browserify: https://github.com/substack/node-browserify
+
+window.require = function (file, cwd) {
+ var resolved = require.resolve(file, cwd || '/');
+ var mod = require.modules[resolved];
+ if (!mod) throw new Error(
+ 'Failed to resolve module ' + file + ', tried ' + resolved
+ );
+ var res = mod._cached ? mod._cached : mod();
+ return res;
+}
+
+require.paths = [];
+require.modules = {};
+require.extensions = [".js",".coffee"];
+
+require._core = {
+ 'assert': true,
+ 'events': true,
+ 'fs': true,
+ 'path': true,
+ 'vm': true
+};
+
+require.resolve = (function () {
+ return function (x, cwd) {
+ if (!cwd) cwd = '/';
+
+ if (require._core[x]) return x;
+ var path = require.modules.path();
+ cwd = path.resolve('/', cwd);
+ var y = cwd || '/';
+
+ if (x.match(/^(?:\.\.?\/|\/)/)) {
+ var m = loadAsFileSync(path.resolve(y, x))
+ || loadAsDirectorySync(path.resolve(y, x));
+ if (m) return m;
+ }
+
+ var n = loadNodeModulesSync(x, y);
+ if (n) return n;
+
+ throw new Error("Cannot find module '" + x + "'");
+
+ function loadAsFileSync (x) {
+ if (require.modules[x]) {
+ return x;
+ }
+
+ for (var i = 0; i < require.extensions.length; i++) {
+ var ext = require.extensions[i];
+ if (require.modules[x + ext]) return x + ext;
+ }
+ }
+
+ function loadAsDirectorySync (x) {
+ x = x.replace(/\/+$/, '');
+ var pkgfile = x + '/package.json';
+ if (require.modules[pkgfile]) {
+ var pkg = require.modules[pkgfile]();
+ var b = pkg.browserify;
+ if (typeof b === 'object' && b.main) {
+ var m = loadAsFileSync(path.resolve(x, b.main));
+ if (m) return m;
+ }
+ else if (typeof b === 'string') {
+ var m = loadAsFileSync(path.resolve(x, b));
+ if (m) return m;
+ }
+ else if (pkg.main) {
+ var m = loadAsFileSync(path.resolve(x, pkg.main));
+ if (m) return m;
+ }
+ }
+
+ return loadAsFileSync(x + '/index');
+ }
+
+ function loadNodeModulesSync (x, start) {
+ var dirs = nodeModulesPathsSync(start);
+ for (var i = 0; i < dirs.length; i++) {
+ var dir = dirs[i];
+ var m = loadAsFileSync(dir + '/' + x);
+ if (m) return m;
+ var n = loadAsDirectorySync(dir + '/' + x);
+ if (n) return n;
+ }
+
+ var m = loadAsFileSync(x);
+ if (m) return m;
+ }
+
+ function nodeModulesPathsSync (start) {
+ var parts;
+ if (start === '/') parts = [ '' ];
+ else parts = path.normalize(start).split('/');
+
+ var dirs = [];
+ for (var i = parts.length - 1; i >= 0; i--) {
+ if (parts[i] === 'node_modules') continue;
+ var dir = parts.slice(0, i + 1).join('/') + '/node_modules';
+ dirs.push(dir);
+ }
+
+ return dirs;
+ }
+ };
+})();
+
+require.alias = function (from, to) {
+ var path = require.modules.path();
+ var res = null;
+ try {
+ res = require.resolve(from + '/package.json', '/');
+ }
+ catch (err) {
+ res = require.resolve(from, '/');
+ }
+ var basedir = path.dirname(res);
+
+ var keys = (Object.keys || function (obj) {
+ var res = [];
+ for (var key in obj) res.push(key)
+ return res;
+ })(require.modules);
+
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ if (key.slice(0, basedir.length + 1) === basedir + '/') {
+ var f = key.slice(basedir.length);
+ require.modules[to + f] = require.modules[basedir + f];
+ }
+ else if (key === basedir) {
+ require.modules[to] = require.modules[basedir];
+ }
+ }
+};
+
+require.define = function (filename, fn) {
+ var dirname = require._core[filename]
+ ? ''
+ : require.modules.path().dirname(filename)
+ ;
+
+ var require_ = function (file) {
+ return require(file, dirname)
+ };
+ require_.resolve = function (name) {
+ return require.resolve(name, dirname);
+ };
+ require_.modules = require.modules;
+ require_.define = require.define;
+ var module_ = { exports : {} };
+
+ require.modules[filename] = function () {
+ require.modules[filename]._cached = module_.exports;
+ fn.call(
+ module_.exports,
+ require_,
+ module_,
+ module_.exports,
+ dirname,
+ filename
+ );
+ require.modules[filename]._cached = module_.exports;
+ return module_.exports;
+ };
+};
+
+if (typeof process === 'undefined') process = {};
+
+if (!process.nextTick) process.nextTick = (function () {
+ var queue = [];
+ var canPost = typeof window !== 'undefined'
+ && window.postMessage && window.addEventListener
+ ;
+
+ if (canPost) {
+ window.addEventListener('message', function (ev) {
+ if (ev.source === window && ev.data === 'browserify-tick') {
+ ev.stopPropagation();
+ if (queue.length > 0) {
+ var fn = queue.shift();
+ fn();
+ }
+ }
+ }, true);
+ }
+
+ return function (fn) {
+ if (canPost) {
+ queue.push(fn);
+ window.postMessage('browserify-tick', '*');
+ }
+ else setTimeout(fn, 0);
+ };
+})();
+
+if (!process.title) process.title = 'browser';
+
+if (!process.binding) process.binding = function (name) {
+ if (name === 'evals') return require('vm')
+ else throw new Error('No such module')
+};
+
+if (!process.cwd) process.cwd = function () { return '.' };
+
+require.define("path", function (require, module, exports, __dirname, __filename) {
+function filter (xs, fn) {
+ var res = [];
+ for (var i = 0; i < xs.length; i++) {
+ if (fn(xs[i], i, xs)) res.push(xs[i]);
+ }
+ return res;
+}
+
+// resolves . and .. elements in a path array with directory names there
+// must be no slashes, empty elements, or device names (c:\) in the array
+// (so also no leading and trailing slashes - it does not distinguish
+// relative and absolute paths)
+function normalizeArray(parts, allowAboveRoot) {
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = parts.length; i >= 0; i--) {
+ var last = parts[i];
+ if (last == '.') {
+ parts.splice(i, 1);
+ } else if (last === '..') {
+ parts.splice(i, 1);
+ up++;
+ } else if (up) {
+ parts.splice(i, 1);
+ up--;
+ }
+ }
+
+ // if the path is allowed to go above the root, restore leading ..s
+ if (allowAboveRoot) {
+ for (; up--; up) {
+ parts.unshift('..');
+ }
+ }
+
+ return parts;
+}
+
+// Regex to split a filename into [*, dir, basename, ext]
+// posix version
+var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;
+
+// path.resolve([from ...], to)
+// posix version
+exports.resolve = function() {
+var resolvedPath = '',
+ resolvedAbsolute = false;
+
+for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) {
+ var path = (i >= 0)
+ ? arguments[i]
+ : process.cwd();
+
+ // Skip empty and invalid entries
+ if (typeof path !== 'string' || !path) {
+ continue;
+ }
+
+ resolvedPath = path + '/' + resolvedPath;
+ resolvedAbsolute = path.charAt(0) === '/';
+}
+
+// At this point the path should be resolved to a full absolute path, but
+// handle relative paths to be safe (might happen when process.cwd() fails)
+
+// Normalize the path
+resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+ return !!p;
+ }), !resolvedAbsolute).join('/');
+
+ return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+};
+
+// path.normalize(path)
+// posix version
+exports.normalize = function(path) {
+var isAbsolute = path.charAt(0) === '/',
+ trailingSlash = path.slice(-1) === '/';
+
+// Normalize the path
+path = normalizeArray(filter(path.split('/'), function(p) {
+ return !!p;
+ }), !isAbsolute).join('/');
+
+ if (!path && !isAbsolute) {
+ path = '.';
+ }
+ if (path && trailingSlash) {
+ path += '/';
+ }
+
+ return (isAbsolute ? '/' : '') + path;
+};
+
+
+// posix version
+exports.join = function() {
+ var paths = Array.prototype.slice.call(arguments, 0);
+ return exports.normalize(filter(paths, function(p, index) {
+ return p && typeof p === 'string';
+ }).join('/'));
+};
+
+
+exports.dirname = function(path) {
+ var dir = splitPathRe.exec(path)[1] || '';
+ var isWindows = false;
+ if (!dir) {
+ // No dirname
+ return '.';
+ } else if (dir.length === 1 ||
+ (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) {
+ // It is just a slash or a drive letter with a slash
+ return dir;
+ } else {
+ // It is a full dirname, strip trailing slash
+ return dir.substring(0, dir.length - 1);
+ }
+};
+
+
+exports.basename = function(path, ext) {
+ var f = splitPathRe.exec(path)[2] || '';
+ // TODO: make this comparison case-insensitive on windows?
+ if (ext && f.substr(-1 * ext.length) === ext) {
+ f = f.substr(0, f.length - ext.length);
+ }
+ return f;
+};
+
+
+exports.extname = function(path) {
+ return splitPathRe.exec(path)[3] || '';
+};
+
+});
View
40 lib/browser_client/index.js
@@ -1,7 +1,9 @@
-var coffee, fs;
+var coffee, fs, fsUtils;
fs = require('fs');
+fsUtils = require('../utils/file');
+
if (process.env['SS_DEV']) coffee = require('coffee-script');
exports.init = function(transport, responders) {
@@ -13,20 +15,40 @@ exports.init = function(transport, responders) {
return cb(output.join('\n'));
},
code: function(cb) {
- var ext, input, name, output, responder;
+ var code, modDir, name, output, responder, systemMods;
output = [];
- ['json.min.js', 'console_log.min.js', 'event_emitter.js'].forEach(function(file) {
+ ['json.min.js', 'console_log.min.js'].forEach(function(file) {
return output.push(fs.readFileSync(__dirname + '/libs/' + file, 'utf8'));
});
- ext = (coffee != null) && 'coffee' || 'js';
- input = fs.readFileSync(__dirname + '/init.' + ext, 'utf8');
- output.push((coffee != null) && coffee.compile(input) || input);
- if (transport.client().code != null) output.push(transport.client().code());
+ output.push(fs.readFileSync(__dirname + '/browserify.js', 'utf8'));
+ systemMods = {};
+ modDir = __dirname + '/system_modules';
+ fsUtils.readDirSync(modDir).files.forEach(function(mod) {
+ var code, ext, input, sp;
+ input = fs.readFileSync(mod, 'utf8');
+ sp = mod.split('.');
+ ext = sp[sp.length - 1];
+ code = ext === 'coffee' && (coffee != null) && coffee.compile(input) || input;
+ return systemMods[mod.substr(modDir.length + 1)] = code;
+ });
+ if (transport.client().libs != null) {
+ output.push(transport.client().libs() + "\n");
+ }
+ if (transport.client().code != null) {
+ systemMods['socketstream-transport'] = transport.client().code();
+ }
+ for (name in responders) {
+ responder = responders[name];
+ systemMods['socketstream-' + name] = responder.client.code();
+ }
+ for (name in systemMods) {
+ code = systemMods[name];
+ output.push("require.define(\"" + name + "\", function (require, module, exports, __dirname, __filename){\n" + code + " \n});");
+ }
for (name in responders) {
responder = responders[name];
- output.push(responder.client.code());
+ output.push("require('socketstream-" + name + "');");
}
- output.push("window.ss = window.SocketStream.apis;\nSocketStream.transport.connect();\n");
return cb(output.join("\n"));
}
};
View
98 lib/browser_client/init.js
@@ -1,98 +0,0 @@
-var async, moduleCache;
-
-window.SocketStream = {
- modules: {},
- apis: {},
- transport: null,
- event: new EventEmitter2(),
- message: new EventEmitter2()
-};
-
-SocketStream.registerApi = function(name, fn) {
- var api;
- api = SocketStream.apis[name];
- if (api) {
- return console.error("SocketStream Error: Unable to register the 'ss." + name + "' responder as this name has already been taken");
- } else {
- return SocketStream.apis[name] = fn;
- }
-};
-
-moduleCache = {};
-
-SocketStream.require = function(name, currentPath) {
- var cache, exports, mod, req;
- if (currentPath == null) currentPath = null;
- if (cache = moduleCache[name]) return cache;
- if (mod = SocketStream.modules[name]) {
- exports = {};
- req = function(name) {
- return SocketStream.require(name, mod.path);
- };
- mod.mod(exports, req);
- return moduleCache[name] = exports;
- } else {
- return console.error("SocketStream Error: Module " + name + " not found. Ensure client dirs containing modules are loaded first and that calls from one module to another are nested within functions");
- }
-};
-
-async = {
- loaded: {},
- loading: new EventEmitter2()
-};
-
-SocketStream.loadAsync = function(nameOrDir, cb) {
- var onError, onSuccess;
- if (!jQuery) {
- return console.error('SocketStream Error: loadAsync() command requires jQuery to present');
- }
- if (async.loaded[nameOrDir]) return cb();
- async.loading.once(nameOrDir, cb);
- if (async.loading.listeners(nameOrDir).length === 1) {
- onError = function() {
- console.error('SocketStream Error: Could not asynchronously load ' + nameOrDir);
- return console.log(arguments);
- };
- onSuccess = function() {
- async.loaded[nameOrDir] = true;
- return async.loading.emit(nameOrDir);
- };
- return $.ajax({
- url: "/_serveAsync/code?" + nameOrDir,
- type: 'GET',
- cache: false,
- dataType: 'script',
- success: onSuccess,
- error: onError
- });
- }
-};
-
-SocketStream.cookie = {
- read: function(c_name) {
- var c_end, c_start;
- if (document.cookie.length > 0) {
- c_start = document.cookie.indexOf(c_name + "=");
- if (c_start !== -1) {
- c_start = c_start + c_name.length + 1;
- c_end = document.cookie.indexOf(";", c_start);
- if (c_end === -1) c_end = document.cookie.length;
- return unescape(document.cookie.substring(c_start, c_end));
- }
- }
- return '';
- },
- write: function(c_name, value, expiredays) {
- var c, exdate;
- if (expiredays == null) expiredays = null;
- exdate = new Date();
- exdate.setDate(exdate.getDate() + expiredays);
- c = "" + c_name + "=" + (escape(value));
- return document.cookie = ("" + c_name + "=" + (escape(value))) + (expiredays === null ? "" : ";expires=" + exdate.toUTCString());
- }
-};
-
-SocketStream.event.on('__ss:reload', function() {
- console.log('Reloading as files have changed...');
- return window.location.reload();
-});
View
0 src/browser_client/libs/event_emitter.js → ...er_client/system_modules/eventemitter2.js
File renamed without changes.
View
72 lib/browser_client/system_modules/socketstream.js
@@ -0,0 +1,72 @@
+var EventEmitter2, async, message, server, transport;
+
+EventEmitter2 = require('eventemitter2').EventEmitter2;
+
+transport = require('socketstream-transport');
+
+server = exports.server = new EventEmitter2;
+
+message = exports.message = new EventEmitter2;
+
+exports.registerApi = function(name, fn) {
+ var api;
+ api = exports[name];
+ if (api) {
+ return console.error("SocketStream Error: Unable to register the 'ss." + name + "' responder as this name has already been taken");
+ } else {
+ return exports[name] = fn;
+ }
+};
+
+exports.connect = function(fn) {
+ return exports.send = transport(server, message).connect();
+};
+
+async = {
+ loaded: {},
+ loading: new EventEmitter2
+};
+
+exports.load = {
+ code: function(nameOrDir, cb) {
+ var errorPrefix, onError, onSuccess;
+ if (nameOrDir && nameOrDir.substr(0, 1) === '/') {
+ nameOrDir = nameOrDir.substr(1);
+ }
+ errorPrefix = 'SocketStream Error: The ss.load.code() command ';
+ if (!jQuery) {
+ return console.error(errorPrefix + 'requires jQuery to be present');
+ }
+ if (!nameOrDir) {
+ return console.error(errorPrefix + 'requires a directory to load. Specify it as the first argument. E.g. The ss.load.code(\'/mail\',cb) will load code in /client/code/mail');
+ }
+ if (!cb) {
+ return console.error(errorPrefix + 'requires a callback. Specify it as the last argument');
+ }
+ if (async.loaded[nameOrDir]) return cb();
+ async.loading.once(nameOrDir, cb);
+ if (async.loading.listeners(nameOrDir).length === 1) {
+ onError = function() {
+ console.error('SocketStream Error: Could not asynchronously load ' + nameOrDir);
+ return console.log(arguments);
+ };
+ onSuccess = function() {
+ async.loaded[nameOrDir] = true;
+ return async.loading.emit(nameOrDir);
+ };
+ return $.ajax({
+ url: "/_serveAsync/code?" + nameOrDir,
+ type: 'GET',
+ cache: false,
+ dataType: 'script',
+ success: onSuccess,
+ error: onError
+ });
+ }
+ }
+};
+
+server.on('__ss:reload', function() {
+ console.log('Reloading as files have changed...');
+ return window.location.reload();
+});
View
48 lib/cli/generate.js
@@ -1,31 +1,51 @@
-var copy, dir_mode, fs, log, makeRootDirectory, showFinishText, util;
-
-require('colors');
+var copy, dir_mode, fs, log, makeRootDirectory, path, util;
log = console.log;
+require('colors');
+
fs = require('fs');
+path = require('path');
+
util = require('util');
copy = require('../utils/copy');
dir_mode = 0755;
-exports.generate = function(name) {
- var source;
+exports.generate = function(program) {
+ var copyOptions, name, source;
+ name = program.args[1];
if (name === void 0) {
return console.log("Please provide a name for your application: $> socketstream new <MyAppName>");
}
if (makeRootDirectory(name)) {
- source = __dirname + '/../../new_project';
- copy.recursiveCopy(source, name);
- return showFinishText(name);
+ source = path.join(__dirname, '/../../new_project');
+ copyOptions = {
+ exclude: {
+ inPaths: ['/client/code/app', '/server/middleware', '/server/rpc'],
+ extensions: [program.coffee && '.js' || '.coffee']
+ }
+ };
+ copy.recursiveCopy(source, name, copyOptions);
+ log(("Success! Created app '" + name + "' with:").yellow);
+ log("".green, "Our recommended stack of optional modules", "(minimal install option coming soon)".grey);
+ if (program.coffee) {
+ log("".green, "CoffeeScript example code", "(-j if you prefer Javascript)".grey);
+ } else {
+ log("".green, "Javascript example code", "(-c if you prefer CoffeeScript)".grey);
+ }
+ log("Next, run the following commands:".yellow);
+ log(" cd " + name);
+ log(" sudo npm install");
+ log(" npm link socketstream", " (just until 0.3 is published to npm)".grey);
+ log("To start your app:".yellow);
+ return log(" node app.js");
}
};
makeRootDirectory = function(name) {
- log("Creating a new SocketStream app called " + name);
try {
fs.mkdirSync(name, dir_mode);
return true;
@@ -38,13 +58,3 @@ makeRootDirectory = function(name) {
}
}
};
-
-showFinishText = function(name) {
- log(("Success! Created app " + name + ". You can now run the app:").green);
- log(" cd " + name);
- log(" sudo npm install");
- log(" npm link socketstream (until 0.3 is published to npm)");
- log(" node app.js");
- log("Note: You're about to install our full recommended stack of optional modules".grey);
- return log("Feel free to remove any you don't need. A 'minimal install' option is coming soon".grey);
-};
View
6 lib/cli/index.js
@@ -1,9 +1,9 @@
-exports.process = function(args) {
- switch (args[0]) {
+exports.process = function(program) {
+ switch (program.args[0]) {
case 'new':
case 'n':
- return require('./generate').generate(args[1]);
+ return require('./generate').generate(program);
default:
return console.log('Type "socketstream new <projectname>" to create a new application');
}
View
47 lib/client_asset_manager/asset.js
@@ -1,4 +1,4 @@
-var fs, loadFile, log, minifyJS, pathlib, uglifyjs, wrapCode;
+var fs, loadFile, log, minifyJS, pathlib, uglifyjs, wrapCode, wrapModule;
log = console.log;
@@ -8,14 +8,15 @@ uglifyjs = require('uglify-js');
pathlib = require('path');
-exports.init = function(root, formatters, codeWrappers) {
+exports.init = function(root, formatters) {
return {
js: function(path, options, cb) {
var fullPath;
fullPath = pathlib.join(root, 'client/code', path);
return loadFile(fullPath, formatters, options, function(output) {
var basename;
- output = wrapCode(output, path, codeWrappers);
+ if (options.raw === true) return cb(output);
+ output = wrapCode(output, path, options.pathPrefix);
if (options && options.compress) {
basename = pathlib.basename(path);
if (!(basename.indexOf('.min') > 0)) output = minifyJS(basename, output);
@@ -58,29 +59,23 @@ minifyJS = function(file_name, orig_code) {
return minified;
};
-wrapCode = function(code, path, codeWrappers) {
- var getWrapper, pathAry;
+wrapCode = function(code, path, pathPrefix) {
+ var dirName, modPath, pathAry;
pathAry = path.split('/');
- getWrapper = function(cb) {
- var codePath, wrapper;
- pathAry.pop();
- codePath = pathAry.join('/');
- wrapper = codeWrappers[codePath];
- if (wrapper === void 0 && pathAry.length > 1) {
- return getWrapper(cb);
- } else {
- return cb(wrapper);
- }
- };
- return getWrapper(function(wrapper) {
- if (wrapper === void 0) wrapper = 'safety';
- if (wrapper) {
- if (typeof wrapper === 'string') {
- wrapper = require('./code_wrappers/' + wrapper);
- }
- return wrapper.process(code, path);
- } else {
+ dirName = pathAry[pathAry.length - 2];
+ switch (dirName) {
+ case 'libs':
return code;
- }
- });
+ case 'system':
+ modPath = pathAry[pathAry.length - 1];
+ return wrapModule(modPath, code);
+ default:
+ modPath = pathAry.slice(1).join('/');
+ if (pathPrefix) modPath = path.substr(pathPrefix.length + 1);
+ return wrapModule('/' + modPath, code);
+ }
+};
+
+wrapModule = function(modPath, code) {
+ return "require.define(\"" + modPath + "\", function (require, module, exports, __dirname, __filename){\n" + code + "\n});";
};
View
36 lib/client_asset_manager/client.js
@@ -8,7 +8,7 @@ pathlib = require('path');
magicPath = require('./magic_path');
-exports.init = function(root, codeWrappers, templateEngine) {
+exports.init = function(root, templateEngine, initAppCode) {
var Client, containerDir, templateDir;
containerDir = pathlib.join(root, 'client/static/assets');
templateDir = 'client/templates';
@@ -41,10 +41,11 @@ exports.init = function(root, codeWrappers, templateEngine) {
if ((_ref2 = this.paths.code) != null) {
_ref2.forEach(function(path) {
return magicPath.files(root + '/client/code', path).forEach(function(file) {
- return headers.push(tag.js("/_serveDev/code/" + file + "?ts=" + ts));
+ return headers.push(tag.js("/_serveDev/code/" + file + "?ts=" + ts + "&pathPrefix=" + path));
});
});
}
+ headers.push(tag.js("/_serveDev/start?ts=" + ts));
}
return headers;
};
@@ -89,7 +90,7 @@ exports.init = function(root, codeWrappers, templateEngine) {
var files;
includes.push(codeForView);
includes = includes.concat(_this.headers(packAssets));
- paths.tmpl !== false && (files = magicPath.files(pathlib.join(root, templateDir).replace(/\\/g, '/'), paths.tmpl));
+ paths.tmpl !== false && (files = magicPath.files(pathlib.join(root, templateDir)));
if (files && files.length > 0) {
return templateEngine.generate(root, templateDir, files, formatters, function(templateHTML) {
includes.push(templateHTML);
@@ -105,16 +106,18 @@ exports.init = function(root, codeWrappers, templateEngine) {
Client.prototype.pack = function(ssClient, formatters, options) {
var asset, clientDir, filesDeleted, id, numFilesDeleted, packAssetSet,
_this = this;
- asset = require('./asset').init(root, formatters, codeWrappers);
- packAssetSet = function(assetType, paths, dir, concatinator, initialCode) {
+ asset = require('./asset').init(root, formatters);
+ packAssetSet = function(assetType, paths, dir, concatinator, initialCode, endCode) {
var filePaths, prefix, processFiles;
if (initialCode == null) initialCode = '';
+ if (endCode == null) endCode = '';
processFiles = function(fileContents, i) {
- var path;
+ var file, path, _ref;
if (fileContents == null) fileContents = [];
if (i == null) i = 0;
- path = filePaths[i];
- return asset[assetType](path, {
+ _ref = filePaths[i], path = _ref.path, file = _ref.file;
+ return asset[assetType](file, {
+ pathPrefix: path,
compress: true
}, function(output) {
var fileName;
@@ -123,19 +126,22 @@ exports.init = function(root, codeWrappers, templateEngine) {
return processFiles(fileContents, i + 1);
} else {
output = fileContents.join(concatinator);
- output = initialCode + output;
+ output = initialCode + output + endCode;
fileName = clientDir + '/' + id + '.' + assetType;
fs.writeFileSync(fileName, output);
- return log(''.green, 'Packed ' + filePaths.length + ' files into ' + fileName);
+ return log(''.green, 'Packed ' + filePaths.length + ' files into ' + fileName.substr(root.length));
}
});
};
if (paths && paths.length > 0) {
filePaths = [];
- prefix = pathlib.join(root, dir).replace(/\\/g, '/');
+ prefix = pathlib.join(root, dir);
paths.forEach(function(path) {
return magicPath.files(prefix, path).forEach(function(file) {
- return filePaths.push(file);
+ return filePaths.push({
+ path: path,
+ file: file
+ });
});
});
return processFiles();
@@ -154,14 +160,14 @@ exports.init = function(root, codeWrappers, templateEngine) {
filesDeleted.length > 1 && log(''.green, "" + filesDeleted.length + " previous packaged files deleted");
}
packAssetSet('css', this.paths.css, 'client/css', "\n");
- ssClient.code(function(output) {
- return packAssetSet('js', _this.paths.code, 'client/code', "; ", output);
+ ssClient.code(function(clientCode) {
+ return packAssetSet('js', _this.paths.code, 'client/code', "; ", clientCode, '; ' + initAppCode);
});
return this.html(ssClient, formatters, true, function(output) {
var fileName;
fileName = pathlib.join(clientDir, id + '.html');
fs.writeFileSync(fileName, output);
- return log(''.green, 'Created and cached HTML file ' + fileName);
+ return log(''.green, 'Created and cached HTML file ' + fileName.substr(root.length));
});
};
View
13 lib/client_asset_manager/code_wrappers/module.js
@@ -1,13 +0,0 @@
-var modPath;
-
-exports.process = function(code, path) {
- return "SocketStream.modules['" + (modPath(path)) + "'] = {mod: function(exports, require){" + code + "}, path: '" + path + "'};";
-};
-
-modPath = function(path) {
- var out;
- out = path.split('.');
- out.pop();
- out = out.join();
- return out.split('/').splice(1).join('/');
-};
View
4 lib/client_asset_manager/code_wrappers/safety.js
@@ -1,4 +0,0 @@
-
-exports.process = function(code, path) {
- return "(function(require, loadAsync){" + code + "}).call(this, SocketStream.require, SocketStream.loadAsync);";
-};
View
41 lib/client_asset_manager/index.js
@@ -1,4 +1,4 @@
-var codeWrappers, formattersByExtension, http, log, packAssetOptions, packAssets, res;
+var formattersByExtension, http, initAppCode, log, packAssets, res, settings;
log = console.log;
@@ -10,18 +10,18 @@ formattersByExtension = null;
packAssets = false;
-packAssetOptions = {};
-
-codeWrappers = {
- 'libs': false,
- 'modules': 'module'
+settings = {
+ packAssets: {},
+ liveReload: true
};
+initAppCode = "require('/entry'); require('socketstream').connect();";
+
exports.init = function(root, router, reservedNames) {
var Client, clients, formatters, ssClient, templateEngine;
formatters = require('./formatters').init(root);
templateEngine = require('./template_engine').init(root);
- Client = require('./client').init(root, codeWrappers, templateEngine);
+ Client = require('./client').init(root, templateEngine, initAppCode);
clients = {};
ssClient = null;
res.serveClient = function(nameOrClient) {
@@ -43,9 +43,21 @@ exports.init = function(root, router, reservedNames) {
return {
formatters: formatters,
templateEngine: templateEngine,
+ set: function(newSettings) {
+ var k, v, _results;
+ if (typeof newSettings !== 'object') {
+ throw new Error('ss.client.set() takes an object e.g. {liveReload: false}');
+ }
+ _results = [];
+ for (k in newSettings) {
+ v = newSettings[k];
+ _results.push(settings[k] = v);
+ }
+ return _results;
+ },
packAssets: function(options) {
packAssets = true;
- return packAssetOptions = options;
+ return settings.packAssets = options;
},
define: function(name, paths) {
var client;
@@ -60,10 +72,7 @@ exports.init = function(root, router, reservedNames) {
return client;
},
wrapCode: function(nameOrModule, dirs) {
- if (!(dirs instanceof Array)) dirs = [dirs];
- return dirs.forEach(function(dir) {
- return codeWrappers[dir] = nameOrModule;
- });
+ throw new Error("Thanks for upgrading to the latest alpha. The ss.client.wrapCode() command has now been deprecated as every file not in /client/code/libs is now assumed to be a module. Please remove calls to ss.client.wrapCode() in your app and restart SocketStream\n\n");
},
load: function(client, ss) {
var asset, name;
@@ -72,13 +81,13 @@ exports.init = function(root, router, reservedNames) {
if (packAssets) {
for (name in clients) {
client = clients[name];
- client.pack(ssClient, formattersByExtension, packAssetOptions);
+ client.pack(ssClient, formattersByExtension, settings.packAssets);
}
} else {
- require('./live_reload').init(root, ss);
+ if (settings.liveReload) require('./live_reload').init(root, ss);
}
- asset = require('./asset').init(root, formattersByExtension, codeWrappers);
- return require('./serve_live').init(router, ssClient, asset, packAssets);
+ asset = require('./asset').init(root, formattersByExtension);
+ return require('./serve_live').init(router, ssClient, asset, initAppCode, packAssets);
}
};
};
View
1 lib/client_asset_manager/magic_path.js
@@ -12,6 +12,7 @@ exports.files = function(prefix, paths) {
if (!(paths instanceof Array)) paths = [paths];
paths.forEach(function(path) {
var dir, sp, tree;
+ path = path.replace(/\\/g, '/');
sp = path.split('/');
if (sp[sp.length - 1].indexOf('.') > 0) {
return files.push(path);
View
19 lib/client_asset_manager/serve_live.js
@@ -1,22 +1,25 @@
-var magicPath, parseUrl, pathlib, serve, url;
+var magicPath, parseUrl, pathlib, qs, serve, url;
require('colors');
url = require('url');
+qs = require('querystring');
+
pathlib = require('path');
magicPath = require('./magic_path');
-exports.init = function(router, ssClient, asset, packAssets) {
+exports.init = function(router, ssClient, asset, initAppCode, packAssets) {
router.on('/_serveAsync/code?*', function(request, response) {
var dir, files, output, path;
path = parseUrl(request.url);
dir = pathlib.join(root, 'client/code');
files = magicPath.files(dir, [path]);
output = [];
- return files.forEach(function(path) {
- return asset.js(path, {
+ return files.forEach(function(file) {
+ return asset.js(file, {
+ pathPrefix: path,
compress: packAssets
}, function(js) {
output.push(js);
@@ -33,14 +36,20 @@ exports.init = function(router, ssClient, asset, packAssets) {
});
});
router.on('/_serveDev/code?*', function(request, response) {
- var path;
+ var params, path, thisUrl;
+ thisUrl = url.parse(request.url);
+ params = qs.parse(thisUrl.query);
path = parseUrl(request.url);
return asset.js(path, {
+ pathPrefix: params.pathPrefix,
compress: false
}, function(output) {
return serve(output, 'text/javascript; charset=utf-8', response);
});
});
+ router.on('/_serveDev/start?*', function(request, response) {
+ return serve(initAppCode, 'text/javascript; charset=utf-8', response);
+ });
return router.on('/_serveDev/css?*', function(request, response) {
var path;
path = parseUrl(request.url);
View
2 lib/request/middleware/internal.js
@@ -24,7 +24,7 @@ exports.init = function(root, ss) {
if (thisSession) {
return next();
} else {
- return console.log(("! Error: Session ID " + request.sessionId + " not found. Terminating incoming request").red);
+ return console.log(("! Error: Session ID " + request.sessionId + " not found. Use Redis to persist sessions between server restarts. Terminating incoming request").red);
}
});
} else {
View
12 lib/request/responders/events/client.js
@@ -1,15 +1,19 @@
-var EE2;
+var EE2, EventEmitter2, ss;
+
+EventEmitter2 = require('eventemitter2').EventEmitter2;
+
+ss = require('socketstream');
EE2 = new EventEmitter2;
-window.SocketStream.registerApi('event', EE2);
+ss.registerApi('event', EE2);
-SocketStream.message.on('event', function(msg, meta) {
+ss.message.on('event', function(msg, meta) {
var args, ee, obj;
obj = JSON.parse(msg);
args = [obj.e];
args = args.concat(obj.p);
(meta != null) && args.push(meta);
- ee = obj.e && obj.e.substr(0, 5) === '__ss:' && SocketStream.event || EE2;
+ ee = obj.e && obj.e.substr(0, 5) === '__ss:' && ss.server || EE2;
return ee.emit.apply(ee, args);
});
View
10 lib/request/responders/rpc/client.js
@@ -1,4 +1,6 @@
-var cbStack, defaultCallback, numRequests;
+var cbStack, defaultCallback, numRequests, ss;
+
+ss = require('socketstream');
numRequests = 0;
@@ -8,7 +10,7 @@ defaultCallback = function(x) {
return console.log(x);
};
-window.SocketStream.registerApi('rpc', function() {
+ss.registerApi('rpc', function() {
var args, cb, lastArg, msg, obj;
args = Array.prototype.slice.call(arguments);
obj = {};
@@ -24,11 +26,11 @@ window.SocketStream.registerApi('rpc', function() {
}
cbStack[obj.id] = cb;
msg = JSON.stringify(obj);
- SocketStream.transport.send('rpc|' + msg);
+ ss.send('rpc|' + msg);
return;
});
-window.SocketStream.message.on('rpc', function(msg) {
+ss.message.on('rpc', function(msg) {
var cb, obj;
obj = JSON.parse(msg);
if (obj.id && (cb = cbStack[obj.id])) {
View
49 lib/utils/copy.js
@@ -1,31 +1,42 @@
-var fs, path, util;
+var copyFile, fs, path, util,
+ __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
fs = require('fs');
path = require('path');
util = require('util');
-exports.copyFile = function(source, destination) {
+exports.recursiveCopy = function(source, destination, options) {
+ var copyDir, originalSourceLength;
+ if (options == null) options = {};
+ originalSourceLength = source.length;
+ copyDir = function(source, destination, options) {
+ var files;
+ files = fs.readdirSync(source);
+ return files.forEach(function(file) {
+ var destinationPath, extension, sourcePath, stats, thisPath;
+ sourcePath = path.join(source, file);
+ destinationPath = path.join(destination, file);
+ stats = fs.statSync(sourcePath);
+ if (stats.isDirectory()) {
+ fs.mkdirSync(destinationPath, 0755);
+ return copyDir(sourcePath, destinationPath, options);
+ } else {
+ thisPath = path.dirname(sourcePath).substr(originalSourceLength) || '/';
+ extension = path.extname(sourcePath);
+ if (!options.exclude || (__indexOf.call(options.exclude.inPaths, thisPath) < 0) || (__indexOf.call(options.exclude.extensions, extension) < 0)) {
+ return copyFile(sourcePath, destinationPath);
+ }
+ }
+ });
+ };
+ return copyDir(source, destination, options);
+};
+
+copyFile = function(source, destination) {
var read, write;
read = fs.createReadStream(source);
write = fs.createWriteStream(destination);
return util.pump(read, write);
};
-
-exports.recursiveCopy = function(source, destination) {
- var files;
- files = fs.readdirSync(source);
- return files.forEach(function(file) {
- var destinationPath, sourcePath, stats;
- sourcePath = path.join(source, file);
- destinationPath = path.join(destination, file);
- stats = fs.statSync(sourcePath);
- if (stats.isDirectory()) {
- fs.mkdirSync(destinationPath, 0755);
- return exports.recursiveCopy(sourcePath, destinationPath);
- } else {
- return exports.copyFile(sourcePath, destinationPath);
- }
- });
-};
View
10 lib/websocket/transports/socketio/index.js
@@ -58,14 +58,14 @@ exports.init = function(emitter, httpServer, config) {
},
client: function() {
return {
+ libs: function() {
+ return fs.readFileSync(__dirname + '/client.min.js', 'utf8');
+ },
code: function() {
- var ext, input, output;
- output = [];
- output.push(fs.readFileSync(__dirname + '/client.min.js', 'utf8'));
+ var ext, input;
ext = (coffee != null) && 'coffee' || 'js';
input = fs.readFileSync(__dirname + '/wrapper.' + ext, 'utf8');
- output.push((coffee != null) && coffee.compile(input) || input);
- return output.join(";\n");
+ return (coffee != null) && coffee.compile(input) || input;
}
};
}
View
64 lib/websocket/transports/socketio/wrapper.js
@@ -1,32 +1,36 @@
+var conn;
-window.SocketStream.transport = {
- connect: function(cb) {
- var conn;
- conn = io.connect();
- conn.on('message', function(msg, meta) {
- var content, i, type;
- if ((i = msg.indexOf('|')) > 0) {
- type = msg.substr(0, i);
- content = msg.substr(i + 1);
- return SocketStream.message.emit(type, content, meta);
- } else {
- return console.error('Invalid websocket message received:', msg);
- }
- });
- conn.on('ready', function(cb) {
- return SocketStream.event.emit('ready');
- });
- conn.on('disconnect', function() {
- return SocketStream.event.emit('disconnect');
- });
- conn.on('reconnect', function() {
- return SocketStream.event.emit('reconnect');
- });
- conn.on('connect', function() {
- return SocketStream.event.emit('connect');
- });
- return SocketStream.transport.send = function(msg) {
- return conn.send(msg);
- };
- }
+conn = null;
+
+module.exports = function(emitter, message) {
+ return {
+ connect: function(fn) {
+ conn = io.connect();
+ conn.on('message', function(msg, meta) {
+ var content, i, type;
+ if ((i = msg.indexOf('|')) > 0) {
+ type = msg.substr(0, i);
+ content = msg.substr(i + 1);
+ return message.emit(type, content, meta);
+ } else {
+ return console.error('Invalid websocket message received:', msg);
+ }
+ });
+ conn.on('ready', function(cb) {
+ return emitter.emit('ready');
+ });
+ conn.on('disconnect', function() {
+ return emitter.emit('disconnect');
+ });
+ conn.on('reconnect', function() {
+ return emitter.emit('reconnect');
+ });
+ conn.on('connect', function() {
+ return emitter.emit('connect');
+ });
+ return function(msg) {
+ return conn.send(msg);
+ };
+ }
+ };
};
View
12 new_project/app.js
@@ -3,12 +3,15 @@
var http = require('http')
, ss = require('socketstream');
+// Define a single-page client
ss.client.define('main', {
view: 'app.jade',
css: ['libs', 'app.styl'],
- code: ['libs', 'modules', 'main']
+ code: ['libs', 'app'],
+ tmpl: '*'
});
+// Serve this client on the root URL
ss.http.router.on('/', function(req, res) {
res.serveClient('main');
});
@@ -21,15 +24,12 @@ ss.client.formatters.add(require('ss-stylus'));
// Use server-side compiled Hogan (Mustache) templates. Others engines available
ss.client.templateEngine.use(require('ss-hogan'));
-// Minimise and pack assets if you type: SS_ENV=production node app.js
+// Minimize and pack assets if you type: SS_ENV=production node app.js
@drosen0
drosen0 added a note Mar 12, 2012

I guess we North Americans are running our spell checkers, but I like the English spelling...

Ha yes, wondered if anyone would spot that. I prefer it too but I know many will think it's misspelt: #136

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
if (ss.env == 'production') ss.client.packAssets();
@drosen0
drosen0 added a note Mar 12, 2012

Since redis is recommended as session store and pub/sub transport in production, why not have something like this:

if (ss.env == 'production') {
  ss.client.packAssets();
  ss.session.store.use('redis');
  ss.publish.transport.use('redis');
}

Tempting but it would turn one line into 4, plus it's not strictly required. Though we should print a warning in the console if you're not using Redis in production.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
-// Enable optional console server access. Install client with 'npm install -g ss-console'
-var consoleServer = require('ss-console').init(ss);
-consoleServer.listen(5000);
-
// Start web server
var server = http.Server(ss.http.middleware);
server.listen(3000);
+// Start SocketStream
ss.start(server);
View
47 new_project/client/code/app/demo.coffee
@@ -0,0 +1,47 @@
+### QUICK CHAT DEMO ####
+
+# Delete this file once you've seen how the demo works
+
+# Listen out for newMessage events coming from the server
+ss.event.on 'newMessage', (message) ->
+
+ # Example of using the Hogan Template in client/templates/chat/message.jade to generate HTML for each message
+ html = HT['chat-message'].render({message: message, time: -> timestamp() })
+
+ # Append it to the #chatlog div and show effect
+ $(html).hide().appendTo('#chatlog').slideDown()
+
+
+# Show the chat form and bind to the submit action
+$('#demo').on 'submit', ->
+
+ # Grab the message from the text box
+ text = $('#myMessage').val()
+
+ # Call the 'send' funtion (below) to ensure it's valid before sending to the server
+ exports.send text, (success) ->
+ if success
+ $('#myMessage').val('') # clear text box
+ else
+ alert('Oops! Unable to send message')
+
+
+# Demonstrates sharing code between modules by exporting function
+exports.send = (text, cb) ->
+ if valid(text)
+ ss.rpc('demo.sendMessage', text, cb)
+ else
+ cb(false)
+
+
+# Private functions
+
+timestamp = ->
+ d = new Date()
+ d.getHours() + ':' + pad2(d.getMinutes()) + ':' + pad2(d.getSeconds())
+
+pad2 = (number) ->
+ (if number < 10 then '0' else '') + number
+
+valid = (text) ->
+ text && text.length > 0