├── screenshot1.PNG
├── apn
├── icons
│ └── apple_icon.png
├── apn-app.js
├── apn-token.js
├── apn-app.html
├── apn-token.html
├── apn.html
├── apn.js
├── apn-notification.js
└── apn-notification.html
├── gcm
├── icons
│ └── android_icon.png
├── gcm-configuration.js
├── gcm-configuration.html
├── gcm.html
├── gcm.js
├── gcm-notification.js
└── gcm-notification.html
├── web
├── icons
│ └── chrome_icon.png
├── web.html
├── web-notification.js
├── web.js
└── web-notification.html
├── .gitignore
├── package.json
├── README.md
└── exampleFlow.json
/screenshot1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xzya/node-red-contrib-push/master/screenshot1.PNG
--------------------------------------------------------------------------------
/apn/icons/apple_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xzya/node-red-contrib-push/master/apn/icons/apple_icon.png
--------------------------------------------------------------------------------
/gcm/icons/android_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xzya/node-red-contrib-push/master/gcm/icons/android_icon.png
--------------------------------------------------------------------------------
/web/icons/chrome_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xzya/node-red-contrib-push/master/web/icons/chrome_icon.png
--------------------------------------------------------------------------------
/gcm/gcm-configuration.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | function GCMConfigurationNode(config) {
3 | RED.nodes.createNode(this, config);
4 | this.apiKey = config.apiKey;
5 | }
6 | RED.nodes.registerType("gcm-configuration", GCMConfigurationNode);
7 | }
--------------------------------------------------------------------------------
/apn/apn-app.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | function APNAppNode(n) {
3 | RED.nodes.createNode(this, n);
4 | this.token = RED.nodes.getNode(n.token);
5 | this.topic = n.topic;
6 | this.production = n.production;
7 | }
8 | RED.nodes.registerType("apn-app", APNAppNode);
9 | }
--------------------------------------------------------------------------------
/apn/apn-token.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | function APNTokenNode(config) {
3 | RED.nodes.createNode(this, config);
4 | this.key = config.key;
5 | this.keyId = config.keyId;
6 | this.teamId = config.teamId;
7 | }
8 | RED.nodes.registerType("apn-token", APNTokenNode);
9 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage
2 | lib-cov
3 | *.seed
4 | *.log
5 | *.csv
6 | *.dat
7 | *.out
8 | *.pid
9 | *.gz
10 | pids
11 | logs
12 | results
13 | node_modules
14 | npm-debug.log
15 | mochahelper.js
16 | .idea/
17 | .settings/
18 | dist
19 | .tmp
20 | .sass-cache
21 | bower_components
22 | options.mine.js
23 | db/
24 | data/
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-red-contrib-push",
3 | "version": "0.0.1",
4 | "description": "Push notifications for node-red",
5 | "author": "Mihail Cristian Dumitru",
6 | "keywords": [
7 | "node-red",
8 | "apn",
9 | "gcm",
10 | "web",
11 | "push",
12 | "notifications"
13 | ],
14 | "node-red": {
15 | "nodes": {
16 | "apn": "apn/apn.js",
17 | "apn-token": "apn/apn-token.js",
18 | "apn-app": "apn/apn-app.js",
19 | "apn-notification": "apn/apn-notification.js",
20 | "gcm": "gcm/gcm.js",
21 | "gcm-configuration": "gcm/gcm-configuration.js",
22 | "gcm-notification": "gcm/gcm-notification.js",
23 | "web": "web/web.js",
24 | "web-notification": "web/web-notification.js"
25 | }
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/Xzya/node-red-contrib-push"
30 | },
31 | "dependencies": {
32 | "apn": "2.1.3",
33 | "node-gcm": "0.14.4",
34 | "web-push": "3.2.2"
35 | },
36 | "license": "Apache-2.0"
37 | }
--------------------------------------------------------------------------------
/gcm/gcm-configuration.html:
--------------------------------------------------------------------------------
1 |
14 |
15 |
25 |
26 |
--------------------------------------------------------------------------------
/apn/apn-app.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
35 |
36 |
--------------------------------------------------------------------------------
/gcm/gcm.html:
--------------------------------------------------------------------------------
1 |
18 |
19 |
29 |
30 |
--------------------------------------------------------------------------------
/gcm/gcm.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | function GCMNode(n) {
3 | RED.nodes.createNode(this, n);
4 | var node = this;
5 |
6 | // Retrieve the config node
7 | this.gcmConfiguration = RED.nodes.getNode(n.gcmConfiguration);
8 |
9 | this.on('input', function (msg) {
10 |
11 | node.status({ fill: "blue", shape: "dot", text: " " })
12 |
13 | var content = msg.notification;
14 |
15 | var gcm = require('node-gcm');
16 |
17 | var message = new gcm.Message(content);
18 | var sender = new gcm.Sender(this.gcmConfiguration.apiKey);
19 |
20 | var recipient;
21 |
22 | if (msg.to) {
23 | recipient = { to: msg.to }
24 | } else if (msg.topic) {
25 | recipient = { topic: msg.topic };
26 | } else if (msg.condition) {
27 | recipient = { condition: msg.condition }
28 | } else if (msg.tokens && msg.tokens.length > 0) {
29 | recipient = { registrationTokens: msg.tokens };
30 | } else if (msg.registrationTokens && msg.registrationTokens.length > 0) {
31 | recipient = { registrationTokens: msg.registrationTokens };
32 | } else {
33 | var err = new Error("Missing recipient");
34 | node.status({ fill: "red", shape: "dot", text: err.message })
35 | node.error(err);
36 | return;
37 | }
38 |
39 | sender.sendNoRetry(message, recipient, function (err, response) {
40 | if (err) {
41 | node.status({ fill: "red", shape: "dot", text: err.message })
42 | node.error(err);
43 | return;
44 | }
45 |
46 | node.status({
47 | fill: "green",
48 | shape: "dot",
49 | text: response.success + " sent, " + response.failure + " failed."
50 | })
51 | msg.result = response;
52 | node.send(msg)
53 | });
54 |
55 | });
56 |
57 | this.on('close', function () {
58 | node.status({});
59 | });
60 | }
61 | RED.nodes.registerType("gcm", GCMNode);
62 | }
--------------------------------------------------------------------------------
/web/web.html:
--------------------------------------------------------------------------------
1 |
18 |
19 |
29 |
30 |
--------------------------------------------------------------------------------
/apn/apn-token.html:
--------------------------------------------------------------------------------
1 |
43 |
44 |
65 |
66 |
--------------------------------------------------------------------------------
/apn/apn.html:
--------------------------------------------------------------------------------
1 |
18 |
19 |
29 |
30 |
--------------------------------------------------------------------------------
/web/web-notification.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | function WebNotificationNode(n) {
3 | RED.nodes.createNode(this, n);
4 |
5 | this.content = n;
6 |
7 | var node = this;
8 |
9 | this.on('input', function (msg) {
10 | var payload = {};
11 |
12 | node.status({});
13 |
14 | try {
15 |
16 | if (node.content.title) {
17 | if (!payload.notification) {
18 | payload.notification = {};
19 | }
20 | payload.notification.title = node.content.title;
21 | }
22 |
23 | if (node.content.body) {
24 | if (!payload.notification) {
25 | payload.notification = {};
26 | }
27 | payload.notification.body = node.content.body;
28 | }
29 |
30 | if (node.content.sound) {
31 | if (!payload.notification) {
32 | payload.notification = {};
33 | }
34 | payload.notification.sound = node.content.sound;
35 | }
36 |
37 | if (node.content.payload) {
38 | var data = {};
39 | var keyValArray = JSON.parse(node.content.payload)
40 | for (var i = 0; i < keyValArray.length; i++) {
41 | switch (keyValArray[i].type) {
42 | case "str":
43 | var value = String(keyValArray[i].value)
44 | data[keyValArray[i].key] = value
45 | break
46 | case "num":
47 | var value = parseFloat(keyValArray[i].value)
48 | if (!Number.isNaN(value)) {
49 | data[keyValArray[i].key] = value
50 | } else {
51 | throw new Error("Could not parse " + keyValArray[i].value + " into a number.")
52 | }
53 | break
54 | case "bool":
55 | var value = keyValArray[i].value == true
56 | data[keyValArray[i].key] = value
57 | break
58 | case "json":
59 | var value = JSON.parse(keyValArray[i].value)
60 | data[keyValArray[i].key] = value
61 | break
62 | }
63 | }
64 | payload.data = data;
65 | }
66 |
67 | msg.notification = payload;
68 |
69 | node.send(msg);
70 |
71 | } catch (err) {
72 | node.status({ fill: "red", shape: "dot", text: err.message })
73 | }
74 | });
75 |
76 | this.on('close', function () {
77 | node.status({});
78 | });
79 | }
80 | RED.nodes.registerType("web-notification", WebNotificationNode);
81 | }
--------------------------------------------------------------------------------
/web/web.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | function WebNode(n) {
3 | RED.nodes.createNode(this, n);
4 | var node = this;
5 |
6 | // Retrieve the config node
7 | this.gcmConfiguration = RED.nodes.getNode(n.gcmConfiguration);
8 |
9 | this.on('input', function (msg) {
10 |
11 | try {
12 | node.status({ fill: "blue", shape: "dot", text: " " })
13 |
14 | var payload;
15 | if ((msg.notification != null) && (typeof msg.notification === "object")) {
16 | payload = JSON.stringify(msg.notification)
17 | }
18 |
19 | var options;
20 | if (node.gcmConfiguration && node.gcmConfiguration.apiKey) {
21 | options = {
22 | gcmAPIKey: node.gcmConfiguration.apiKey
23 | }
24 | }
25 |
26 | if (msg.tokens && msg.tokens.length > 0) {
27 | var webPush = require('web-push');
28 |
29 | function sendNotification(subscription, payload, options) {
30 | return new Promise(function (fulfill, reject) {
31 | webPush.sendNotification(subscription, payload, options).then(function (response) {
32 | fulfill({
33 | sent: {
34 | endpoint: subscription.endpoint
35 | }
36 | });
37 | }).catch(function (err) {
38 | fulfill({
39 | failed: JSON.parse(JSON.stringify(err)) // for some reason it only puts the message without this
40 | })
41 | })
42 | })
43 | }
44 |
45 | var calls = []
46 |
47 | for (var i = 0; i < msg.tokens.length; i++) {
48 | calls.push(sendNotification(msg.tokens[i], payload, options))
49 | }
50 |
51 | Promise.all(calls).then(function (results) {
52 | msg.result = {
53 | sent: results.reduce((prev, current) => {
54 | if (current.sent) {
55 | prev.push(current.sent)
56 | }
57 | return prev
58 | }, []),
59 | failed: results.reduce((prev, current) => {
60 | if (current.failed) {
61 | prev.push(current.failed)
62 | }
63 | return prev
64 | }, [])
65 | }
66 | node.status({
67 | fill: "green",
68 | shape: "dot",
69 | text: msg.result.sent.length + " sent, " + msg.result.failed.length + " failed."
70 | })
71 | node.send(msg)
72 | })
73 |
74 | } else {
75 | throw new Error("Missing recipient");
76 | }
77 |
78 | } catch (err) {
79 | node.status({ fill: "red", shape: "dot", text: err.message })
80 | node.error(err);
81 | return;
82 | }
83 | });
84 |
85 | this.on('close', function () {
86 | node.status({});
87 | });
88 | }
89 | RED.nodes.registerType("web", WebNode);
90 | }
--------------------------------------------------------------------------------
/apn/apn.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | function APNNode(n) {
3 | RED.nodes.createNode(this, n);
4 | var node = this;
5 |
6 | // Retrieve the app node
7 | this.app = RED.nodes.getNode(n.app);
8 |
9 | this.on('input', function (msg) {
10 |
11 | node.status({ fill: "blue", shape: "dot", text: " " })
12 |
13 | var rawNotification = msg.rawNotification;
14 | var content = msg.notification;
15 |
16 | if (rawNotification || content) {
17 | var apn = require("apn");
18 | var provider = new apn.Provider({
19 | token: node.app.token,
20 | production: node.app.production
21 | })
22 |
23 | var notification = new apn.Notification();
24 | notification.topic = node.app.topic;
25 |
26 | if (rawNotification) {
27 | notification.rawPayload = rawNotification;
28 | } else {
29 | if (content.title && content.body) {
30 | notification.title = content.title;
31 | notification.body = content.body;
32 | } else if (content.body) {
33 | notification.alert = content.body;
34 | }
35 | if (content.sound) {
36 | notification.sound = content.sound;
37 | }
38 | if (content.badge != null) {
39 | notification.badge = content.badge;
40 | }
41 | if (content.action) {
42 | notification.action = content.action;
43 | }
44 | if (content.category) {
45 | notification.category = content.category;
46 | }
47 | if (content.contentAvailable) {
48 | notification.contentAvailable = 1;
49 | }
50 | if (content.mutableContent) {
51 | notification.mutableContent = 1;
52 | }
53 | if (content.expiry) {
54 | notification.expiry = content.expiry;
55 | }
56 | if (content.urlArgs) {
57 | notification.urlArgs = content.urlArgs;
58 | }
59 | if (content.priority && (content.alert || content.title || content.body || content.sound || content.badge)) {
60 | notification.priority = content.priority;
61 | }
62 | if (content.payload) {
63 | notification.payload = content.payload;
64 | }
65 | }
66 |
67 | var tokens;
68 |
69 | if (msg.tokens && msg.tokens.length > 0) {
70 | tokens = msg.tokens;
71 | } else {
72 | var err = new Error("Missing recipient");
73 | node.status({ fill: "red", shape: "dot", text: err.message })
74 | node.error(err);
75 | return;
76 | }
77 |
78 | provider.send(notification, tokens).then(function (response) {
79 | node.status({
80 | fill: "green",
81 | shape: "dot",
82 | text: response.sent.length + " sent, " + response.failed.length + " failed."
83 | })
84 | msg.result = response;
85 | node.send(msg)
86 | }).catch(function (err) {
87 | node.status({ fill: "red", shape: "dot", text: err.message })
88 | node.error(err);
89 | })
90 | }
91 | });
92 |
93 | this.on('close', function () {
94 | node.status({});
95 | });
96 |
97 | }
98 | RED.nodes.registerType("apn", APNNode);
99 | }
--------------------------------------------------------------------------------
/gcm/gcm-notification.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | function GCMNotificationNode(n) {
3 | RED.nodes.createNode(this, n);
4 |
5 | this.content = n;
6 |
7 | var node = this;
8 |
9 | this.on('input', function (msg) {
10 | var payload = {};
11 |
12 | node.status({});
13 |
14 | try {
15 |
16 | if (node.content.title) {
17 | if (!payload.notification) {
18 | payload.notification = {};
19 | }
20 | payload.notification.title = node.content.title;
21 | }
22 |
23 | if (node.content.body) {
24 | if (!payload.notification) {
25 | payload.notification = {};
26 | }
27 | payload.notification.body = node.content.body;
28 | }
29 |
30 | if (node.content.sound) {
31 | payload.sound = node.content.sound;
32 | }
33 |
34 | if (node.content.payload) {
35 | var data = {};
36 | var keyValArray = JSON.parse(node.content.payload)
37 | for (var i = 0; i < keyValArray.length; i++) {
38 | switch (keyValArray[i].type) {
39 | case "str":
40 | var value = String(keyValArray[i].value)
41 | data[keyValArray[i].key] = value
42 | break
43 | case "num":
44 | var value = parseFloat(keyValArray[i].value)
45 | if (!Number.isNaN(value)) {
46 | data[keyValArray[i].key] = value
47 | } else {
48 | throw new Error("Could not parse " + keyValArray[i].value + " into a number.")
49 | }
50 | break
51 | case "bool":
52 | var value = keyValArray[i].value == true
53 | data[keyValArray[i].key] = value
54 | break
55 | case "json":
56 | var value = JSON.parse(keyValArray[i].value)
57 | data[keyValArray[i].key] = value
58 | break
59 | }
60 | }
61 | payload.data = data;
62 | }
63 |
64 | if (node.content.expiry) {
65 | var units = node.content.expiry.split(" ").map(function (unit) {
66 | return Number(unit)
67 | })
68 | var minutes = units[0] || 0;
69 | var hours = units[1] || 0;
70 | var days = units[2] || 0;
71 | var weeks = units[3] || 0;
72 |
73 | payload.timeToLive = (60 * minutes)
74 | + (60 * 60 * hours)
75 | + (60 * 60 * 24 * days)
76 | + (60 * 60 * 24 * 7 * weeks)
77 | }
78 |
79 | if (node.content.priority) {
80 | payload.priority = node.content.priority;
81 | }
82 |
83 | if (node.content.dryRun) {
84 | payload.dryRun = node.content.dryRun;
85 | }
86 |
87 | msg.notification = payload;
88 |
89 | node.send(msg);
90 |
91 | } catch (err) {
92 | node.status({ fill: "red", shape: "dot", text: err.message })
93 | }
94 | });
95 |
96 | this.on('close', function () {
97 | node.status({});
98 | });
99 | }
100 | RED.nodes.registerType("gcm-notification", GCMNotificationNode);
101 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Node-RED Push Notification nodes
2 | ====================================
3 |
4 |
5 | `node-red-contrib-push` is a [Node-RED](http://nodered.org/docs/creating-nodes/packaging.html) package that allows you to send APN, GCM and Web push notifications.
6 |
7 | It uses the [node-apn](https://github.com/node-apn/node-apn) library for APN notifications, [node-gcm](https://github.com/ToothlessGear/node-gcm) for GCM notifications and [web-push](https://github.com/web-push-libs/web-push) for Web notifications.
8 |
9 | ## Table of Contents
10 | - [APN](#apn)
11 | - [GCM](#gcm)
12 | - [Web](#web)
13 | - [Additional information](#additional)
14 | - [Example flow](#example)
15 | - [Screenshots](#screenshots)
16 | - [License](#license)
17 |
18 | ## APN
19 |
20 | You can use the ```apn``` node to send notifications to iOS and Safari devices. You will need to configure a [Provider Authentication Token](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html) and then create an ```app``` configuration, which will contain the token, as well as the topic (bundle-id) of your app and wheather it's using a production or sandbox environment. Assign the ```app``` configuration to the ```apn``` node and you are ready to send notifications.
21 |
22 | You can use the ```apn-notification``` node to set the properties of a notification, or you can send the values in the ```msg.notification``` object. You can also set the raw payload which will be sent to Apple in the ```msg.rawNotification``` object. More information available in the ```apn``` node info tab.
23 |
24 | The tokens must be provided in the ```msg.tokens``` object as an array.
25 |
26 | ## GCM
27 |
28 | The ```gcm``` node is used to send notifications to Android and iOS (if it's configured in Firebase) devices. For Chrome notifications, check out the ```web``` node. You will need to configure your GCM Api Key and assign it to the node.
29 |
30 | You can use the ```gcm-notification``` node to set the properties of a notification, or you can send the values in the ```msg.notification``` object.
31 |
32 | The recipient of the notification can be specified by setting one of the following keys: ```to```, ```topic```, ```condition```, ```registrationTokens``` or ```tokens```.
33 |
34 | More information available in the ```gcm``` node info tab.
35 |
36 | ## Web
37 |
38 | The ```web``` node is used to send notifications to Chrome, Firefox, Opera, and Samsung Internet browsers. For a list of supported versions of those browsers, check the [web-push](https://github.com/web-push-libs/web-push) page.
39 |
40 | Some browsers (Chrome and Opera) requires a GCM Api Key to send notifications, so you will need to configure it in the node.
41 |
42 | You can use the ```web-notification``` node to set the properties of a notification, or you can send the values in the ```msg.notification``` object.
43 |
44 | The device tokens must be provided in the ```msg.tokens``` object and they must contain the ```endpoint```, as well as the ```p256dh``` and the ```auth``` keys.
45 |
46 | More information available in the ```web``` node info tab.
47 |
48 | ## Additional information
49 |
50 | The ```apn```, ```gcm``` and ```web``` nodes will return the result in the ```msg.result``` key.
51 |
52 | ## Example flow
53 |
54 | You can find an example flow in ```exampleFlow.json```.
55 |
56 | ## Screenshots
57 |
58 | 
59 |
60 | ## License
61 |
62 | Copyright 2017 Mihail Cristian Dumitru
63 |
64 | Licensed under the Apache License, Version 2.0 (the "License");
65 | you may not use this file except in compliance with the License.
66 | You may obtain a copy of the License at
67 |
68 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
69 |
70 | Unless required by applicable law or agreed to in writing, software
71 | distributed under the License is distributed on an "AS IS" BASIS,
72 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
73 | See the License for the specific language governing permissions and
74 | limitations under the License.
--------------------------------------------------------------------------------
/apn/apn-notification.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | function APNNotificationNode(n) {
3 | RED.nodes.createNode(this, n);
4 |
5 | this.content = n;
6 |
7 | var node = this;
8 |
9 | this.on('input', function (msg) {
10 | var payload = {};
11 |
12 | node.status({});
13 |
14 | try {
15 |
16 | if (node.content.title) {
17 | payload.title = node.content.title;
18 | }
19 |
20 | if (node.content.body) {
21 | payload.body = node.content.body;
22 | }
23 |
24 | if (node.content.sound) {
25 | payload.sound = node.content.sound;
26 | }
27 |
28 | if (node.content.badge) {
29 | payload.badge = Number(node.content.badge);
30 | }
31 |
32 | if (node.content.action) {
33 | payload.action = node.content.action;
34 | }
35 |
36 | if (node.content.category) {
37 | payload.category = node.content.category;
38 | }
39 |
40 | if (node.content.contentAvailable) {
41 | payload.contentAvailable = 1;
42 | }
43 |
44 | if (node.content.mutableContent) {
45 | payload.mutableContent = 1;
46 | }
47 |
48 | if (node.content.payload) {
49 | var data = {};
50 | var keyValArray = JSON.parse(node.content.payload)
51 | for (var i = 0; i < keyValArray.length; i++) {
52 | switch (keyValArray[i].type) {
53 | case "str":
54 | var value = String(keyValArray[i].value)
55 | data[keyValArray[i].key] = value
56 | break
57 | case "num":
58 | var value = parseFloat(keyValArray[i].value)
59 | if (!Number.isNaN(value)) {
60 | data[keyValArray[i].key] = value
61 | } else {
62 | throw new Error("Could not parse " + keyValArray[i].value + " into a number.")
63 | }
64 | break
65 | case "bool":
66 | var value = keyValArray[i].value == true
67 | data[keyValArray[i].key] = value
68 | break
69 | case "json":
70 | var value = JSON.parse(keyValArray[i].value)
71 | data[keyValArray[i].key] = value
72 | break
73 | }
74 | }
75 | payload.payload = data;
76 | }
77 |
78 | if (node.content.expiry) {
79 | var units = node.content.expiry.split(" ").map(function (unit) {
80 | return Number(unit)
81 | })
82 | var minutes = units[0] || 0;
83 | var hours = units[1] || 0;
84 | var days = units[2] || 0;
85 | var weeks = units[3] || 0;
86 |
87 | payload.expiry = Math.floor(Date.now() / 1000)
88 | + (60 * minutes)
89 | + (60 * 60 * hours)
90 | + (60 * 60 * 24 * days)
91 | + (60 * 60 * 24 * 7 * weeks)
92 | }
93 |
94 | if (node.content.urlArgs) {
95 | var urlArgs = JSON.parse(node.content.urlArgs);
96 |
97 | if (urlArgs.length > 0) {
98 | payload.urlArgs = urlArgs;
99 | }
100 | }
101 |
102 | if (node.content.priority) {
103 | payload.priority = node.content.priority;
104 | }
105 |
106 | msg.notification = payload;
107 |
108 | node.send(msg);
109 |
110 | } catch (err) {
111 | node.status({ fill: "red", shape: "dot", text: err.message })
112 | }
113 | });
114 |
115 | this.on('close', function () {
116 | node.status({});
117 | });
118 | }
119 | RED.nodes.registerType("apn-notification", APNNotificationNode);
120 | }
--------------------------------------------------------------------------------
/web/web-notification.html:
--------------------------------------------------------------------------------
1 |
124 |
125 |
165 |
166 |
181 |
182 |
--------------------------------------------------------------------------------
/exampleFlow.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "727cd3c5.76010c",
4 | "type": "tab",
5 | "label": "Push"
6 | },
7 | {
8 | "id": "94c512b4.9589e",
9 | "type": "apn",
10 | "z": "727cd3c5.76010c",
11 | "name": "Demo iOS App Prod",
12 | "app": "4f846751.82f658",
13 | "x": 760,
14 | "y": 180,
15 | "wires": [
16 | [
17 | "7932dc64.d623a4"
18 | ]
19 | ]
20 | },
21 | {
22 | "id": "f83f93a1.5ba51",
23 | "type": "apn-notification",
24 | "z": "727cd3c5.76010c",
25 | "name": "",
26 | "title": "",
27 | "body": "Hello, world!",
28 | "action": "",
29 | "badge": "42",
30 | "sound": "default",
31 | "contentAvailable": true,
32 | "mutableContent": "",
33 | "urlArgs": "",
34 | "category": "",
35 | "expiry": "* * 1 *",
36 | "priority": "10",
37 | "payload": "[{\"key\":\"item\",\"value\":\"37\",\"type\":\"num\"},{\"key\":\"category\",\"value\":\"2\",\"type\":\"num\"},{\"key\":\"name\",\"value\":\"Some random item\",\"type\":\"str\"}]",
38 | "x": 540,
39 | "y": 160,
40 | "wires": [
41 | [
42 | "e48becf2.02004"
43 | ]
44 | ]
45 | },
46 | {
47 | "id": "2144f4b0.d1633c",
48 | "type": "gcm",
49 | "z": "727cd3c5.76010c",
50 | "name": "Demo Android App",
51 | "gcmConfiguration": "4114a855.7b8bf8",
52 | "x": 750,
53 | "y": 240,
54 | "wires": [
55 | [
56 | "7932dc64.d623a4"
57 | ]
58 | ]
59 | },
60 | {
61 | "id": "3a0403e9.d5e4fc",
62 | "type": "gcm-notification",
63 | "z": "727cd3c5.76010c",
64 | "name": "",
65 | "title": "",
66 | "body": "Hello, world!",
67 | "sound": "default",
68 | "expiry": "* * 1 *",
69 | "priority": "high",
70 | "dryRun": "",
71 | "payload": "[{\"key\":\"item\",\"value\":\"37\",\"type\":\"str\"},{\"key\":\"category\",\"value\":\"2\",\"type\":\"str\"},{\"key\":\"name\",\"value\":\"Some random item\",\"type\":\"str\"}]",
72 | "x": 540,
73 | "y": 240,
74 | "wires": [
75 | [
76 | "2144f4b0.d1633c"
77 | ]
78 | ]
79 | },
80 | {
81 | "id": "7d0466d6.7ea788",
82 | "type": "web",
83 | "z": "727cd3c5.76010c",
84 | "name": "",
85 | "gcmConfiguration": "",
86 | "x": 710,
87 | "y": 300,
88 | "wires": [
89 | [
90 | "7932dc64.d623a4"
91 | ]
92 | ]
93 | },
94 | {
95 | "id": "c619e6f9.ff73b8",
96 | "type": "web-notification",
97 | "z": "727cd3c5.76010c",
98 | "name": "",
99 | "title": "",
100 | "body": "Hello, world!",
101 | "sound": "default",
102 | "payload": "[{\"key\":\"item\",\"value\":\"37\",\"type\":\"str\"},{\"key\":\"category\",\"value\":\"2\",\"type\":\"str\"},{\"key\":\"name\",\"value\":\"Some random item\",\"type\":\"str\"}]",
103 | "x": 540,
104 | "y": 300,
105 | "wires": [
106 | [
107 | "7d0466d6.7ea788"
108 | ]
109 | ]
110 | },
111 | {
112 | "id": "e48becf2.02004",
113 | "type": "apn",
114 | "z": "727cd3c5.76010c",
115 | "name": "Demo iOS App Dev",
116 | "app": "4f7edc8a.cfacf4",
117 | "x": 750,
118 | "y": 120,
119 | "wires": [
120 | [
121 | "7932dc64.d623a4"
122 | ]
123 | ]
124 | },
125 | {
126 | "id": "7932dc64.d623a4",
127 | "type": "debug",
128 | "z": "727cd3c5.76010c",
129 | "name": "",
130 | "active": true,
131 | "console": "false",
132 | "complete": "result",
133 | "x": 970,
134 | "y": 240,
135 | "wires": []
136 | },
137 | {
138 | "id": "e5c3b331.f2a51",
139 | "type": "inject",
140 | "z": "727cd3c5.76010c",
141 | "name": "",
142 | "topic": "",
143 | "payload": "",
144 | "payloadType": "date",
145 | "repeat": "",
146 | "crontab": "",
147 | "once": false,
148 | "x": 160,
149 | "y": 120,
150 | "wires": [
151 | [
152 | "97d5c689.6c0518"
153 | ]
154 | ]
155 | },
156 | {
157 | "id": "97d5c689.6c0518",
158 | "type": "function",
159 | "z": "727cd3c5.76010c",
160 | "name": "iOS Dev Tokens",
161 | "func": "msg.tokens = [\n \n]\nreturn msg;",
162 | "outputs": 1,
163 | "noerr": 0,
164 | "x": 340,
165 | "y": 120,
166 | "wires": [
167 | [
168 | "f83f93a1.5ba51"
169 | ]
170 | ]
171 | },
172 | {
173 | "id": "b94f107.b4396f",
174 | "type": "function",
175 | "z": "727cd3c5.76010c",
176 | "name": "iOS Prod Tokens",
177 | "func": "msg.tokens = [\n \n]\nreturn msg;",
178 | "outputs": 1,
179 | "noerr": 0,
180 | "x": 330,
181 | "y": 180,
182 | "wires": [
183 | []
184 | ]
185 | },
186 | {
187 | "id": "5ed8262.917f0d8",
188 | "type": "function",
189 | "z": "727cd3c5.76010c",
190 | "name": "Android Tokens",
191 | "func": "msg.tokens = [\n \n]\nreturn msg;",
192 | "outputs": 1,
193 | "noerr": 0,
194 | "x": 340,
195 | "y": 240,
196 | "wires": [
197 | [
198 | "3a0403e9.d5e4fc"
199 | ]
200 | ]
201 | },
202 | {
203 | "id": "ca5cc2ba.4f269",
204 | "type": "function",
205 | "z": "727cd3c5.76010c",
206 | "name": "Web Tokens",
207 | "func": "msg.tokens = [\n \n]\nreturn msg;",
208 | "outputs": 1,
209 | "noerr": 0,
210 | "x": 350,
211 | "y": 300,
212 | "wires": [
213 | [
214 | "c619e6f9.ff73b8"
215 | ]
216 | ]
217 | },
218 | {
219 | "id": "6e2cf095.4ff6c",
220 | "type": "inject",
221 | "z": "727cd3c5.76010c",
222 | "name": "",
223 | "topic": "",
224 | "payload": "",
225 | "payloadType": "date",
226 | "repeat": "",
227 | "crontab": "",
228 | "once": false,
229 | "x": 160,
230 | "y": 180,
231 | "wires": [
232 | [
233 | "b94f107.b4396f"
234 | ]
235 | ]
236 | },
237 | {
238 | "id": "5f91f727.449258",
239 | "type": "inject",
240 | "z": "727cd3c5.76010c",
241 | "name": "",
242 | "topic": "",
243 | "payload": "",
244 | "payloadType": "date",
245 | "repeat": "",
246 | "crontab": "",
247 | "once": false,
248 | "x": 160,
249 | "y": 240,
250 | "wires": [
251 | [
252 | "5ed8262.917f0d8"
253 | ]
254 | ]
255 | },
256 | {
257 | "id": "2689df58.4d09",
258 | "type": "inject",
259 | "z": "727cd3c5.76010c",
260 | "name": "",
261 | "topic": "",
262 | "payload": "",
263 | "payloadType": "date",
264 | "repeat": "",
265 | "crontab": "",
266 | "once": false,
267 | "x": 160,
268 | "y": 300,
269 | "wires": [
270 | [
271 | "ca5cc2ba.4f269"
272 | ]
273 | ]
274 | },
275 | {
276 | "id": "4f846751.82f658",
277 | "type": "apn-app",
278 | "z": "",
279 | "name": "Demo App",
280 | "token": "95bf22d2.9332b",
281 | "topic": "com.example.app",
282 | "production": true
283 | },
284 | {
285 | "id": "4114a855.7b8bf8",
286 | "type": "gcm-configuration",
287 | "z": "",
288 | "name": "Demo GCM Project",
289 | "apiKey": "API_KEY"
290 | },
291 | {
292 | "id": "4f7edc8a.cfacf4",
293 | "type": "apn-app",
294 | "z": "",
295 | "name": "Demo App",
296 | "token": "95bf22d2.9332b",
297 | "topic": "com.example.app",
298 | "production": false
299 | },
300 | {
301 | "id": "95bf22d2.9332b",
302 | "type": "apn-token",
303 | "z": "",
304 | "name": "MyTeam",
305 | "key": "-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----",
306 | "keyId": "KEY_ID",
307 | "teamId": "TEAM_ID"
308 | }
309 | ]
--------------------------------------------------------------------------------
/gcm/gcm-notification.html:
--------------------------------------------------------------------------------
1 |
214 |
215 |
293 |
294 |
323 |
324 |
--------------------------------------------------------------------------------
/apn/apn-notification.html:
--------------------------------------------------------------------------------
1 |
341 |
342 |
459 |
460 |
495 |
496 |
--------------------------------------------------------------------------------