Skip to content

Commit 2ee3cd1

Browse files
committed
Merge with master
2 parents fdc85cc + 79bc313 commit 2ee3cd1

20 files changed

+1627
-113
lines changed

APNS.js

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
var Parse = require('parse/node').Parse;
2+
// TODO: apn does not support the new HTTP/2 protocal. It is fine to use it in V1,
3+
// but probably we will replace it in the future.
4+
var apn = require('apn');
5+
6+
/**
7+
* Create a new connection to the APN service.
8+
* @constructor
9+
* @param {Object} args Arguments to config APNS connection
10+
* @param {String} args.cert The filename of the connection certificate to load from disk, default is cert.pem
11+
* @param {String} args.key The filename of the connection key to load from disk, default is key.pem
12+
* @param {String} args.passphrase The passphrase for the connection key, if required
13+
* @param {Boolean} args.production Specifies which environment to connect to: Production (if true) or Sandbox
14+
*/
15+
function APNS(args) {
16+
this.sender = new apn.connection(args);
17+
18+
this.sender.on('connected', function() {
19+
console.log('APNS Connected');
20+
});
21+
22+
this.sender.on('transmissionError', function(errCode, notification, device) {
23+
console.error('APNS Notification caused error: ' + errCode + ' for device ', device, notification);
24+
// TODO: For error caseud by invalid deviceToken, we should mark those installations.
25+
});
26+
27+
this.sender.on("timeout", function () {
28+
console.log("APNS Connection Timeout");
29+
});
30+
31+
this.sender.on("disconnected", function() {
32+
console.log("APNS Disconnected");
33+
});
34+
35+
this.sender.on("socketError", console.error);
36+
}
37+
38+
/**
39+
* Send apns request.
40+
* @param {Object} data The data we need to send, the format is the same with api request body
41+
* @param {Array} deviceTokens A array of device tokens
42+
* @returns {Object} A promise which is resolved immediately
43+
*/
44+
APNS.prototype.send = function(data, deviceTokens) {
45+
var coreData = data.data;
46+
var expirationTime = data['expiration_time'];
47+
var notification = generateNotification(coreData, expirationTime);
48+
this.sender.pushNotification(notification, deviceTokens);
49+
// TODO: pushNotification will push the notification to apn's queue.
50+
// We do not handle error in V1, we just relies apn to auto retry and send the
51+
// notifications.
52+
return Parse.Promise.as();
53+
}
54+
55+
/**
56+
* Generate the apns notification from the data we get from api request.
57+
* @param {Object} coreData The data field under api request body
58+
* @returns {Object} A apns notification
59+
*/
60+
var generateNotification = function(coreData, expirationTime) {
61+
var notification = new apn.notification();
62+
var payload = {};
63+
for (key in coreData) {
64+
switch (key) {
65+
case 'alert':
66+
notification.setAlertText(coreData.alert);
67+
break;
68+
case 'badge':
69+
notification.badge = coreData.badge;
70+
break;
71+
case 'sound':
72+
notification.sound = coreData.sound;
73+
break;
74+
case 'content-available':
75+
notification.setNewsstandAvailable(true);
76+
var isAvailable = coreData['content-available'] === 1;
77+
notification.setContentAvailable(isAvailable);
78+
break;
79+
case 'category':
80+
notification.category = coreData.category;
81+
break;
82+
default:
83+
payload[key] = coreData[key];
84+
break;
85+
}
86+
}
87+
notification.payload = payload;
88+
notification.expiry = expirationTime;
89+
return notification;
90+
}
91+
92+
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
93+
APNS.generateNotification = generateNotification;
94+
}
95+
module.exports = APNS;

CONTRIBUTING.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ We really want Parse to be yours, to see it grow and thrive in the open source c
66

77
##### Please Do's
88

9-
* Please write tests to cover new methods.
10-
* Please run the tests and make sure you didn't break anything.
9+
* Take testing seriously! Aim to increase the test coverage with every pull request.
10+
* Run the tests for the file you are working on with `TESTING=1 (repo-root)/node_modules/jasmine/bin/jasmine.js spec/MyFile.spec.js`
11+
* Run the tests for the whole project and look at the coverage report to make sure your tests are exhaustive by running `npm test` and looking at (project-root)/lcov-report/parse-server/FileUnderTest.js.html
1112

1213
##### Code of Conduct
1314

