Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT PR] Workpop release #56

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
143 changes: 2 additions & 141 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,142 +1,3 @@
<img alt="Gi-SoftWare" src="https://raw.githubusercontent.com/raix/push/master/docs/logo.png" width="30%" height="30%">
raix:push Push notifications
=========
# THIS FORK IS NO LONGER BEING MAINTAINED.

> Push notifications for cordova (ios, android) browser (Chrome, Safari, Firefox) - One unified api on client and server.

Status:
* [x] APN iOS
* [x] GCM Android
* [x] APN Safari web push (partially implemented)
* [x] GCM Chrome OS (partially implemented)
* [x] Firefox OS (partially implemented)
* [ ] BPS Blackberry 10
* [ ] MPNS Windows phone 8
* [ ] MPNS Windows 8
* [ ] ADM Amazon Fire OS
* [ ] Meteor in app notifications

## Getting started
Depending on the platforms you want to work with you will need some credentials or certificates.
* [Android](docs/ANDROID.md)
* [iOS](docs/IOS.md)

Have a look at the [Basic example](docs/BASIC.md)

## Config
Add a `config.push.json` file in your project and configure credentials / keys / certificates:

```js
{
"apn": {
"passphrase": "xxxxxxxxx",
"key": "apnProdKey.pem",
"cert": "apnProdCert.pem"
},
"gcm": {
"apiKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"projectNumber": xxxxxxxxxxxx
},
"production": true,
// "badge": true,
// "sound": true,
// "alert": true,
// "vibrate": true
}
```

## Common api
```js
// Push.debug = true; // Add verbosity

Push.send({
from: 'push',
title: 'Hello',
text: 'world',
query: {
// Ex. send to a specific user if using accounts:
userId: 'xxxxxxxxx'
} // Query the appCollection
// token: appId or token eg. "{ apn: token }"
// tokens: array of appId's or tokens
// payload: user data
});
```
*When in secure mode the client send features require adding allow/deny rules in order to allow the user to send push messages to other users directly from the client - Read more below*

## Client api
```js
Push.id(); // Unified application id - not a token
Push.setBadge(count); // ios specific - ignored everywhere else
```

## Security allow/deny send
This package allows you to send notifications from the server and client. To restrict the client or allowing the client to send use `allow` or `deny` rules.

When a client calls send on Push, the Push's allow and deny callbacks are called on the server to determine if the send should be allowed. If at least one allow callback allows the send, and no deny callbacks deny the send, then the send is allowed to proceed.

```js
Push.allow({
send: function(userId, notification) {
return true; // Allow all users to send
}
});

// Or...
Push.deny({
send: function(userId, notification) {
return false; // Allow all users to send
}
});
```

## Meteor Methods

### raix:push-update

Stores a token associated with an application and optionally, a userId.

**Parameters**:

*options* - An object containing the necessary data to store a token. Fields:
* `id` - String (optional) - a record id for the Application/Token document to update. If this does not exist, will return 404.
* `token` - Object - `{ apn: 'TOKEN' }` or `{ gcm: 'TOKEN' }`
* `appName` - String - the name of the application to associate the token with
* `userId` - String (optional) - the user id so associate with the token and application. If none is included no user will be associated. Use `raix:push-setuser` to later associate a userId with a token.

**Returns**:

*recordId* - The id of the stored document associating appName, token, and optionally user in an object of the form:

```
{
result: 'recordId'
}
```

### raix:push-setuser

Associates the current users ID with an Application/Token record based on the given id.

**Parameters**:

*id* - String - The ID of the Application/Token record

### raix:push-metadata

Adds metadata to a particular Application/Token record.

**Parameters**

*data* - Object containing the following fields:
* `id` - String - the ID of the Application/Token record to update
* `metadata` - Object - The metadata object to add to the Application/Token document

## More Info


For more internal or advanced features read [ADVANCED.md](docs/ADVANCED.md)

Kind regards

