├── .gitignore ├── .npmignore ├── README.md ├── examples ├── net-socket-client-server │ ├── net-socket-client.js │ └── net-socket-server.js ├── socketio-fib │ ├── fib-server.js │ ├── index.html │ └── sio-fib.js ├── webworker-pi │ ├── index.html │ ├── worker-process.js │ └── ww-pi.js └── webworker-socketio-childproc │ ├── child-process.js │ ├── index.html │ ├── sio-server.js │ ├── worker-process.js │ └── ww-sio-cp.js ├── index.js ├── package.json └── transports ├── nodestream ├── index.js └── package.json ├── socketio ├── index.js └── package.json └── webworker ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remote CSP Channel 2 | 3 | A [CSP (Communicating Sequential Processes)](https://github.com/getify/asynquence/tree/master/contrib#go-style-csp-api-emulation) channel that bridges to remote contexts. 4 | 5 | ## Overview 6 | 7 | This library provides facilities for defining CSP channels that are transparently bridged across a transport layer to a remote context. A remote channel is used in your CSP code identically to a local channel. 8 | 9 | `RemoteCSPChannel` includes the following three API methods: 10 | 11 | * `initCSP(..)`: wraps CSP methods to make working with remote channels transparent 12 | * `defineTransport(..)`: defines a transport (adapter) instance to carry the messages from the `RemoteCSPChannel` library between the local and remote contexts 13 | * `openChannel(..)`: opens a channel instance for a specified transport 14 | 15 | `RemoteCSPChannel` implements a signal/ACK protocol to communicate with a remote context and ensure that both sides of the communication are in sync with CSP actions. It relies on a transport (adapter) to actually carry the message to a remote process. 16 | 17 | ## Transports 18 | 19 | The following transports are currently available (more to come): 20 | 21 | * Web Worker (Dedicated or Shared): [`transports/webworker`](../../tree/master/transports/webworker) (`TransportWebWorker`) 22 | * Socket.io: [`transports/socketio`](../../tree/master/transports/socketio) (`TransportSocketIO`) 23 | * Node.js I/O Stream: [`transports/nodestream`](../../tree/master/transports/nodestream) (`TransportNodeStream`) 24 | 25 | Transports are responsible for ensuring messages are delivered in both directions, between the local and remote contexts. 26 | 27 | ### Building Your Own Transport 28 | 29 | The simplest/easiest transport to inspect to see how to define other transports is the [`transports/socketio`](../../tree/master/transports/socketio/index.js) transport. 30 | 31 | A transport must expose a public API method called `connect(..)`. Arguments to this `connect(..)` method come from the optional arguments to the [`RemoteCSPChannel.defineTransport(..)`](#define-transport) utility. 32 | 33 | `connect(..)` must return an object with two members: 34 | 35 | * `onMessage(..)`: this method will be called once by `RemoteCSPChannel` and passed a single function reference as argument. Make sure to save a reference to that received function; any time a message is received from the remote context, pass it to this saved function so that `RemoteCSPChannel` can handle it. 36 | * `sendMessage(..)`: this method will be called automatically by `RemoteCSPChannel` any time it needs to send a message to the remote context. It will pass a single argument which is a message object (JSON-compatible). You must serialize and/or transfer that message to the remote context, and ensure it's reconstructed (as necessary) as the message object. 37 | 38 | **Note:** You do not need to implement any special signaling/ACKing protocols, as the `RemoteCSPChannel` handles that. However, depending on the nuances of the transport type, other message stream processing may be necessary. For example, see `transports/nodestream` for how message delimiter text is added and parsed, to counteract OS level message bundling that happens over certain types of I/O streams. 39 | 40 | ## How To Use 41 | 42 | There are three setup steps to prepare your CSP to use a remote channel. 43 | 44 | ### Initialize CSP 45 | 46 | **(1)** Adapt your lib's CSP methods to transparently handle remote channels as well as local channels: 47 | 48 | ```js 49 | RemoteCSPChannel.initCSP( 50 | %csp-lib-namespace%, 51 | %csp-channel-factory%, 52 | 53 | // optional: 54 | %promisewrapper% 55 | ); 56 | ``` 57 | 58 | `%csp-lib-namespace%` is the namespace object for your CSP lib, such as `csp`. For [asynquence's CSP](https://github.com/getify/asynquence/tree/master/contrib#go-style-csp-api-emulation), it would be `ASQ.csp`. If your lib has all methods in the global namespace (boo!), pass an object with references for each method (such as `{ put: put, take: take, .. }`), and then make sure to use the replaced wrapped methods on that object instead of the global ones. 59 | 60 | `%csp-channel-factory%` is a reference to the function your CSP lib provides for creating normal local channels, such as `chan`. For [asynquence's CSP](https://github.com/getify/asynquence/tree/master/contrib#go-style-csp-api-emulation), it would be `ASQ.csp.chan`. 61 | 62 | `%promisewrapper%` (optional) is a function used to wrap the return values from a CSP lib's methods (like `put(..)` and `take(..)`) into a normal Promise, if it's not already that way. If your CSP lib already returns promises, you can omit this argument. For [asynquence's CSP](https://github.com/getify/asynquence/tree/master/contrib#go-style-csp-api-emulation), which returns sequences instead of normal promises, the wrapper function could be: 63 | 64 | ```js 65 | function promisewrap(v) { 66 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 67 | } 68 | ``` 69 | 70 | ### Define Transport 71 | 72 | **(2)** Setup an instance of a transport for remote channel bridging: 73 | 74 | ```js 75 | RemoteCSPChannel.defineTransport( 76 | "%unique-transport-id%", 77 | %transport-type%, 78 | 79 | // optional: 80 | %transport-args% [, .. ] 81 | ); 82 | ``` 83 | 84 | `%unique-transport-id%` is a string value used for the transport ID and can be anything you want. It must be unique within the program and must match on both sides of your remote CSP. 85 | 86 | `%transport-type%` is the chosen transport factory (like `TransportSocketIO`). See [Transports](#transports) for available options. 87 | 88 | `%transport-args%` (optional) specify arguments to pass along to the transport factory's `connect(..)` method. For example, if you're spinning up a web worker bridged channel from a browser, you'd need to create a `new Worker(..)` instance to pass along as a `%transport-args%` argument. 89 | 90 | ### Open Channel 91 | 92 | **(3)** Open up a remote channel: 93 | 94 | ```js 95 | var pr = RemoteCSPChannel.openChannel( 96 | "%transport-id%", 97 | "%channel-id%" 98 | ); 99 | ``` 100 | 101 | `%transport-id%` is the same string value you used to define the transport you want to use. 102 | 103 | `%channel-id` is a unique string value that represents a channel. This ID must be application-wide unique (not per transport) and must match on both sides of your remote CSP. 104 | 105 | `openChannel(..)` returns a promise for the channel, which will resolve with the actual channel object once it's been fully opened both locally and remote. You can wait for that channel value resolution using a normal `Promise#then(..)` approach, or (preferred) `yield`ing in a generator. 106 | 107 | ### Using a Channel 108 | 109 | Now you can use the remote channel throughout your program (with `put(..)` and `take(..)`, etc), just like any normal local channel: 110 | 111 | ```js 112 | // note: `remoteCh` already opened 113 | 114 | go(function *localProc(){ 115 | var meaning = yield take( remoteCh ); 116 | console.log( meaning ); 117 | }); 118 | 119 | // *************************** 120 | // and in some remote context: 121 | // *************************** 122 | // note: `remoteCh` already opened 123 | 124 | go(function *remoteProc(){ 125 | yield put( remoteCh, 42 ); 126 | }); 127 | ``` 128 | 129 | That's pretty much it! 130 | 131 | ### Transport ID and Channel ID 132 | 133 | It's very important to recognize that you will need a mechanism for determining (or generating) a unique string value for both a transport ID and a channel ID. These must both be unique within your application; you cannot use the same channel ID across two different transports. 134 | 135 | For example, if you have a Socket.io server process receiving incoming connections from multiple clients, you'll obviously need to have a different transport ID for each client-server connection, since the server cannot use the same transport ID for its multiple transports, and each transport must be directly connected to a specific client socket. 136 | 137 | Since both sides of a remote channel need to agree on the transport ID and channel ID, and since you may need to actually generate these values to ensure their uniqueness, you may very well have to pass the transport ID and/or channel ID manually to/from the remote context during initial connections. 138 | 139 | In the code for the [Examples](#examples) (see below), you'll see one way of handling this: by passing the transport ID and channel ID in the URL for connection to a Web Worker or Socket.io server. For Node.js child processes, the IDs are passed along as shell arguments. 140 | 141 | How you choose to generate, manage or transmit these IDs is entirely up to you. They just have to be unique and match on both sides. **They will be treated internally as strings.** 142 | 143 | ## Examples 144 | 145 | See the [`examples/`](../../tree/master/examples/) directory for the following examples: 146 | 147 | * [Long-running Loop](../../tree/master/examples/webworker-socketio-childproc) (Web Worker + Socket.io + Node.js Child Process) 148 | * [Fibonacci](../../tree/master/examples/socketio-fib) (Socket.io) 149 | * [Pi Digits](../../tree/master/examples/webworker-pi) (Web Worker) 150 | * [Long-running Loop](../../tree/master/examples/net-socket-client-server) (Node.js `net.Socket` Client / Server) 151 | 152 | To run these examples locally on your own system: 153 | 154 | 1. Clone this repo 155 | 2. In the root directory of this repo, run `npm install` to install its `devDependencies` (listed in `package.json`) 156 | 3. For the examples with server components, start up the appropriate server process(s) at the command line with `node {server-filename}.js` 157 | 4. For the examples with a web page UI: 158 | * run a local file server (like `python -m SimpleHTTPServer 8080`, `node-static`, etc) in the root directory of this repo, simply to serve up the HTML/JS files to the browser under `localhost` 159 | * navigate to the appropriate `index.html` file in your browser 160 | 161 | ## Known Issues 162 | 163 | This project is at a very early alpha stage. There are a number of caveats to be aware of: 164 | 165 | * An assumption is made of the method names for CSP actions: `put`, `take`, etc. If your lib's method names don't match, you may have to define your own adaptation/mapping logic. 166 | * Local CSP channels can have a buffer size > 1, but `RemoteCSPChannel` doesn't yet support those semantics. This is very difficult to achieve. 167 | * Currently only `put(..)` and `take(..)` are supported. CSP `alts(..)`, for example, is **much more complicated** to negotiate across multiple contexts, so they are not yet supported. 168 | * If you have a **local** `put(..)` and `take(..)` on the same **remote** channel, an assumption is made that you'd like to fulfill that action locally and not remotely. It will attempt to signal the remote context to tell it to cancel the first (already-messaged) remote behavior. However, that cancelation may not fully work yet, which means that remote channel may get into a weird state. 169 | 170 | ## License 171 | 172 | The code and all the documentation are released under the MIT license. 173 | 174 | http://getify.mit-license.org/ 175 | -------------------------------------------------------------------------------- /examples/net-socket-client-server/net-socket-client.js: -------------------------------------------------------------------------------- 1 | var net = require("net"), 2 | 3 | client = net.createConnection({ port: 8010 },onConnection), 4 | 5 | ASQ = require("../../node_modules/asynquence"), 6 | RemoteCSPChannel = require("../../"), 7 | TransportNodeStream = require("../../transports/nodestream"), 8 | 9 | transport_id = "netsocket", 10 | channel_id = "123"; 11 | 12 | require("../../node_modules/asynquence-contrib/contrib-es6.src.js")(ASQ); 13 | 14 | RemoteCSPChannel.initCSP( 15 | /*API=*/ASQ.csp, 16 | /*channelFactory=*/ASQ.csp.chan, 17 | /*returnAsPromise=*/function $$promisewrap(v){ 18 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 19 | } 20 | ); 21 | 22 | 23 | // ************************ 24 | 25 | function onConnection() { 26 | RemoteCSPChannel.defineTransport( 27 | /*transportID=*/transport_id, 28 | /*transport=*/TransportNodeStream, 29 | 30 | // transport args: 31 | { from: client, to: client } 32 | ); 33 | 34 | ASQ().runner( 35 | ASQ.csp.go(function *main(){ 36 | var ch = yield RemoteCSPChannel.openChannel(transport_id,channel_id); 37 | 38 | console.log("(" + channel_id + ") channel opened"); 39 | 40 | var num = yield ASQ.csp.take(ch); 41 | 42 | console.log("(" + channel_id + ") message received:",num); 43 | }) 44 | ) 45 | .val(function(){ 46 | client.end(); 47 | console.log("socket connection complete"); 48 | }) 49 | .or(function(err){ 50 | console.log(err); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /examples/net-socket-client-server/net-socket-server.js: -------------------------------------------------------------------------------- 1 | var net = require("net"), 2 | 3 | server = net.createServer(onConnection), 4 | 5 | ASQ = require("../../node_modules/asynquence"), 6 | RemoteCSPChannel = require("../../"), 7 | TransportNodeStream = require("../../transports/nodestream"), 8 | 9 | transport_id = "netsocket", 10 | channel_id = "123"; 11 | 12 | require("../../node_modules/asynquence-contrib/contrib-es6.src.js")(ASQ); 13 | 14 | RemoteCSPChannel.initCSP( 15 | /*API=*/ASQ.csp, 16 | /*channelFactory=*/ASQ.csp.chan, 17 | /*returnAsPromise=*/function $$promisewrap(v){ 18 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 19 | } 20 | ); 21 | 22 | server.listen(8010); 23 | 24 | 25 | // ************************** 26 | 27 | function onConnection(conn) { 28 | RemoteCSPChannel.defineTransport( 29 | /*transportID=*/transport_id, 30 | /*transport=*/TransportNodeStream, 31 | 32 | // transport args: 33 | { from: conn, to: conn } 34 | ); 35 | 36 | ASQ().runner( 37 | ASQ.csp.go(function *main(){ 38 | var ch = yield RemoteCSPChannel.openChannel(transport_id,channel_id); 39 | 40 | console.log("(" + channel_id + ") channel opened"); 41 | 42 | for (var i=0; i<1E9; i++) {} 43 | 44 | yield ASQ.csp.put(ch,i); 45 | 46 | console.log("(" + channel_id + ") message sent"); 47 | }) 48 | ) 49 | .val(function(){ 50 | server.close(); 51 | console.log("socket connection complete"); 52 | }) 53 | .or(function(err){ 54 | server.close(); 55 | console.log(err); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /examples/socketio-fib/fib-server.js: -------------------------------------------------------------------------------- 1 | var http = require("http"), 2 | server = http.createServer(router), 3 | io = require("../../node_modules/socket.io")(server), 4 | 5 | ASQ = require("../../node_modules/asynquence"), 6 | RemoteCSPChannel = require("../../"), 7 | TransportSocketIO = require("../../transports/socketio"), 8 | 9 | transports_defined = {}; 10 | 11 | require("../../node_modules/asynquence-contrib/contrib-es6.src.js")(ASQ); 12 | 13 | RemoteCSPChannel.initCSP( 14 | /*API=*/ASQ.csp, 15 | /*channelFactory=*/ASQ.csp.chan, 16 | /*returnAsPromise=*/function $$promisewrap(v){ 17 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 18 | } 19 | ); 20 | 21 | server.listen(8005); 22 | 23 | ASQ().runner( 24 | ASQ.csp.go(function *main(ch){ 25 | spinUpServerSocket(ch.go); 26 | 27 | // keep alive (waiting for incoming socket requests) 28 | yield ASQ.csp.take(ch); 29 | }) 30 | ) 31 | .val(function(){ 32 | console.log("server complete"); 33 | }) 34 | .or(function(err){ 35 | console.log(err); 36 | }); 37 | 38 | 39 | // ********************* 40 | 41 | function spinUpServerSocket(go) { 42 | io.of("/csp").on("connection",function onconn(CSPsocket){ 43 | onCSPSocket(go,CSPsocket); 44 | }); 45 | } 46 | 47 | function onCSPSocket(go,CSPsocket) { 48 | var params = parseUri(CSPsocket.handshake.url).queryKey, 49 | transport_id = decodeURIComponent(params.tr), 50 | channel_id = decodeURIComponent(params.ch), 51 | channel_ch = ASQ.csp.chan(), 52 | id = transport_id + ":" + channel_id; 53 | 54 | // if socket is disconnected, close the channel_ch 55 | CSPsocket.on("disconnect",function ondisc(){ 56 | if (channel_ch) { 57 | channel_ch.close(); 58 | channel_ch = null; 59 | } 60 | }); 61 | 62 | if (!(id in transports_defined)) { 63 | RemoteCSPChannel.defineTransport( 64 | /*transportID=*/transport_id, 65 | /*transport=*/TransportSocketIO, 66 | 67 | // transport args: 68 | CSPsocket 69 | ); 70 | 71 | transports_defined[id] = true; 72 | } 73 | 74 | RemoteCSPChannel.openChannel(transport_id,channel_id) 75 | .then(function chReady(ch){ 76 | if (channel_ch) { 77 | ASQ.csp.putAsync(channel_ch,ch); 78 | } 79 | }); 80 | 81 | go(getFib,[ channel_ch ] ); 82 | } 83 | 84 | function *getFib(_,channelCh) { 85 | var ch = yield ASQ.csp.take(channelCh); 86 | if (ch === ASQ.csp.CLOSED) return; 87 | channelCh.close(); 88 | 89 | // wait for FIB(n) request 90 | var n = yield ASQ.csp.take(ch); 91 | 92 | // calculate FIB(n) 93 | var fib = calcFib(n); 94 | 95 | // send answer back 96 | yield ASQ.csp.put(ch,fib); 97 | } 98 | 99 | // silly/naive fibonnaci 100 | function calcFib(n) { 101 | if (n > 1) { 102 | return calcFib(n-1) + calcFib(n-2); 103 | } 104 | else { 105 | return n; 106 | } 107 | } 108 | 109 | function router(req,res) { 110 | res.writeHead(200,{ "Content-Type": "text/plain" }); 111 | res.end("Nothing to see here."); 112 | } 113 | 114 | // adapted from: http://blog.stevenlevithan.com/archives/parseuri 115 | function parseUri(r){for(var e={key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}},o=e.parser.loose.exec(r),a={},s=14;s--;)a[e.key[s]]=o[s]||"";return a[e.q.name]={},a[e.key[12]].replace(e.q.parser,function(r,o,s){o&&(a[e.q.name][o]=s)}),a} 116 | -------------------------------------------------------------------------------- /examples/socketio-fib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fibonacci - Remote CSP (Socket.io) 6 | 9 | 10 | 11 |

Fibonacci - Remote CSP (Socket.io)

12 | 13 | FIB(42): ... 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/socketio-fib/sio-fib.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | "use strict"; 3 | 4 | var transport_ids = {}, 5 | channel_ids = {}, 6 | transport_id = getNewTransportID("FIB"), 7 | channel_id = getNewChannelID("CH"), 8 | 9 | CSPsocket = io( 10 | "http://localhost:8005/csp?" + 11 | "tr=" + transport_id + 12 | "&ch=" + channel_id 13 | ); 14 | 15 | RemoteCSPChannel.initCSP( 16 | /*API=*/ASQ.csp, 17 | /*channelFactory=*/ASQ.csp.chan, 18 | /*returnAsPromise=*/function $$promisewrap(v){ 19 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 20 | } 21 | ); 22 | 23 | RemoteCSPChannel.defineTransport( 24 | /*transportID=*/transport_id, 25 | /*transport=*/TransportSocketIO, 26 | 27 | // transport args: 28 | CSPsocket 29 | ); 30 | 31 | ASQ().runner( ASQ.csp.go(getFib) ); 32 | 33 | 34 | // **************************** 35 | 36 | function *getFib() { 37 | // open remote channel 38 | var ch = yield RemoteCSPChannel.openChannel( 39 | transport_id, 40 | channel_id 41 | ); 42 | 43 | // ask server for FIB(42) 44 | yield ASQ.csp.put(ch,42); 45 | 46 | // wait for answer 47 | var fib = yield ASQ.csp.take(ch); 48 | 49 | // show answer 50 | document.getElementById("answer").innerHTML = fib; 51 | } 52 | 53 | function getNewTransportID(prefix) { 54 | do { var id = prefix + ":" + Math.round(Math.random() * 1E9); } 55 | while (id in transport_ids); 56 | transport_ids[id] = true; 57 | return id; 58 | } 59 | 60 | function getNewChannelID(prefix) { 61 | do { var id = prefix + ":" + Math.round(Math.random() * 1E9); } 62 | while (id in channel_ids); 63 | channel_ids[id] = true; 64 | return id; 65 | } 66 | 67 | })(); 68 | -------------------------------------------------------------------------------- /examples/webworker-pi/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pi Digits - Remote CSP (Web Worker) 6 | 10 | 11 | 12 |

Pi Digits - Remote CSP (Web Worker)

13 |

Note: because of floating point precision, digits only accurate up to PI(31)...

14 | 15 |

16 | PI(0): 3. 17 |

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/webworker-pi/worker-process.js: -------------------------------------------------------------------------------- 1 | importScripts( 2 | "../../node_modules/asynquence/asq.js", 3 | "../../node_modules/asynquence-contrib/contrib-es6.src.js", 4 | "../../index.js", 5 | "../../transports/webworker/index.js" 6 | ); 7 | 8 | RemoteCSPChannel.initCSP( 9 | /*API=*/ASQ.csp, 10 | /*channelFactory=*/ASQ.csp.chan, 11 | /*returnAsPromise=*/function $$promisewrap(v){ 12 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 13 | } 14 | ); 15 | 16 | var digits_per_worker = 5; 17 | 18 | ASQ().runner( ASQ.csp.go(workerProc) ); 19 | 20 | 21 | // *********************** 22 | 23 | function *workerProc(){ 24 | var params = parseHash(self.location.href), 25 | transport_id = params.tr || "WWTR", 26 | channel_id = params.ch || "WWCH", 27 | start_index, ch, digit, digits = []; 28 | 29 | 30 | RemoteCSPChannel.defineTransport( 31 | /*transportID=*/transport_id, 32 | /*transport=*/TransportWebWorker 33 | ); 34 | 35 | ch = yield RemoteCSPChannel.openChannel(transport_id,channel_id); 36 | 37 | while (true) { 38 | // what digit index should we start at? 39 | start_index = yield ASQ.csp.take(ch); 40 | 41 | // reset local digits list 42 | digits.length = 0; 43 | 44 | // generate digits 45 | for (var i=0; i < digits_per_worker; i++) { 46 | digits.push( getDigit( start_index + i )[0] ); 47 | } 48 | 49 | // push digits back to page 50 | for (var i=0; i < digits_per_worker; i++) { 51 | yield ASQ.csp.put(ch,digits[i]); 52 | } 53 | } 54 | } 55 | 56 | // adapted from: http://blog.stevenlevithan.com/archives/parseuri 57 | function parseHash(t){var a="",e={};return/#./.test(t)&&(a=t.match(/#(.*)$/)[1],a.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(t,a,n){a&&(e[a]=n)})),e} 58 | 59 | 60 | // ****************** 61 | 62 | /* 63 | This program implements the BBP algorithm to generate a few hexadecimal 64 | digits beginning immediately after a given position id, or in other words 65 | beginning at position id + 1. On most systems using IEEE 64-bit floating- 66 | point arithmetic, this code works correctly so long as d is less than 67 | approximately 1.18 x 10^7. If 80-bit arithmetic can be employed, this limit 68 | is significantly higher. Whatever arithmetic is used, results for a given 69 | position id can be checked by repeating with id-1 or id+1, and verifying 70 | that the hex digits perfectly overlap with an offset of one, except possibly 71 | for a few trailing digits. The resulting fractions are typically accurate 72 | to at least 11 decimal digits, and to at least 9 hex digits. 73 | */ 74 | /* David H. Bailey 2006-09-08 */ 75 | function getDigit(id) { 76 | var pid, s1, s2, s3, s4; 77 | var NHX = 16; 78 | var chx; 79 | /* id is the digit position. Digits generated follow immediately after id. */ 80 | s1 = series(1, id); 81 | s2 = series(4, id); 82 | s3 = series(5, id); 83 | s4 = series(6, id); 84 | pid = 4 * s1 - 2 * s2 - s3 - s4; 85 | pid = pid - Math.trunc(pid) + 1; 86 | chx = ihex(pid, NHX); 87 | // printf (" position = %i\n fraction = %.15f \n hex digits = %10.10s\n", 88 | // id, pid, chx); 89 | // console.log(chx); 90 | return chx; 91 | } 92 | 93 | function ihex(x, nhx) { 94 | /* This returns the first nhx hex digits of the fraction of x. */ 95 | var i; 96 | var y; 97 | var hx = "0123456789ABCDEF"; 98 | var chx = []; 99 | y = Math.abs(x); 100 | for (i = 0; i < nhx; i++) { 101 | y = 16 * (y - Math.floor(y)); 102 | chx[i] = hx[Math.trunc(y)]; 103 | } 104 | return chx; 105 | } 106 | 107 | function series(m, id) { 108 | /* This routine evaluates the series sum_k 16^(id-k)/(8*k+m) 109 | using the modular exponentiation technique. */ 110 | var k; 111 | var ak, eps, p, s, t; 112 | var eps = 1e-17; 113 | s = 0; 114 | /* Sum the series up to id. */ 115 | for (k = 0; k < id; k++) { 116 | ak = 8 * k + m; 117 | p = id - k; 118 | t = expm(p, ak); 119 | s = s + t / ak; 120 | s = s - Math.trunc(s); 121 | } 122 | /* Compute a few terms where k >= id. */ 123 | for (k = id; k <= id + 100; k++) { 124 | ak = 8 * k + m; 125 | t = Math.pow(16, (id - k)) / ak; 126 | if (t < eps) break; 127 | s = s + t; 128 | s = s - Math.trunc(s); 129 | } 130 | return s; 131 | } 132 | 133 | function expm(p, ak) { 134 | /* expm = 16^p mod ak. This routine uses the left-to-right binary 135 | exponentiation scheme. */ 136 | var i, j; 137 | var p1, pt, r; 138 | var ntp = 25; 139 | expm.tp = []; 140 | expm.tp1 = 0; 141 | /* If this is the first call to expm, fill the power of two table tp. */ 142 | if (expm.tp1 == 0) { 143 | expm.tp1 = 1; 144 | expm.tp[0] = 1; 145 | for (i = 1; i < ntp; i++) expm.tp[i] = 2 * expm.tp[i - 1]; 146 | } 147 | if (ak == 1) return 0; 148 | /* Find the greatest power of two less than or equal to p. */ 149 | for (i = 0; i < ntp; i++) 150 | if (expm.tp[i] > p) break; 151 | pt = expm.tp[i - 1]; 152 | p1 = p; 153 | r = 1; 154 | /* Perform binary exponentiation algorithm modulo ak. */ 155 | for (j = 1; j <= i; j++) { 156 | if (p1 >= pt) { 157 | r = 16 * r; 158 | r = r - Math.trunc(r / ak) * ak; 159 | p1 = p1 - pt; 160 | } 161 | pt = 0.5 * pt; 162 | if (pt >= 1) { 163 | r = r * r; 164 | r = r - Math.trunc(r / ak) * ak; 165 | } 166 | } 167 | return r; 168 | } 169 | 170 | -------------------------------------------------------------------------------- /examples/webworker-pi/ww-pi.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | "use strict"; 3 | 4 | var transport_ids = {}, 5 | channel_ids = {}, 6 | local_channels = [], 7 | 8 | worker_count = 4, 9 | digits_per_worker = 5, 10 | 11 | overall_digit_count = 0; 12 | 13 | RemoteCSPChannel.initCSP( 14 | /*API=*/ASQ.csp, 15 | /*channelFactory=*/ASQ.csp.chan, 16 | /*returnAsPromise=*/function $$promisewrap(v){ 17 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 18 | } 19 | ); 20 | 21 | ASQ().runner( ASQ.csp.go(main) ); 22 | 23 | 24 | // **************************** 25 | 26 | function *main(ch){ 27 | var indx, i, localCh, hex, dec, cur, 28 | between_hidden = true, 29 | 30 | digit_count = document.getElementById("digit-count"), 31 | first_digits = document.getElementById("first-digits"), 32 | between_digits = document.getElementById("between-digits"), 33 | recent_digits = document.getElementById("recent-digits"); 34 | 35 | 36 | for (indx=0; indx < worker_count; indx++) { 37 | localCh = ASQ.csp.chan(); 38 | local_channels.push(localCh); 39 | spinUpWorkerProcess(ch.go,indx,localCh); 40 | } 41 | 42 | indx = 0; 43 | while (true) { 44 | for (i=0; i < digits_per_worker; i++) { 45 | hex = yield ASQ.csp.take(local_channels[indx]); 46 | dec = nextHexToDec(hex); 47 | 48 | overall_digit_count++; 49 | digit_count.innerHTML = overall_digit_count; 50 | 51 | if (overall_digit_count <= 10) { 52 | first_digits.innerHTML += dec; 53 | } 54 | else { 55 | cur = recent_digits.innerHTML + dec; 56 | if (cur.length > 20) { 57 | cur = cur.substr(cur.length - 20); 58 | if (between_hidden) { 59 | between_digits.style.display = "inline"; 60 | between_hidden = true; 61 | } 62 | } 63 | recent_digits.innerHTML = cur; 64 | } 65 | } 66 | 67 | indx = (indx + 1) % worker_count; 68 | } 69 | } 70 | 71 | function spinUpWorkerProcess(go,index,localCh) { 72 | var transport_id = getNewTransportID("WWTR"), 73 | channel_id = getNewChannelID("WWCH"); 74 | 75 | RemoteCSPChannel.defineTransport( 76 | /*transportID=*/transport_id, 77 | /*transport=*/TransportWebWorker, 78 | 79 | // transport args: 80 | new Worker("worker-process.js#tr=" + transport_id + "&ch=" + channel_id) 81 | ); 82 | 83 | go(remoteProc,[ transport_id, channel_id, index, localCh ]); 84 | } 85 | 86 | function *remoteProc(_,transportID,channelID,index,localCh) { 87 | var ch = yield RemoteCSPChannel.openChannel(transportID,channelID), 88 | hexdigit; 89 | 90 | // where should each worker start? 91 | index = index * digits_per_worker; 92 | 93 | while (true) { 94 | // tell the worker what index it starts at 95 | yield ASQ.csp.put(ch,index); 96 | 97 | for (var i=0; i < digits_per_worker; i++) { 98 | // get digit from worker 99 | hexdigit = yield ASQ.csp.take(ch); 100 | 101 | // push digit to local handler 102 | yield ASQ.csp.put(localCh,hexdigit); 103 | } 104 | 105 | index += (worker_count * digits_per_worker); 106 | } 107 | } 108 | 109 | var nextHexToDec = (function(){ 110 | var holding = 0, position = 0; 111 | 112 | return function nextHexToDec(hexChar) { 113 | var dec = parseInt(hexChar,16) / Math.pow(16,position+1) * Math.pow(10,position+1); 114 | dec = dec + holding; 115 | var whole = Math.floor(dec); 116 | holding = (dec % 1) * 10; 117 | position++; 118 | return whole; 119 | } 120 | })(); 121 | 122 | function getNewTransportID(prefix) { 123 | do { var id = prefix + ":" + Math.round(Math.random() * 1E9); } 124 | while (id in transport_ids); 125 | transport_ids[id] = true; 126 | return id; 127 | } 128 | 129 | function getNewChannelID(prefix) { 130 | do { var id = prefix + ":" + Math.round(Math.random() * 1E9); } 131 | while (id in channel_ids); 132 | channel_ids[id] = true; 133 | return id; 134 | } 135 | 136 | })(); 137 | -------------------------------------------------------------------------------- /examples/webworker-socketio-childproc/child-process.js: -------------------------------------------------------------------------------- 1 | var ASQ = require("../../node_modules/asynquence"), 2 | RemoteCSPChannel = require("../../"), 3 | TransportNodeStream = require("../../transports/nodestream"), 4 | 5 | fs = require("fs"); 6 | 7 | require("../../node_modules/asynquence-contrib/contrib-es6.src.js")(ASQ); 8 | 9 | RemoteCSPChannel.initCSP( 10 | /*API=*/ASQ.csp, 11 | /*channelFactory=*/ASQ.csp.chan, 12 | /*returnAsPromise=*/function $$promisewrap(v){ 13 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 14 | } 15 | ); 16 | 17 | 18 | ASQ().runner( ASQ.csp.go(childProc) ); 19 | 20 | 21 | // *********************** 22 | 23 | function *childProc(){ 24 | var transport_id = process.argv[2] || "CPTR", 25 | channel_id = process.argv[3] || "CPCH"; 26 | 27 | RemoteCSPChannel.defineTransport( 28 | /*transportID=*/transport_id, 29 | /*transport=*/TransportNodeStream, 30 | 31 | // transport args: 32 | { from: process.stdin, to: process.stdout } 33 | ); 34 | 35 | var ch = yield RemoteCSPChannel.openChannel(transport_id,channel_id); 36 | 37 | outputLog("(" + channel_id + ") channel opened"); 38 | 39 | //console.log("(" + channel_id + ") channel opened"); 40 | 41 | for (var i=0; i<1E9; i++) {} 42 | 43 | yield ASQ.csp.put(ch,i); 44 | 45 | outputLog("(" + channel_id + ") message sent"); 46 | 47 | //console.log("(" + channel_id + ") message sent"); 48 | } 49 | 50 | function outputLog(msg) { 51 | // var file = fs.openSync("output.txt","a"); 52 | // fs.writeSync(file,msg + "\n"); 53 | // fs.closeSync(file); 54 | } 55 | -------------------------------------------------------------------------------- /examples/webworker-socketio-childproc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Long-running Loop - Remote CSP (Web Worker, Socket.io, Node.js Child Process) 6 | 7 | 8 |

Long-running Loop - Remote CSP (Web Worker, Socket.io, Node.js Child Process)

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/webworker-socketio-childproc/sio-server.js: -------------------------------------------------------------------------------- 1 | var http = require("http"), 2 | server = http.createServer(router), 3 | io = require("socket.io")(server), 4 | spawn = require("child_process").spawn, 5 | 6 | ASQ = require("../../node_modules/asynquence"), 7 | RemoteCSPChannel = require("../../"), 8 | TransportSocketIO = require("../../transports/socketio"), 9 | TransportNodeStream = require("../../transports/nodestream"), 10 | 11 | transport_ids = {}, 12 | channel_ids = {}, 13 | 14 | transports_defined = {}; 15 | 16 | require("../../node_modules/asynquence-contrib/contrib-es6.src.js")(ASQ); 17 | 18 | RemoteCSPChannel.initCSP( 19 | /*API=*/ASQ.csp, 20 | /*channelFactory=*/ASQ.csp.chan, 21 | /*returnAsPromise=*/function $$promisewrap(v){ 22 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 23 | } 24 | ); 25 | 26 | server.listen(8005); 27 | 28 | ASQ().runner( 29 | ASQ.csp.go(function *main(ch){ 30 | spinUpServerSocket(ch.go); 31 | 32 | // keep alive (waiting for incoming socket requests) 33 | yield ASQ.csp.take(ch); 34 | }) 35 | ) 36 | .val(function(){ 37 | console.log("server complete"); 38 | }) 39 | .or(function(err){ 40 | console.log(err); 41 | }); 42 | 43 | 44 | // ********************* 45 | 46 | function spinUpServerSocket(go) { 47 | io.of("/csp").on("connection",function onconn(CSPsocket){ 48 | onCSPSocket(go,CSPsocket); 49 | 50 | for (var i=0; i<3; i++) { 51 | spinUpChildProcess(go); 52 | } 53 | }); 54 | } 55 | 56 | function onCSPSocket(go,CSPsocket) { 57 | var params = parseUri(CSPsocket.handshake.url).queryKey, 58 | transport_id = decodeURIComponent(params.tr), 59 | channel_id = decodeURIComponent(params.ch), 60 | channel_ch = ASQ.csp.chan(), 61 | id = transport_id + ":" + channel_id; 62 | 63 | // if socket is disconnected, close the channel_ch 64 | CSPsocket.on("disconnect",function ondisc(){ 65 | if (channel_ch) { 66 | channel_ch.close(); 67 | channel_ch = null; 68 | } 69 | }); 70 | 71 | if (!(id in transports_defined)) { 72 | RemoteCSPChannel.defineTransport( 73 | /*transportID=*/transport_id, 74 | /*transport=*/TransportSocketIO, 75 | 76 | // transport args: 77 | CSPsocket 78 | ); 79 | 80 | transports_defined[id] = true; 81 | } 82 | 83 | RemoteCSPChannel.openChannel(transport_id,channel_id) 84 | .then(function chReady(ch){ 85 | if (channel_ch) { 86 | ASQ.csp.putAsync(channel_ch,ch); 87 | } 88 | }); 89 | 90 | go(cspProc,[ channel_ch, channel_id ] ); 91 | } 92 | 93 | function spinUpChildProcess(go) { 94 | var transport_id = getNewTransportID("CPTR"), 95 | channel_id = getNewChannelID("CPCH"), 96 | child; 97 | 98 | child = spawn("node",[ "child-process.js", transport_id, channel_id ],{ 99 | cwd: process.cwd(), 100 | env: process.env, 101 | stdio: [ "pipe", "pipe", process.stderr ] 102 | }); 103 | 104 | RemoteCSPChannel.defineTransport( 105 | /*transportName=*/transport_id, 106 | /*transport=*/TransportNodeStream, 107 | 108 | // transport args: 109 | { from: child.stdout, to: child.stdin } 110 | ); 111 | 112 | go(remoteProc,[ transport_id, channel_id ]); 113 | } 114 | 115 | function *cspProc(_,channelCh,channelID) { 116 | var ch = yield ASQ.csp.take(channelCh); 117 | if (ch === ASQ.csp.CLOSED) { 118 | return; 119 | } 120 | channelCh.close(); 121 | 122 | console.log("(" + channelID + ") channel opened"); 123 | 124 | for (var i=0; i<1E9; i++) {} 125 | 126 | yield ASQ.csp.put(ch,i); 127 | 128 | console.log("(" + channelID + ") message sent"); 129 | } 130 | 131 | function *remoteProc(_,transportID,channelID) { 132 | var ch = yield RemoteCSPChannel.openChannel(transportID,channelID); 133 | console.log("(" + channelID + ") channel opened"); 134 | 135 | var num = yield ASQ.csp.take(ch); 136 | console.log("(" + channelID + ") message received:",num); 137 | } 138 | 139 | function getNewTransportID(prefix) { 140 | do { var id = prefix + ":" + Math.round(Math.random() * 1E9); } 141 | while (id in transport_ids); 142 | transport_ids[id] = true; 143 | return id; 144 | } 145 | 146 | function getNewChannelID(prefix) { 147 | do { var id = prefix + ":" + Math.round(Math.random() * 1E9); } 148 | while (id in channel_ids); 149 | channel_ids[id] = true; 150 | return id; 151 | } 152 | 153 | function router(req,res) { 154 | res.writeHead(200,{ "Content-Type": "text/plain" }); 155 | res.end("Nothing to see here."); 156 | } 157 | 158 | // adapted from: http://blog.stevenlevithan.com/archives/parseuri 159 | function parseUri(r){for(var e={key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}},o=e.parser.loose.exec(r),a={},s=14;s--;)a[e.key[s]]=o[s]||"";return a[e.q.name]={},a[e.key[12]].replace(e.q.parser,function(r,o,s){o&&(a[e.q.name][o]=s)}),a} 160 | -------------------------------------------------------------------------------- /examples/webworker-socketio-childproc/worker-process.js: -------------------------------------------------------------------------------- 1 | importScripts( 2 | "../../node_modules/asynquence/asq.js", 3 | "../../node_modules/asynquence-contrib/contrib-es6.src.js", 4 | "../../index.js", 5 | "../../transports/webworker/index.js" 6 | ); 7 | 8 | RemoteCSPChannel.initCSP( 9 | /*API=*/ASQ.csp, 10 | /*channelFactory=*/ASQ.csp.chan, 11 | /*returnAsPromise=*/function $$promisewrap(v){ 12 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 13 | } 14 | ); 15 | 16 | ASQ().runner( ASQ.csp.go(workerProc) ) 17 | .val(function(){ 18 | console.log("remote complete"); 19 | }); 20 | 21 | 22 | // *********************** 23 | 24 | function *workerProc(){ 25 | var params = parseHash(self.location.href), 26 | transport_id = params.tr || "WWTR", 27 | channel_id = params.ch || "WWCH"; 28 | 29 | RemoteCSPChannel.defineTransport( 30 | /*transportID=*/transport_id, 31 | /*transport=*/TransportWebWorker 32 | ); 33 | 34 | var ch = yield RemoteCSPChannel.openChannel(transport_id,channel_id); 35 | 36 | console.log("(" + channel_id + ") channel opened"); 37 | 38 | for (var i=0; i<1E9; i++) {} 39 | 40 | yield ASQ.csp.put(ch,i); 41 | 42 | console.log("(" + channel_id + ") message sent"); 43 | } 44 | 45 | // adapted from: http://blog.stevenlevithan.com/archives/parseuri 46 | function parseHash(t){var a="",e={};return/#./.test(t)&&(a=t.match(/#(.*)$/)[1],a.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,function(t,a,n){a&&(e[a]=n)})),e} 47 | -------------------------------------------------------------------------------- /examples/webworker-socketio-childproc/ww-sio-cp.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | "use strict"; 3 | 4 | var transport_ids = {}, 5 | channel_ids = {}; 6 | 7 | RemoteCSPChannel.initCSP( 8 | /*API=*/ASQ.csp, 9 | /*channelFactory=*/ASQ.csp.chan, 10 | /*returnAsPromise=*/function $$promisewrap(v){ 11 | return (ASQ.isSequence(v) ? v : ASQ(v)).toPromise(); 12 | } 13 | ); 14 | 15 | ASQ().runner( 16 | ASQ.csp.go(function *main(ch){ 17 | for (var i=0; i<1; i++) { 18 | spinUpWorkerProcess(ch.go); 19 | } 20 | 21 | spinUpServerProcess(ch.go); 22 | }) 23 | ) 24 | .val(function(){ 25 | console.log("local complete"); 26 | }); 27 | 28 | 29 | // **************************** 30 | 31 | function spinUpWorkerProcess(go) { 32 | var transport_id = getNewTransportID("WWTR"), 33 | channel_id = getNewChannelID("WWCH"); 34 | 35 | RemoteCSPChannel.defineTransport( 36 | /*transportID=*/transport_id, 37 | /*transport=*/TransportWebWorker, 38 | 39 | // transport args: 40 | new Worker("worker-process.js#tr=" + transport_id + "&ch=" + channel_id) 41 | ); 42 | 43 | go(remoteProc,[ transport_id, channel_id ]); 44 | } 45 | 46 | function spinUpServerProcess(go) { 47 | var transport_id = getNewTransportID("SIOTR"), 48 | channel_id = getNewChannelID("SIOCH"), 49 | 50 | CSPsocket = io( 51 | "http://localhost:8005/csp?" + 52 | "tr=" + transport_id + 53 | "&ch=" + channel_id 54 | ); 55 | 56 | RemoteCSPChannel.defineTransport( 57 | /*transportID=*/transport_id, 58 | /*transport=*/TransportSocketIO, 59 | 60 | // transport args: 61 | CSPsocket 62 | ); 63 | 64 | go(remoteProc,[ transport_id, channel_id ]); 65 | } 66 | 67 | function *remoteProc(_,transportID,channelID) { 68 | var ch = yield RemoteCSPChannel.openChannel(transportID,channelID); 69 | console.log("(" + channelID + ") channel opened"); 70 | 71 | var num = yield ASQ.csp.take(ch); 72 | console.log("(" + channelID + ") message received:",num); 73 | } 74 | 75 | function getNewTransportID(prefix) { 76 | do { var id = prefix + ":" + Math.round(Math.random() * 1E9); } 77 | while (id in transport_ids); 78 | transport_ids[id] = true; 79 | return id; 80 | } 81 | 82 | function getNewChannelID(prefix) { 83 | do { var id = prefix + ":" + Math.round(Math.random() * 1E9); } 84 | while (id in channel_ids); 85 | channel_ids[id] = true; 86 | return id; 87 | } 88 | 89 | })(); 90 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function UMD(name,context,definition){ 2 | if (typeof define === "function" && define.amd) { define(definition); } 3 | else if (typeof module !== "undefined" && module.exports) { module.exports = definition(); } 4 | else { context[name] = definition(name,context); } 5 | })("RemoteCSPChannel",this,function DEF(name,namespaceContext){ 6 | "use strict"; 7 | 8 | var transports = {}, 9 | channel_transport = {}, 10 | message_ids = {}, 11 | pre_channel_queue = {}, 12 | wrap_csp_return, 13 | channel_factory, 14 | orig_csp, 15 | rejected_pr = Promise.reject(), 16 | publicAPI = { 17 | initCSP: initCSP, 18 | defineTransport: defineTransport, 19 | openChannel: openChannel 20 | }; 21 | 22 | 23 | // opting out of false "unhandled rejection" warnings 24 | rejected_pr.catch(function noop(){}); 25 | 26 | return publicAPI; 27 | 28 | 29 | // *************************** 30 | 31 | function defaultPromiseWrap(v) { 32 | return Promise.resolve(v); 33 | } 34 | 35 | function defaultChannelFactory() { 36 | if (namespaceContext && 37 | typeof namespaceContext.chan !== "undefined" 38 | ) { 39 | return namespaceContext.chan.apply(null,arguments); 40 | } 41 | 42 | throw "Missing default channel factory method"; 43 | } 44 | 45 | function initCSP(CSP,channelFactory,wrapCSPReturn) { 46 | channel_factory = channelFactory || defaultChannelFactory; 47 | wrap_csp_return = wrapCSPReturn || defaultPromiseWrap; 48 | 49 | // save existing API methods 50 | orig_csp = { 51 | take: CSP.take, 52 | put: CSP.put, 53 | alts: CSP.alts, 54 | takem: CSP.takem, 55 | takeAsync: CSP.takeAsync, 56 | takemAsync: CSP.takemAsync, 57 | putAsync: CSP.putAsync, 58 | altsAsync: CSP.altsAsync 59 | }; 60 | 61 | // hijack CSP API with wrapped methods 62 | Object.keys(orig_csp).forEach(function eacher(key){ 63 | CSP[key] = makeCSPMethodWrapper(key,orig_csp); 64 | }); 65 | 66 | 67 | // *************************** 68 | 69 | function makeCSPMethodWrapper(methodName,orig) { 70 | return function $$wrapper(ch) { 71 | var args = [].slice.call(arguments); 72 | 73 | if ( 74 | (methodName == "alts" || methodName == "altsAsync") && 75 | Array.isArray(ch) 76 | ) { 77 | for (var i=0; i", 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /transports/nodestream/index.js: -------------------------------------------------------------------------------- 1 | (function UMD(name,context,definition){ 2 | if (typeof define === "function" && define.amd) { define(definition); } 3 | else if (typeof module !== "undefined" && module.exports) { module.exports = definition(); } 4 | else { context[name] = definition(name,context); } 5 | })("TransportNodeStream",this,function DEF(name,namespaceContext){ 6 | "use strict"; 7 | 8 | var context = "main", 9 | MESSAGE_DELIMITER = "\n^\n^\n^\n", 10 | MESSAGE_DELIMITER_RE = /\n\^\n\^\n\^\n/, 11 | 12 | publicAPI = { 13 | connect: connect 14 | }; 15 | 16 | return publicAPI; 17 | 18 | 19 | // ******************************* 20 | 21 | function connect(iostreams) { 22 | var msg_handler; 23 | 24 | iostreams.from.on("data",onMessage); 25 | 26 | return { 27 | onMessage: defineMessageHandler, 28 | sendMessage: sendMessage 29 | }; 30 | 31 | 32 | // ******************************* 33 | 34 | function defineMessageHandler(handler) { 35 | msg_handler = handler; 36 | } 37 | 38 | function sendMessage(msg) { 39 | msg["msg-source"] = context; 40 | msg = JSON.stringify(msg); 41 | iostreams.to.write(msg + MESSAGE_DELIMITER); 42 | } 43 | 44 | function onMessage(buf) { 45 | var msg = buf.toString(), 46 | msgs = msg.split(MESSAGE_DELIMITER_RE); 47 | 48 | for (var i=0; i 0) { 65 | start_evt = message_evts.shift(); 66 | onStart(start_evt); 67 | } 68 | } 69 | } 70 | 71 | if (workerObj) { 72 | if (connect_context != "main") { 73 | connect_context += "-main"; 74 | } 75 | setup(workerObj,workerObj); 76 | sendMessage({ start: true }); 77 | } 78 | else if (connect_context == "shared-worker") { 79 | if (!connect_evt) { 80 | self.addEventListener("connect",onConnect,false); 81 | } 82 | connect_evt = null; 83 | } 84 | else if (connect_context == "dedicated-worker") { 85 | if (!start_evt) { 86 | self.addEventListener("message",onStart,false); 87 | } 88 | start_evt = null; 89 | } 90 | 91 | return { 92 | onMessage: defineMessageHandler, 93 | sendMessage: sendMessage 94 | }; 95 | 96 | 97 | // ******************************* 98 | 99 | function defineMessageHandler(handler) { 100 | if (!msg_handler) { 101 | msg_handler = handler; 102 | 103 | if (message_evts.length > 0) { 104 | message_evts.forEach(onMessage); 105 | } 106 | message_evts.length = 0; 107 | } 108 | else { 109 | msg_handler = handler; 110 | } 111 | } 112 | 113 | function sendMessage(msg) { 114 | msg["msg-source"] = connect_context; 115 | 116 | if (!msg_target) { 117 | send_queue.push(msg); 118 | } 119 | else { 120 | msg = JSON.stringify(msg); 121 | msg_target.postMessage(msg); 122 | } 123 | } 124 | 125 | function onMessage(evt) { 126 | var msg; 127 | 128 | try { 129 | msg = JSON.parse(evt.data); 130 | } 131 | catch (err) { return; } 132 | 133 | if (msg_handler) { 134 | msg_handler(msg); 135 | } 136 | } 137 | 138 | function setup(source,target) { 139 | if (target) { 140 | msg_target = target; 141 | if (send_queue.length > 0) { 142 | send_queue.forEach(sendMessage); 143 | send_queue.length = 0; 144 | } 145 | } 146 | 147 | source.addEventListener("message", 148 | msg_target ? onMessage : waitForMsgTarget, 149 | false 150 | ); 151 | 152 | 153 | // ******************************* 154 | 155 | function waitForMsgTarget(evt) { 156 | source.removeEventListener("message",waitForMsgTarget,false); 157 | source.addEventListener("message",onMessage,false); 158 | 159 | msg_target = evt.source; 160 | send_queue.forEach(sendMessage); 161 | send_queue.length = 0; 162 | 163 | onMessage(evt); 164 | } 165 | } 166 | 167 | function onConnect(evt) { 168 | self.removeEventListener("connect",onConnect,false); 169 | 170 | var port = evt.ports[0]; 171 | setup(port,port); 172 | port.start(); 173 | } 174 | 175 | function onStart(evt) { 176 | try { 177 | var msg = JSON.parse(evt.data); 178 | } 179 | catch (err) { return; } 180 | 181 | if (msg.start) { 182 | self.removeEventListener("message",onStart,false); 183 | setup(self,self); 184 | } 185 | } 186 | } 187 | 188 | }); 189 | -------------------------------------------------------------------------------- /transports/webworker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.js" 3 | } 4 | --------------------------------------------------------------------------------