├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── dotenv.js ├── lib └── client.js ├── package.json └── test ├── helpers └── superagent-mock.js └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": [ 4 | "transform-runtime" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # babel 40 | dist 41 | 42 | # dotenv 43 | .env 44 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dotenv.js 2 | test/ 3 | .travis.yml 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "4" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Scout 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OneSignal SDK for Node.js [![Build Status](https://travis-ci.org/scoutforpets/node-onesignal.svg?branch=master)](https://travis-ci.org/scoutforpets/node-onesignal) 2 | This is an unofficial Node.js SDK for the [OneSignal Push Notification Service](https://onesignal.com/), which wraps their [REST API](https://documentation.onesignal.com/docs/server-api-overview). 3 | 4 | ## Basic Usage 5 | 6 | ```js 7 | // require the module 8 | const OneSignalClient = require('node-onesignal'); 9 | 10 | // create a new clinet 11 | const client = new OneSignalClient([YOUR APP ID], [YOUR REST API KEY]); 12 | 13 | // send a notification 14 | client.sendNotification('test notification', { 15 | included_segments: 'all' 16 | }); 17 | ``` 18 | 19 | ## API 20 | 21 | `OneSignalClient(appId, restApiKey)` 22 | * `appId`_(string, required)_ - your OneSignal App ID 23 | 24 | * `restApiKey`_(string, required)_ - your OneSignal REST API Key 25 | 26 | `sendNotification(message, options)` 27 | * `message`_(string/object, required)_ - the content of your message. **Note:** when passing an object, please see the [OneSignal documentation](https://documentation.onesignal.com/docs/notifications-create-notification) for details on the format. 28 | 29 | * `options`_(object)_ - OneSignal options. Please see the [OneSignal documentation](https://documentation.onesignal.com/docs/notifications-create-notification). 30 | 31 | As you can see, this SDK does not implement all of the methods available through the OneSignal REST API. If there are other methods you require, please open an issue or feel free to create a PR (with tests!). 32 | 33 | ## Contributing 34 | Just open a PR and include tests. Any help is greatly appreciated! 35 | -------------------------------------------------------------------------------- /dotenv.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const FindUp = require('find-up'); 4 | process.argv.push(`dotenv_config_path=${FindUp.sync('.env')}`); 5 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import Request from 'superagent'; 3 | 4 | 5 | // OneSignal v1 API url 6 | const API_URL = 'https://onesignal.com/api/v1'; 7 | 8 | 9 | // The OneSignal Client 10 | export default class Client { 11 | 12 | /** 13 | * Creates a new OneSignal client 14 | * @param {string} appId the appId for your app 15 | * @param {string} restApiKey the REST API key for your app 16 | * @return {object} an initialized client 17 | */ 18 | constructor(appId, restApiKey) { 19 | 20 | Joi.assert(appId, Joi.string().guid().required(), new Error('`appId` is required')); 21 | Joi.assert(restApiKey, Joi.string().required(), new Error('`restApiKey` is required')); 22 | 23 | this.appId = appId; 24 | this.restApiKey = restApiKey; 25 | } 26 | 27 | /** 28 | * Sends a notification. 29 | * @param {string|object} message the message to display to the recipient 30 | * @param {object} options a hash of options to pass to the API 31 | * @return {object} the response 32 | */ 33 | async sendNotification(message, options) { 34 | 35 | options = options || {}; 36 | 37 | // Perform some basic validation 38 | Joi.assert(message, Joi.alternatives().try(Joi.string(), Joi.object()).required(), new Error('`message` is required')); 39 | Joi.assert(options, Joi.object()); 40 | 41 | // Convert message to object as required by the API 42 | if (typeof message === 'string') { 43 | message = { 44 | en: message 45 | } 46 | } 47 | 48 | // Craft the payload 49 | const payload = Object.assign({ 50 | app_id: this.appId, 51 | contents: message 52 | }, options); 53 | 54 | // Make the request 55 | try { 56 | 57 | return await Request 58 | .post(`${API_URL}/notifications`) 59 | .set('Authorization', `Basic ${this.restApiKey}`) 60 | .send(payload); 61 | } 62 | catch(err) { 63 | 64 | throw new Error(err.response.error.text); 65 | } 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-onesignal", 3 | "version": "0.3.1", 4 | "description": "Node wrapper for the One Signal API", 5 | "main": "./dist/client.js", 6 | "scripts": { 7 | "build": "./node_modules/.bin/babel -q -L -D ./lib/ --out-dir ./dist/", 8 | "prepublish": "npm run clean && npm run build", 9 | "test": "npm run prepublish && ava -v -r ./dotenv -r dotenv/config -r babel-register", 10 | "clean": "rm -rf dist/", 11 | "coverage": "nyc npm test" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/scoutforpets/node-onesignal.git" 16 | }, 17 | "keywords": [ 18 | "onesignal", 19 | "push notification", 20 | "push" 21 | ], 22 | "author": "James Dixon ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/scoutforpets/node-onesignal/issues" 26 | }, 27 | "homepage": "https://github.com/scoutforpets/node-onesignal#readme", 28 | "devDependencies": { 29 | "ava": "0.16.0", 30 | "babel-cli": "6.16.0", 31 | "babel-plugin-transform-runtime": "6.15.0", 32 | "babel-preset-es2015": "6.16.0", 33 | "babel-preset-stage-2": "6.17.0", 34 | "babel-register": "6.16.3", 35 | "babel-runtime": "6.11.6", 36 | "dotenv": "2.0.0", 37 | "find-up": "2.0.0", 38 | "superagent-mock": "1.12.0" 39 | }, 40 | "dependencies": { 41 | "babel-runtime": "^6.11.6", 42 | "joi": "9.1.0", 43 | "superagent": "2.3.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/helpers/superagent-mock.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | /** 3 | * regular expression of URL 4 | */ 5 | pattern: 'https://onesignal.com/api/v1/(.*)', 6 | 7 | /** 8 | * returns the data 9 | * 10 | * @param match array Result of the resolution of the regular expression 11 | * @param params object sent by 'send' function 12 | * @param headers object set by 'set' function 13 | */ 14 | fixtures: function(match, params, headers) { 15 | 16 | // Notifications endpoint 17 | if (match[1] === 'notifications') { 18 | 19 | const contents = params['contents']; 20 | const includedSegments = params['included_segments']; 21 | 22 | // 200 - Success 23 | if (contents && includedSegments) { 24 | return { 25 | statusCode: 200 26 | }; 27 | } 28 | 29 | // 400 - no message specified 30 | if (!contents) { 31 | throw new Error(400); 32 | } 33 | 34 | // 400 - message specified, but no one to send to 35 | if (contents && !includedSegments) { 36 | throw new Error(400); 37 | } 38 | } 39 | }, 40 | 41 | /** 42 | * returns the result of the GET request 43 | * 44 | * @param match array Result of the resolution of the regular expression 45 | * @param data mixed Data returns by `fixtures` attribute 46 | */ 47 | get: function(match, data) { 48 | return { 49 | body: data 50 | }; 51 | }, 52 | 53 | /** 54 | * returns the result of the POST request 55 | * 56 | * @param match array Result of the resolution of the regular expression 57 | * @param data mixed Data returns by `fixtures` attribute 58 | */ 59 | post: function(match, data) { 60 | return data; 61 | } 62 | }]; 63 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import Client from '../lib/client'; 2 | 3 | import test from 'ava'; 4 | import Superagent from 'superagent'; 5 | import SuperagentMockConfig from './helpers/superagent-mock'; 6 | 7 | const superagentMock = require('superagent-mock')(Superagent, SuperagentMockConfig); 8 | 9 | 10 | // Globals 11 | 12 | let client; 13 | 14 | 15 | // Test setup 16 | 17 | test.before(t => { 18 | 19 | client = new Client(process.env.APP_ID, process.env.REST_API_KEY); 20 | }); 21 | 22 | 23 | // Tests 24 | 25 | 26 | test('throws an error if no app id is specified', t => { 27 | 28 | t.throws(() => { 29 | new Client(); 30 | }, '`appId` is required'); 31 | }); 32 | 33 | 34 | test('throws an error if no api key is specified', t => { 35 | 36 | t.throws(() => { 37 | new Client('a0944967-e2b6-4549-91d2-91fdd290ad94'); 38 | }, '`restApiKey` is required'); 39 | }); 40 | 41 | 42 | test('sends a notification (message as string)', async t => { 43 | 44 | const message = 'Test Message'; 45 | const options = { 46 | included_segments: 'all' 47 | }; 48 | 49 | const response = await client.sendNotification(message, options); 50 | 51 | t.is(response.statusCode, 200); 52 | }); 53 | 54 | 55 | test('sends a notification (message as object)', async t => { 56 | 57 | const message = { 58 | en: 'Test Message' 59 | }; 60 | 61 | const options = { 62 | included_segments: 'all' 63 | }; 64 | 65 | const response = await client.sendNotification(message, options); 66 | 67 | t.is(response.statusCode, 200); 68 | }); 69 | 70 | test('throws an error if no message is provided', async t => { 71 | 72 | const message = ''; 73 | const options = { 74 | included_segments: 'all' 75 | }; 76 | 77 | const response = client.sendNotification(message, options); 78 | 79 | t.throws(response, '`message` is required'); 80 | }); 81 | 82 | 83 | test('handles API error', async t => { 84 | 85 | const client = new Client(process.env.APP_ID, process.env.REST_API_KEY); 86 | 87 | const message = 'Test Message'; 88 | const options = {}; 89 | 90 | const response = client.sendNotification(message, options); 91 | 92 | t.throws(response); 93 | }); 94 | --------------------------------------------------------------------------------