Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1 from argon/feature/v2
Browse files Browse the repository at this point in the history
Reimplementation with node-apn v2
  • Loading branch information
argon committed Jul 26, 2016
2 parents a956465 + 01ad2d9 commit 56902a6
Show file tree
Hide file tree
Showing 14 changed files with 489 additions and 123 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
language: node_js
node_js:
- v6.0.0
after_success:
- npm install -g istanbul
- npm install coveralls
- istanbul cover node_modules/.bin/_mocha --report lcovonly -- -R spec
- ./node_modules/.bin/coveralls < ./coverage/lcov.info
- rm -rf ./coverage
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# push notify

Apple Mail (iOS) Push support.

Handles registration of devices and sending of push notifications when mail is
received.

Works best with [dovecot-push_notify][dovecot-push_notify]

## Requirements

- Mail server
- Dovecot, see [dovecot-push_notify][dovecot-push_notify]
- Mail server push certificates from macOS Server
- node-v6.3+
- redis

`push_notify` must be run on the same machine as the mail server as it
communicates via a UNIX socket.

## Caveat

This software requires credentials which can only be obtained through
macOS Server. As such it should only be run on Macintosh hardware. No other
configurations are supported or endorsed.

## Environment Variables

- `REDIS_URL`
- `REDIS_PREFIX` default: `pn:`


[dovecot-push_notify]:https://github.com/argon/dovecot-push_notify
26 changes: 26 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use strict";

const apn = require("apn");
const redis = require("redis");
const Promise = require("bluebird");

Promise.promisifyAll(redis.RedisClient.prototype);

const Controller = require("./lib/controller")({Notification: apn.Notification});
const Server = require("./lib/server");
const Socket = require("./lib/socket");

Socket("/var/dovecot/push_notify")
.then( socket => {
const redisURL = process.env["REDIS_URL"];
const redisPrefix = process.env["REDIS_PREFIX"] || "pn:";
const redisClient = redis.createClient({url: redisURL});
const apnConnection = new apn.Connection({ production: true });

const controller = new Controller({ redis: redisClient, apn: apnConnection, prefix: redisPrefix });
const server = new Server({ controller });

socket.on("connection", connection => {
connection.on("data", server.receive.bind(server));
});
});
37 changes: 37 additions & 0 deletions lib/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use strict";

module.exports = function(dependencies) {

const Notification = dependencies.Notification;

function Controller(props) {
this.apn = props.apn;
this.redis = props.redis;
this.prefix = props.prefix;
}

Controller.prototype.register = function register(username, accountId, deviceToken, subtopic) {
const prefix = this.prefix;

this.redis.sadd(`${prefix}${username}:device`, `${deviceToken}:${accountId}`);
}

Controller.prototype.notify = function notify(username, mailbox) {
const prefix = this.prefix;
const usernameKey = `${prefix}${username}:device`

return this.redis.smembersAsync(usernameKey)
.then( deviceAccounts => Promise.all(
deviceAccounts.map( deviceAccount => {
const [device, accountId] = deviceAccount.split(":", 2);
const notification = new Notification({aps: {"account-id": accountId}});

return this.apn.pushNotification(notification, device)
.then( ( { failed } ) => failed)
.each( failed => this.redis.sremAsync(usernameKey, `${failed.device}:${accountId}`))
}))
);
}

return Controller;
};
42 changes: 42 additions & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use strict";

function Server(dependencies) {
this.controller = dependencies.controller;
}

function readCString(buf) {
for(let i = 0; i < buf.length; i++) {
if(buf[i] === 0) {
return buf.toString("utf8", 0, i);
}
}
}

function decode(data) {
return {
type: data.readUInt32LE(0),
pid: data.readUInt32LE(4),

d1: readCString(data.slice(8, 135), 'utf8', 0),
d2: readCString(data.slice(136, 647), 'utf8', 0),
d3: readCString(data.slice(648, 1159), 'utf8', 0),
d4: readCString(data.slice(1160, 1671), 'utf8', 0),
};
}

Server.prototype.receive = function receive(data) {
const message = decode(data);

switch(message.type) {
case 2:
this.controller.register(message.d1, message.d2, message.d3, message.d4);
break;

case 3:
this.controller.notify(message.d1, message.d2);
break;
}
}

module.exports = Server;

51 changes: 51 additions & 0 deletions lib/socket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use strict";

const Promise = require("bluebird");
const net = Promise.promisifyAll(require("net"));
const fs = Promise.promisifyAll(require("fs"));

function Socket(path) {
const server = net.createServer();

return fs.statAsync(path)
.then(cleanup.bind(this, path))
.catch( err => {
if (err.code == "ENOENT") {
return;
} else {
throw err;
}
})
.then(() => listen(server, path))
.then(() => fs.chmodAsync(path, 0o777))
.then(() => server);
}

function listen(server, path) {
return new Promise( (resolve, reject) => {
server.once("error", err => {
reject(err);
});
server.listen(path, resolve);
});
}

function cleanup(path) {
return new Promise((resolve, reject) => {
let client = new net.Socket();
client.once("error", e => {
if (e.code == "ENOTSOCK" || e.code == "ECONNREFUSED") {
resolve(fs.unlinkAsync(path));
} else {
reject(e);
}
});
client.once("connect", () => {
client.end();
reject(new Error("Path in use"));
});
client.connect({ path });
});
}

module.exports = Socket;
33 changes: 26 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
{
"name": "push_notify"
, "version": "0.0.1"
, "private": true
, "dependencies": {
"apn": ">=1.3.5"
, "redis": ">= 0.8.1"
, "hiredis": "0.1.14"
"name": "push_notify",
"version": "0.0.1",
"main": "index.js",
"private": true,
"dependencies": {
"apn": "2.0.0-alpha1",
"bluebird": "^3.4.1",
"debug": "^2.1.3",
"redis": "^2.6.2"
},
"devDependencies": {
"chai": "2.x",
"chai-as-promised": "*",
"mocha": "*",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0"
},
"scripts": {
"test": "node_modules/.bin/mocha"
},
"eslintConfig": {
"ecmaVersion": 6,
"env": {
"es6": true,
"node": true
}
}
}
116 changes: 0 additions & 116 deletions push_notify.js

This file was deleted.

Loading

0 comments on commit 56902a6

Please sign in to comment.