parent
079dd23c73
commit
ff5e32a047
@ -0,0 +1,22 @@ |
||||
(The MIT License) |
||||
|
||||
Copyright (c) 2013 Arunoda Susiripala <arunoda.susiripala@gmail.com> |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining |
||||
a copy of this software and associated documentation files (the |
||||
'Software'), to deal in the Software without restriction, including |
||||
without limitation the rights to use, copy, modify, merge, publish, |
||||
distribute, sublicense, and/or sell copies of the Software, and to |
||||
permit persons to whom the Software is furnished to do so, subject to |
||||
the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be |
||||
included in all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
@ -0,0 +1,10 @@ |
||||
# Meteor Streams [](https://travis-ci.org/arunoda/meteor-streams) |
||||
|
||||
DB less realtime communication for meteor |
||||
|
||||
## Development Status |
||||
Project development status is [inactive](https://github.com/arunoda/meteor-streams/issues/21#issuecomment-59030380). |
||||
|
||||
## [Documentation](http://arunoda.github.io/meteor-streams/) |
||||
|
||||
[](http://arunoda.github.io/meteor-streams/) |
||||
@ -0,0 +1,48 @@ |
||||
Meteor.Stream = function Stream(name, callback) { |
||||
EV.call(this); |
||||
|
||||
var self = this; |
||||
var streamName = 'stream-' + name; |
||||
var collection = new Meteor.Collection(streamName); |
||||
var subscription; |
||||
var subscriptionId; |
||||
|
||||
var connected = false; |
||||
var pendingEvents = []; |
||||
|
||||
self._emit = self.emit; |
||||
|
||||
collection.find({}).observe({ |
||||
"added": function(item) { |
||||
if(item.type == 'subscriptionId') { |
||||
subscriptionId = item._id; |
||||
connected = true; |
||||
pendingEvents.forEach(function(args) { |
||||
self.emit.apply(self, args); |
||||
}); |
||||
pendingEvents = []; |
||||
} else { |
||||
var context = {}; |
||||
context.subscriptionId = item.subscriptionId; |
||||
context.userId = item.userId; |
||||
self._emit.apply(context, item.args);
|
||||
} |
||||
} |
||||
}); |
||||
|
||||
subscription = Meteor.subscribe(streamName, callback); |
||||
|
||||
self.emit = function emit() { |
||||
if(connected) { |
||||
Meteor.call(streamName, subscriptionId, arguments); |
||||
} else { |
||||
pendingEvents.push(arguments); |
||||
} |
||||
}; |
||||
|
||||
self.close = function close() { |
||||
subscription.stop(); |
||||
}; |
||||
} |
||||
|
||||
_.extend(Meteor.Stream.prototype, EV.prototype); |
||||
@ -0,0 +1,44 @@ |
||||
function _EV() { |
||||
var self = this; |
||||
var handlers = {}; |
||||
|
||||
self.emit = function emit(event) { |
||||
var args = Array.prototype.slice.call(arguments, 1); |
||||
|
||||
if(handlers[event]) { |
||||
for(var lc=0; lc<handlers[event].length; lc++) { |
||||
var handler = handlers[event][lc]; |
||||
handler.apply(this, args); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
self.on = function on(event, callback) { |
||||
if(!handlers[event]) { |
||||
handlers[event] = []; |
||||
} |
||||
handlers[event].push(callback); |
||||
}; |
||||
|
||||
self.once = function once(event, callback) { |
||||
self.on(event, function onetimeCallback() { |
||||
callback.apply(this, arguments); |
||||
self.removeListener(event, onetimeCallback); |
||||
}); |
||||
}; |
||||
|
||||
self.removeListener = function removeListener(event, callback) { |
||||
if(handlers[event]) { |
||||
var index = handlers[event].indexOf(callback); |
||||
console.log(index); |
||||
if (index > -1) |
||||
handlers[event].splice(index, 1); |
||||
} |
||||
}; |
||||
|
||||
self.removeAllListeners = function removeAllListeners(event) { |
||||
handlers[event] = undefined; |
||||
}; |
||||
} |
||||
|
||||
EV = _EV; |
||||
@ -0,0 +1,114 @@ |
||||
var EventEmitter = Npm.require('events').EventEmitter; |
||||
var util = Npm.require('util'); |
||||
var Fibers = Npm.require('fibers'); |
||||
|
||||
Meteor.Stream = function Stream(name) { |
||||
EV.call(this); |
||||
|
||||
var self = this; |
||||
var streamName = 'stream-' + name; |
||||
var allowFunction; |
||||
var allowResultCache = true; |
||||
var allowResults = {}; |
||||
var filters = []; |
||||
|
||||
self.name = name; |
||||
|
||||
var events = new EventEmitter(); |
||||
events.setMaxListeners(0); |
||||
|
||||
var disconnectEvents = new EV(); |
||||
|
||||
self._emit = self.emit; |
||||
self.emit = function emit() { |
||||
self.emitToSubscriptions(arguments, null, null); |
||||
}; |
||||
|
||||
var defaultResult = (typeof(Package) == 'object' && Package.insecure)? true: Meteor.Collection.insecure === true; |
||||
self.permissions = new Meteor.Stream.Permission(defaultResult, true); |
||||
|
||||
self.addFilter = function addFilter(callback) { |
||||
filters.push(callback); |
||||
}; |
||||
|
||||
self.emitToSubscriptions = function emitToSubscriptions(args, subscriptionId, userId) { |
||||
events.emit('item', {args: args, userId: userId, subscriptionId: subscriptionId}); |
||||
}; |
||||
|
||||
Meteor.publish(streamName, function() { |
||||
check(arguments, Match.Any); |
||||
var subscriptionId = Random.id(); |
||||
var publication = this; |
||||
|
||||
//send subscription id as the first document
|
||||
publication.added(streamName, subscriptionId, {type: 'subscriptionId'}); |
||||
publication.ready(); |
||||
events.on('item', onItem); |
||||
|
||||
function onItem(item) { |
||||
Fibers(function() { |
||||
var id = Random.id(); |
||||
if(self.permissions.checkPermission('read', subscriptionId, publication.userId, item.args)) { |
||||
//do not send again this to the sender
|
||||
if(subscriptionId != item.subscriptionId) { |
||||
publication.added(streamName, id, item); |
||||
publication.removed(streamName, id); |
||||
} |
||||
} |
||||
}).run(); |
||||
} |
||||
|
||||
publication.onStop(function() { |
||||
//trigger related onDisconnect handlers if exists
|
||||
Fibers(function() { |
||||
disconnectEvents.emit(subscriptionId); |
||||
disconnectEvents.removeAllListeners(subscriptionId); |
||||
}).run(); |
||||
events.removeListener('item', onItem); |
||||
}); |
||||
}); |
||||
|
||||
var methods = {}; |
||||
methods[streamName] = function(subscriptionId, args) { |
||||
check(arguments, Match.Any); |
||||
//in order to send this to the server callback
|
||||
var userId = this.userId; |
||||
Fibers(function() { |
||||
var methodContext = {}; |
||||
methodContext.userId = userId; |
||||
methodContext.subscriptionId = subscriptionId; |
||||
|
||||
//in order to send this to the serve callback
|
||||
methodContext.allowed = self.permissions.checkPermission('write', subscriptionId, methodContext.userId, args); |
||||
if(methodContext.allowed) { |
||||
//apply filters
|
||||
args = applyFilters(args, methodContext); |
||||
self.emitToSubscriptions(args, subscriptionId, methodContext.userId); |
||||
//send to firehose if exists
|
||||
if(self.firehose) { |
||||
self.firehose(args, subscriptionId, methodContext.userId); |
||||
} |
||||
} |
||||
//need to send this to server always
|
||||
self._emit.apply(methodContext, args); |
||||
|
||||
//register onDisconnect handlers if provided
|
||||
if(typeof(methodContext.onDisconnect) == 'function') { |
||||
disconnectEvents.on(subscriptionId, methodContext.onDisconnect) |
||||
} |
||||
|
||||
}).run(); |
||||
}; |
||||
Meteor.methods(methods); |
||||
|
||||
function applyFilters(args, context) { |
||||
var eventName = args.shift(); |
||||
filters.forEach(function(filter) { |
||||
args = filter.call(context, eventName, args); |
||||
}); |
||||
args.unshift(eventName); |
||||
return args; |
||||
} |
||||
}; |
||||
|
||||
util.inherits(Meteor.Stream, EV); |
||||
@ -0,0 +1,42 @@ |
||||
Meteor.Stream.Permission = function (acceptAll, cacheAll) { |
||||
var options = { |
||||
"read": { |
||||
results: {} |
||||
},
|
||||
"write": { |
||||
results: {} |
||||
} |
||||
}; |
||||
|
||||
this.read = function(func, cache) { |
||||
options['read']['func'] = func; |
||||
options['read']['doCache'] = (cache === undefined)? cacheAll: cache;
|
||||
}; |
||||
|
||||
this.write = function(func, cache) { |
||||
options['write']['func'] = func; |
||||
options['write']['doCache'] = (cache === undefined)? cacheAll: cache;
|
||||
}; |
||||
|
||||
this.checkPermission = function(type, subscriptionId, userId, args) { |
||||
var eventName = args[0]; |
||||
var namespace = subscriptionId + '-' + eventName; |
||||
var result = options[type].results[namespace]; |
||||
|
||||
if(result === undefined) { |
||||
var func = options[type].func; |
||||
if(func) { |
||||
var context = {subscriptionId: subscriptionId, userId: userId}; |
||||
result = func.apply(context, args); |
||||
if(options[type].doCache) { |
||||
options[type].results[namespace] = result; |
||||
} |
||||
return result; |
||||
} else { |
||||
return acceptAll; |
||||
} |
||||
} else { |
||||
return result; |
||||
} |
||||
};
|
||||
} |
||||
@ -0,0 +1,11 @@ |
||||
Package.describe({ |
||||
name: 'arunoda:streams', |
||||
version: '0.1.17', |
||||
summary: "DB less realtime communication for meteor" |
||||
}); |
||||
|
||||
Package.on_use(function (api, where) { |
||||
api.use('underscore', ['client', 'server']); |
||||
api.add_files(['lib/ev.js', 'lib/server.js', 'lib/stream_permission.js'], 'server'); |
||||
api.add_files(['lib/ev.js', 'lib/client.js'], 'client'); |
||||
}); |
||||
Loading…
Reference in new issue