This package helps you create activity streams & newsfeeds with MeteorJS and GetStream.io.
You can build:
- Activity streams such as seen on Github
- A twitter style newsfeed
- A feed like instagram/pinterest
- Facebook style newsfeeds
- A notification system
You can check out our example app built using this library on Github https://github.com/GetStream/Stream-Example-Meteor
Add the package to your Meteor by app by running the following command:
meteor add getstream:stream-meteor
Add the following keys to your settings.json (Meteor settings) with their respective values retrieved from your dashboard
"streamApiSecret": "",
"public" : {
"streamApiKey": "",
"streamApiAppId": ""
}
Read more about how to use a settings.json file at the Meteor Chef
The architecture of this meteor package is based on the concepts portrayed in this article, read it to get a better understanding of the difficulties that arise when implementing news feeds in Meteor.
This Meteor integration package is divided into two separate parts. The first is the part responsible for publishing feeds so you can retrieve activities for specific feeds on your clients (see section Publications). The second is responsible for automatically creating activities on the getstream.io API when an item is added to an (activity) Collection (see section Collection integration).
The following keys can be configured in your Meteor.settings:
- streamApiSecret the secret key for your getstream app
- streamApiKey the api key for your getstream app
- streamApiAppId the api app id for your getstream app
- userFeed the name of the user feed group, defaults to 'user'
- notificationFeed the name of the notification feed group, defaults to 'notification'
- newsFeeds the newsfeed groups available defaults to, where property is a feed group and the value is the feed type:
{
"flat": "flat",
"aggregated": "aggregated"
}
Be sure to set the userFeed, notificationFeed, and newsFeeds on the public Meteor settings, this way you can also use the FeedManager on the client and the correct Collections can be setup on the client.
Read more about using these feeds in the section FeedManager
This package automatically creates publications for the news feed groups you have defined in your settings file (or the feed groups defined by default). All publications are namespaced starting with 'Stream.feeds' followed by the name of the feed group, so for feed group 'user' we can subscribe to publication 'Stream.feeds.user'. The publication can handle two parameters, the first is the limit on how many activities to retrieve from the feed (defaults to twenty). And the second is the id of the feed to retrieve from the feed group (defaults to the current user's id). So the following code subscribes to feed with id 'some-feed-id' on feed group 'user' with a limit of 10 activities:
Meteor.subscribe('Stream.feeds.user', 10, 'some-feed-id');
A publication stores it values in an 'synthetic' collection on Stream.feeds
with the name of the feed group. For above code the collection is Stream.feeds.user
. Notice that these collections aren't actually stored in MongoDB they are only used to communicate feed activities to the client over DDP and store them in Minimongo (i.e. why they are called 'synthetic colelctions). Once we retrieve objects from this collection on the client the activities are automatically enriched (see section Activity enrichment), for this process to be possible on the client we have to be subscribed to the correct collections. Luckily the feed publication already returns the correct cursors needed to enrich the activities in this feed, we only have to make sure we wait for the subscription to be ready before we retrieve the corresponding activities from our collection. The desired behavior can for instance be achieved by using iron-router's waitOn function:
Router.route('/flat', {
waitOn: function() {
return Meteor.subscribe('Stream.feeds.user');
},
action: function() {
var enrichedActivities = Stream.feeds.user.find().fetch();
// Do something with our activities (i.e. render a template)
},
});
Or inside a template onCreated handler:
Template.someTemplate.onCreated(function() {
this.autorun(function() {
var subscription = Meteor.subscribe('Stream.feeds.user');
if(subscription.ready()) {
var enrichedActivities = Stream.feeds.user.find().fetch();
// Do something with the enriched activities, normally you would store them in a
// reactive variable and retrieve this var with a helper method in your template
}
});
});
Stream Meteor can automatically publish new activities to your feeds. To do that you only need to register the collections you want to publish with this library.
Tweets = Mongo.Collection.('tweets');
Stream.registerActivity(Tweets, {
activityVerb: 'tweet',
});
Every time a Tweet is created it will be added to the user's feed. Users which follow the given user will also automatically get the new tweet in their feeds.
Collections are stored in feeds as activities. An activity is composed of at least the following fields: actor, verb, object, time. You can also add more custom data if needed. By registering a collection as an activity collection the integration library tries to setup things automatically:
object is a reference to the collection instance actor is a reference to the actor attribute of the instance
By default the actor field will look for an attribute called actor and a field called created_at to track creation time. If you're user field is called differently you'll need to tell us where to look for it. Below shows an example how to set things up if your actor field is called author.
Stream.registerActivity(Tweets, {
activityActorProp: 'author';
});
Often you'll want to store more data than just the basic fields. You achieve this by implementing the extraActivityData method on the document instance:
Stream.registerActivity(Tweets, {
activityVerb: 'tweet',
activityExtraData: function() {
return {
'isRetweet': this.isRetweet
};
}
});
Serialized extra data fields are important for the automatic publication of the correct Collection cursors. These fields can be automatically enriched during a fetch on the client. For instance if you have created a Collection 'likes' wich stores likes with a reference to an 'item' identifier:
Stream.registerActivity(Likes, {
activityVerb: 'like'
});
Likes.insert({
'item': item._id
});
The item can not be automatically enriched on the client we only retrieve the Like document from MongoDB. Thus the activities object field wil reference:
{
//...
object: /* our like document */
}
If we however specify to store the item as extra data:
Stream.registerActivity(Likes, {
activityVerb: 'like',
activityExtraData: function() {
return {
'item': 'items:' + this.item,
}
}
});
The enriched activity will have a property with a reference to our item document:
{
//..
object: /* like document */,
item: /* item document */,
}
- activityNotify array of feeds to notify when document is inserted into collection
- activityActorFeed which feed the activity is added to on creation
- activityTime created at time set on the activity object that is send to the getstream API
- activityActorProp which property holds the user id of current activity's actor
- activityExtraData extra data set on the activity object that is send to the getstream API
activityNotify should be a function returning a list of feeds retrieved from the FeedManager, so for example:
activityNotify: function() {
targetFeed = Stream.feedManager.getNotificationFeed(this.target);
return [targetFeed];
},
This packages comes with a FeedManager class that helps with all common feed operations. This class can be accessed from both the client and the server, to use the FeedManager on the client consult the section Using feed manager on the client.
To get you started the manager has 4 feed groups pre configured. You can add more feeds if your application needs it. The feeds are divided in three categories.
The user feed stores all activities for a user. Think of it as your personal Facebook page. You can easily get this feed from the manager.
Stream.feedManager.getUserFeed(this.userId);
The news feeds store the activities from the people you follow. There is both a flat newsfeed (similar to twitter) and an aggregated newsfeed (like facebook).
var flatFeed = Stream.feedManager.getNewsFeeds(foundUser.id)['flat'];
var aggregatedFeed = Stream.feedManager.getNewsFeeds(this.user)['aggregated'];
The notification feed can be used to build notification functionality.
var notificationFeed = Stream.feedManager.getNotificationFeed(this.userId);
Notification feeds are aggregated feeds that have two additional properties, a counter holding the amount of
unread notifications and a counter holding the amount of unseen notifications. The Meteor library exposes these
statistics on a Mongo collection under Stream.notifications, here you can query the amount of unread and unseen activities for any notification feed via { feedGroup: 'FEED_GROUP_NAME', feedId: 'USER_ID' }
, the FeedManager
also exposes a method called getNotificationFeedStats
which can be used to retrieve the notification feed stats for your notification feed defined in settings.json
:
var stats = Stream.feedManager.getNotificationFeedStats(this.userId);
// Now you can access stats.unread and stats.unseen
These values will update reactive automatically when you are subsribed to the publication belonging to this notification feed.
The create the newsfeeds you need to notify the system about follow relationships. The manager comes with APIs to let a user's news feeds follow another user's feed. This code lets the current user's flat and aggregated feeds follow the target_user's personal feed.
Stream.feedManager.followUser(userId, targetId);
The server has access to your getstream.io secret key, this makes it possible to edit feeds of any user. For obvious security reasons the client does not have access to all feeds, and to grant it access to the feed of the current user we need to generate a feed token.
var notificationFeedToken = Stream.feedManager.getNotificationFeedToken(this.userId);
Use Meteor (server) methods to retrieve feed tokens from the client e.g.:
if(Meteor.isServer) {
Meteor.methods({
'notificationFeedToken': function() {
if(! this.userId) {
throw new Meteor.Error('not-authorized');
}
return Stream.feedManager.getNotificationFeedToken(this.userId);
}
});
}
To subscribe to the notification feed on the client call the method and retrieve a feed instance from the feedManager:
Meteor.call('notificationFeedToken', function(err, token) {
if(err) console.error(err);
var feed = Stream.feedManager.notificationFeed(Meteor.userId(), token);
feed.subscribe(function(data) {
// Respond to data
});
});
If you use the feedManager in this way on the client we can not handle automatic activity enrichment for you and you will have to manage the needed subscriptions for this yourself.
When you read data from feeds, a like activity will look like this:
{'actor': 'User:1', 'verb': 'like', 'object': 'Like:42'}
This is far from ready for usage in your template. We call the process of loading the references from the database enrichment. Collections created by feed publications (see section Publications) handle this enrichment when they are fetched automatically.
When needed you can also use the low level JS API directly. Stream.stream
exposes the low-level javascript client on the server. If you want to access an instance of the client directly use Stream.feedManager.client
. For more information on how to use the low-level javascript client visit the getstream.io documentation.
To run the tests first install the velocity-cli:
npm install -g velocity-cli
Then run velocity test-packages --settings test/settings.json
from the package directory. The test page is now served at http://localhost:3000
.