commit 1fa512da93d17572281d136867a29db513f09071 Author: David Kebler Date: Thu Jul 16 12:40:06 2020 -0700 0.1.3 basic working version with example using two service plugins (email and pushsafer) diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..ead540f --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,38 @@ +module.exports = { + "ecmaFeatures": { + "modules": true, + "spread" : true, + "restParams" : true + }, + // "plugins": [ + // "unicorn" + // ], + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "parser": 'babel-eslint', + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" + }, + "extends": "eslint:recommended", + "rules": { + "indent": [ + "error", + 2 + ], + // "unicorn/no-array-instanceof": "error", + "no-console": 0, + "semi": ["error", "never"], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9aab533 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ +.yalc +/example/node_modules/ +*.lock diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..02078d5 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +tests/ +test/ +*.test.js +testing/ +example/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1a495b3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2016 Appzer.de Kevin Siml + * + * Forked from and original created by: Copyright (c) 2012 Aaron Bieber + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..c409500 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# UCI Notify + +A live application notification system supporting notificaiton service plugins + +## Quick Example App + +* clone this repo +* cd to example folder +* `npm install` the pushsafter plugin and the email plugin (support smtp from gmail or aws/ses) will be loaded +* modify options of the two services and choose some default message to send by uncommenting + * `npm start` + +## Usage diff --git a/example/example.js b/example/example.js new file mode 100644 index 0000000..8f48bda --- /dev/null +++ b/example/example.js @@ -0,0 +1,41 @@ +// import Notifier from '../src/notify.js' +import Notifier from '@uci-utils/notify' + + ;(async () => { + const notify = await Notifier.create({ + // one could read a file for these environment variables or hard code them here. + services:{ + pushsafer:{ + module: '@uci-utils/notify-pushsafer-plugin', + k: process.env.PS_API_KEY, + d: process.env.PS_DEVICE_ID // device or group id + }, + email:{ + module: '@uci-utils/notify-email-plugin', + credentials: + { user: process.env.GMAIL_SMTP_USER, + pass:process.env.GMAIL_SMTP_PW + }, + to: process.env.NOTIFY_EMAIL_RECIPIENTS, + subject:'uci notification' + } + } + }) + + // console.log (notify._services.pushsafer._req) + // notify.disableService('pushsafer') + // notify.enableService('pushsafer') + // console.log(await notify.send('a test message','pushsafer')) + let res = await notify.send('simple message to all services') + // let res = await notify.send({subject:'subject as message',text:'this details'}) + // if (res.error) console.log('errors',res) + // else console.log(res) + // res = await notify.send({subject:'object message',otherprop:'some object in body'}) + if (res.error) console.log('errors',res) + // else console.log(res) + +})().catch(err => { + console.log('FATAL: UNABLE TO START NOTIFER! \n',err) + process.exitCode = 1 + process.kill(process.pid, 'SIGINT') +}) diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000..aeacfd7 --- /dev/null +++ b/example/package.json @@ -0,0 +1,17 @@ +{ + "name": "notify-example", + "version": "1.0.0", + "description": "notify example application", + "main": "example.js", + "scripts": { + "start": "node -r esm example.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@uci-utils/notify": "^0.1.3", + "@uci-utils/notify-email-plugin": "^0.1.5", + "@uci-utils/notify-pushsafer-plugin": "^0.1.2", + "esm": "^3.2.25" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..85d8f10 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "@uci-utils/notify", + "version": "0.1.3", + "description": "Integrated Notification System with service plugins", + "main": "src/notify", + "scripts": { + "example:dev": "./node_modules/.bin/nodemon -r esm ./examples/example.js", + "test": "./node_modules/.bin/mocha -r esm --timeout 30000" + }, + "author": "David Kebler", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/uCOMmandIt/uci-utils.git" + }, + "keywords": [ + "node.js", + "notify", + "notifications" + ], + "bugs": { + "url": "https://github.com/uCOMmandIt/uci-utils/issues" + }, + "homepage": "https://github.com/uCOMmandIt/uci-utils#readme", + "dependencies": { + "app-root-path": "^3.0.0", + "await-to-js": "^2.1.1", + "jsonfile": "^6.0.1" + } +} diff --git a/src/notify.js b/src/notify.js new file mode 100644 index 0000000..3eb542f --- /dev/null +++ b/src/notify.js @@ -0,0 +1,95 @@ +// import RxClass from '@uci-utils/rx-class' +import path from 'path' +import { path as root } from 'app-root-path' +import json from 'jsonfile' +import to from 'await-to-js' + +const internal = Symbol('token for Notifier Instance') +let inst + +class Notifier { + constructor(token,opts={}){ + if(token !== internal) { + throw new Error('\'new Notifer\' not supported. Use the \'await Notifier.create()\' async static method') + } + // super(opts) + this._services = {} + this._disabled = [] + this.services = opts.services || {} + } // end constructor + + static async create(opts){ + inst = new Notifier(internal,opts) + console.log('notifcation service registrations:', + await Promise.all( + Object.entries(inst.services).map(async service => { + return await inst.registerService.call(inst,service[0],service[1]) + } + ) + ) + ) + return inst + } + + async registerService(name,opts) { + let mod; let file + let dir = `${root}/node_modules/${opts.module || name}` + let [err,pkg] = await to(json.readFile(dir+'/package.json')) + if (!err) { + file = (dir + '/' + (pkg.main ? pkg.main : 'index.js')) + // eslint-disable-next-line + let [err,res] = await to(import(file)) + if (err) return `${name} failed - no file at ${file}` + mod = res + } else { + // try in service directory for baked in plugins + console.log(opts.module||name, ': no plugin found in node_modules, looking for internal module') + let file = (`${path.dirname(__dirname)}/services/${name}.js`) + // eslint-disable-next-line + let [err,res] = await to(import(file)) + if (err) return `${name} failed - no file at ${file}` + mod = res + } + + // delete opts.module + + const Service = mod.Service || mod.default + if (!Service.prototype.constructor) { + console.log('no default or "Service" class was exported from plugin') + return `${name} failed` + } + else this._services[name] = new Service(opts) + return `${name} registered` + } + + disableService(name) { + this._disabled.push(name) + } + + enableService(name) { + this._disabled= this._disabled.filter(service=>service!==name) + } + + async send (msg,service,opts={}) { + if (service) { + if (this._services[service]) return await this._services[service].send(msg,opts) + } + let response = {} + await Promise.all( + Object.entries(this._services).map(async service => { + let res = this._disabled.includes(service[0]) ? 'disabled' : await service[1].send(msg,opts[service[0]]) + if (res.error) response.error ? response.error[service[0]] = res.error : response = { error:{[service[0]]:res.error}} + response[service[0]]=res + } + ) + ) + return response + } + + getService(name) { return this._services[name]} + +} // end Notifier class + + +export default Notifier +export { Notifier }