ExportAdapter.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,7 @@ ExportAdapter.prototype.connect = function() {
6060
var joinRegex = /^_Join:[A-Za-z0-9_]+:[A-Za-z0-9_]+/;
6161
var otherRegex = /^[A-Za-z][A-Za-z0-9_]*$/;
6262
ExportAdapter.prototype.collection = function(className) {
63-
if (className !== '_User' &&
64-
className !== '_Installation' &&
65-
className !== '_Session' &&
66-
className !== '_SCHEMA' &&
67-
className !== '_Role' &&
68-
!joinRegex.test(className) &&
69-
!otherRegex.test(className)) {
63+
if (!Schema.classNameIsValid(className)) {
7064
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME,
7165
'invalid className: ' + className);
7266
}
@@ -500,6 +494,7 @@ ExportAdapter.prototype.smartFind = function(coll, where, options) {
500494

501495
var index = {};
502496
index[key] = '2d';
497+
//TODO: condiser moving index creation logic into Schema.js
503498
return coll.createIndex(index).then(() => {
504499
// Retry, but just once.
505500
return coll.find(where, options).toArray();

GCM.js

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
var Parse = require('parse/node').Parse;
2+
var gcm = require('node-gcm');
3+
var randomstring = require('randomstring');
4+
5+
var GCMTimeToLiveMax = 4 * 7 * 24 * 60 * 60; // GCM allows a max of 4 weeks
6+
var GCMRegistrationTokensMax = 1000;
7+
8+
function GCM(apiKey) {
9+
this.sender = new gcm.Sender(apiKey);
10+
}
11+
12+
/**
13+
* Send gcm request.
14+
* @param {Object} data The data we need to send, the format is the same with api request body
15+
* @param {Array} registrationTokens A array of registration tokens
16+
* @returns {Object} A promise which is resolved after we get results from gcm
17+
*/
18+
GCM.prototype.send = function (data, registrationTokens) {
19+
if (registrationTokens.length >= GCMRegistrationTokensMax) {
20+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
21+
'Too many registration tokens for a GCM request.');
22+
}
23+
var pushId = randomstring.generate({
24+
length: 10,
25+
charset: 'alphanumeric'
26+
});
27+
var timeStamp = Date.now();
28+
var expirationTime;
29+
// We handle the expiration_time convertion in push.js, so expiration_time is a valid date
30+
// in Unix epoch time in milliseconds here
31+
if (data['expiration_time']) {
32+
expirationTime = data['expiration_time'];
33+
}
34+
// Generate gcm payload
35+
var gcmPayload = generateGCMPayload(data.data, pushId, timeStamp, expirationTime);
36+
// Make and send gcm request
37+
var message = new gcm.Message(gcmPayload);
38+
var promise = new Parse.Promise();
39+
this.sender.send(message, { registrationTokens: registrationTokens }, 5, function (error, response) {
40+
// TODO: Use the response from gcm to generate and save push report
41+
// TODO: If gcm returns some deviceTokens are invalid, set tombstone for the installation
42+
promise.resolve();
43+
});
44+
return promise;
45+
}
46+
47+
/**
48+
* Generate the gcm payload from the data we get from api request.
49+
* @param {Object} coreData The data field under api request body
50+
* @param {String} pushId A random string
51+
* @param {Number} timeStamp A number whose format is the Unix Epoch
52+
* @param {Number|undefined} expirationTime A number whose format is the Unix Epoch or undefined
53+
* @returns {Object} A promise which is resolved after we get results from gcm
54+
*/
55+
var generateGCMPayload = function(coreData, pushId, timeStamp, expirationTime) {
56+
var payloadData = {
57+
'time': new Date(timeStamp).toISOString(),
58+
'push_id': pushId,
59+
'data': JSON.stringify(coreData)
60+
}
61+
var payload = {
62+
priority: 'normal',
63+
data: payloadData
64+
};
65+
if (expirationTime) {
66+
// The timeStamp and expiration is in milliseconds but gcm requires second
67+
var timeToLive = Math.floor((expirationTime - timeStamp) / 1000);
68+
if (timeToLive < 0) {
69+
timeToLive = 0;
70+
}
71+
if (timeToLive >= GCMTimeToLiveMax) {
72+
timeToLive = GCMTimeToLiveMax;
73+
}
74+
payload.timeToLive = timeToLive;
75+
}
76+
return payload;
77+
}
78+
79+
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
80+
GCM.generateGCMPayload = generateGCMPayload;
81+
}
82+
module.exports = GCM;

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Read the migration guide here: https://parse.com/docs/server/guide#migrating
1212

1313
There is a development wiki here on GitHub: https://github.com/ParsePlatform/parse-server/wiki
1414

15+
We also have an [example project](https://github.com/ParsePlatform/parse-server-example) using the parse-server module on Express.
16+
1517
---
1618

1719
#### Basic options:
@@ -58,7 +60,7 @@ var api = new ParseServer({
5860
databaseURI: 'mongodb://localhost:27017/dev',
5961
cloud: '/home/myApp/cloud/main.js', // Provide an absolute path
6062
appId: 'myAppId',
61-
masterKey: 'mySecretMasterKey',
63+
masterKey: '', //Add your master key here. Keep it secret!
6264
fileKey: 'optionalFileKey',
6365
serverURL: 'http://localhost:' + port + '/parse' // Don't forget to change to https if needed
6466
});

0 commit comments

Comments
 (0)