Morten (aka RaiX)
> Head on over to the original [raix/push](http://github.com/raix/push) for updates.
52 changes: 22 additions & 30 deletions lib/common/notifications.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// Notifications collection
Push.notifications = new Mongo.Collection('_raix_push_notifications');

// This is a general function to validate that the data added to notifications
// is in the correct format. If not this function will throw errors
var _validateDocument = function(notification) {
Expand Down Expand Up @@ -30,12 +27,14 @@ var _validateDocument = function(notification) {
};

Push.send = function(options) {
// If on the client we set the user id - on the server we need an option
// set or we default to "<SERVER>" as the creator of the notification
if (Meteor.isClient) {
throw new Error("Cannot call Push.send from client");
}

// We default to "<SERVER>" as the creator of the notification
// If current user not set see if we can set it to the logged in user
// this will only run on the client if Meteor.userId is available
var currentUser = Meteor.isClient && Meteor.userId && Meteor.userId() ||
Meteor.isServer && (options.createdBy || '<SERVER>') || null;
var currentUser = (options.createdBy || '<SERVER>') || null;

// Rig the notification object
var notification = {
Expand Down Expand Up @@ -67,31 +66,24 @@ Push.send = function(options) {
_validateDocument(notification);

// Try to add the notification to send, we return an id to keep track
return Push.notifications.insert(notification);
return Push.serverSend(notification);
};

Push.allow = function(rules) {
if (rules.send) {
Push.notifications.allow({
'insert': function(userId, notification) {
// Validate the notification
_validateDocument(notification);
// Set the user defined "send" rules
return rules.send.apply(this, [userId, notification]);
}
});
Push.hasRegisteredToken = function(userId, appName) {
if (Meteor.isClient) {
throw new Error("Cannot call Push.hasRegisteredToken from the client.");
}
};

Push.deny = function(rules) {
if (rules.send) {
Push.notifications.deny({
'insert': function(userId, notification) {
// Validate the notification
_validateDocument(notification);
// Set the user defined "send" rules
return rules.send.apply(this, [userId, notification]);
}
});
// basic query will check userId has existing token field
var query = {
userId: userId,
token: { $exists: true }
};

// add appName to query if it was included
if (appName) {
query.appName = appName;
}
};

return Push.appCollection.find(query).count() > 0;
}
102 changes: 33 additions & 69 deletions lib/server/push.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,13 @@ Push.Configure = function(options) {
if (typeof notification.sound !== 'undefined') note.sound = notification.sound;

note.alert = notification.text;

// Allow the user to set payload data
note.payload = (notification.payload) ? { ejson: EJSON.stringify(notification.payload) } : {};
if (options.useRootJSONPayload) {
note.payload = notification.payload ? notification.payload : {};
} else {
note.payload = (notification.payload) ? { ejson: EJSON.stringify(notification.payload) } : {};
}

note.payload.messageFrom = notification.from;
note.priority = priority;
Expand Down Expand Up @@ -190,7 +195,11 @@ Push.Configure = function(options) {
var Fiber = Npm.require('fibers');

// Allow user to set payload
var data = (notification.payload) ? { ejson: EJSON.stringify(notification.payload) } : {};
if (options.useRootJSONPayload) {
var data = notification.payload ? notification.payload : {};
} else {
var data = (notification.payload) ? { ejson: EJSON.stringify(notification.payload) } : {};
}

data.title = notification.title;
data.message = notification.text;
Expand Down Expand Up @@ -341,32 +350,31 @@ Push.Configure = function(options) {
};
};

self.serverSend = function(options) {
options = options || { badge: 0 };
var query;

// Check basic options
if (options.from !== ''+options.from)
throw new Error('Push.send: option "from" not a string');

if (options.title !== ''+options.title)
throw new Error('Push.send: option "title" not a string');

if (options.text !== ''+options.text)
throw new Error('Push.send: option "text" not a string');
/**
* Sends the given push notification immediately. Sends to apple/google depending
* on the included token
*
* @param notification - {Object} The push notification object to send.
* @returns - {Object} - Send status containing number of apn/gcn sent.
*/
self.serverSend = function(notification) {
if (!notification) {
return;
}

if (options.token || options.tokens) {
var query;

// The user set one token or array of tokens
var tokenList = (options.token)? [options.token] : options.tokens;
if (notification.token || notification.tokens) {
// if token is set wrap in array, otherwise use tokens array
var tokenList = (notification.token) ? [notification.token] : notification.tokens;

if (Push.debug) console.log('Push: Send message "' + options.title + '" via token(s)', tokenList);
if (Push.debug) console.log('Push: Send message "' + notification.title + '" via token(s)', tokenList);

query = {
$or: [
// XXX: Test this query: can we hand in a list of push tokens?
// TODO: Test this query: can we hand in a list of push tokens?
{ token: { $in: tokenList } },
// XXX: Test this query: does this work on app id?
// TODO: Test this query: does this work on app id?
{ $and: [
{ _in: { $in: tokenList } }, // one of the app ids
{ $or: [
Expand All @@ -377,13 +385,13 @@ Push.Configure = function(options) {
]
};

} else if (options.query) {
} else if (notification.query) {

if (Push.debug) console.log('Push: Send message "' + options.title + '" via query', options.query);
if (Push.debug) console.log('Push: Send message "' + notification.title + '" via query', notification.query);

query = {
$and: [
options.query, // query object
notification.query, // query object
{ $or: [
{ 'token.apn': { $exists: true } }, // got apn token
{ 'token.gcm': { $exists: true } } // got gcm token
Expand All @@ -394,56 +402,12 @@ Push.Configure = function(options) {


if (query) {

// Convert to querySend and return status
return _querySend(query, options)

return _querySend(query, notification)
} else {
throw new Error('Push.send: please set option "token"/"tokens" or "query"');
}

};

var isSendingNotification = false;

Meteor.setInterval(function() {

if (!isSendingNotification) {
// Set send fence
isSendingNotification = true;

// Find one notification
var notification = Push.notifications.findOne({ sent : { $ne: true } }, { sort: { createdAt: 1 } });

// Check if we got any notifications to send
if (notification) {

// Send the notification
var result = Push.serverSend(notification);

if (!options.keepNotifications) {
// Pr. Default we will remove notifications
Push.notifications.remove({ _id: notification._id });
} else {

// Update the notification
Push.notifications.update({ _id: notification._id }, {
$set: {
sent: true,
sentAt: new Date(),
count: result
}
});

}

// Emit the send
self.emit('send', { notification: notification._id, result: result });
}

// Remove the send fence
isSendingNotification = false;
}
}, options.sendInterval || 15000); // Default every 15'the sec

};
Loading