UNPKG

pushd

Version:

Blazing fast multi-protocol mobile push notification service

169 lines (146 loc) 6.51 kB
express = require 'express' bodyParser = require 'body-parser' dgram = require 'dgram' zlib = require 'zlib' url = require 'url' Netmask = require('netmask').Netmask settings = require './settings' Subscriber = require('./lib/subscriber').Subscriber EventPublisher = require('./lib/eventpublisher').EventPublisher Event = require('./lib/event').Event PushServices = require('./lib/pushservices').PushServices Payload = require('./lib/payload').Payload logger = require 'winston' if settings.server.redis_socket? redis = require('redis').createClient(settings.server.redis_socket) else if settings.server.redis_port? or settings.server.redis_host? redis = require('redis').createClient(settings.server.redis_port, settings.server.redis_host) if settings.logging? logger.remove(logger.transports.Console) for loggerconfig in settings.logging transport = logger.transports[loggerconfig['transport']] if transport? logger.add(transport, loggerconfig.options || {}) else process.stderr.write "Invalid logger transport: #{loggerconfig['transport']}\n" if settings.server?.redis_auth? redis.auth(settings.server.redis_auth) createSubscriber = (fields, cb) -> logger.verbose "creating subscriber proto = #{fields.proto}, token = #{fields.token}" throw new Error("Invalid value for `proto'") unless service = pushServices.getService(fields.proto) throw new Error("Invalid value for `token'") unless fields.token = service.validateToken(fields.token) Subscriber::create(redis, fields, cb) tokenResolver = (proto, token, cb) -> Subscriber::getInstanceFromToken redis, proto, token, cb eventSourceEnabled = no pushServices = new PushServices() for name, conf of settings when conf.enabled logger.info "Registering push service: #{name}" if name is 'event-source' # special case for EventSource which isn't a pluggable push protocol eventSourceEnabled = yes else pushServices.addService(name, new conf.class(conf, logger, tokenResolver)) eventPublisher = new EventPublisher(pushServices) checkUserAndPassword = (username, password) => if settings.server?.auth? if not settings.server.auth[username]? logger.error "Unknown user #{username}" return false passwordOK = password? and password is settings.server.auth[username].password if not passwordOK logger.error "Invalid password for #{username}" return passwordOK return false app = express() app.use(express.logger(':method :url :status')) if settings.server?.access_log if settings.server?.auth? and not settings.server?.acl? app.use(express.basicAuth checkUserAndPassword) app.use(bodyParser.urlencoded({ limit: '1mb', extended: true })) app.use(bodyParser.json({ limit: '1mb' })) app.use(app.router) app.disable('x-powered-by'); app.param 'subscriber_id', (req, res, next, id) -> try req.subscriber = new Subscriber(redis, req.params.subscriber_id) delete req.params.subscriber_id next() catch error res.json error: error.message, 400 getEventFromId = (id) -> return new Event(redis, id) testSubscriber = (subscriber) -> pushServices.push(subscriber, null, new Payload({msg: "Test", "data.test": "ok"})) app.param 'event_id', (req, res, next, id) -> try req.event = getEventFromId(req.params.event_id) delete req.params.event_id next() catch error res.json error: error.message, 400 authorize = (realm) -> if settings.server?.auth? return (req, res, next) -> # req.user has been set by express.basicAuth logger.verbose "Authenticating #{req.user} for #{realm}" if not req.user? logger.error "User not authenticated" res.json error: 'Unauthorized', 403 return allowedRealms = settings.server.auth[req.user]?.realms or [] if realm not in allowedRealms logger.error "No access to #{realm} for #{req.user}, allowed: #{allowedRealms}" res.json error: 'Unauthorized', 403 return next() else if allow_from = settings.server?.acl?[realm] networks = [] for network in allow_from networks.push new Netmask(network) return (req, res, next) -> if remoteAddr = req.socket and (req.socket.remoteAddress or (req.socket.socket and req.socket.socket.remoteAddress)) for network in networks if network.contains(remoteAddr) next() return res.json error: 'Unauthorized', 403 else return (req, res, next) -> next() require('./lib/api').setupRestApi(app, createSubscriber, getEventFromId, authorize, testSubscriber, eventPublisher) if eventSourceEnabled require('./lib/eventsource').setup(app, authorize, eventPublisher) port = settings?.server?.tcp_port ? 80 app.listen port logger.info "Listening on tcp port #{port}" # UDP Event API udpApi = dgram.createSocket("udp4") event_route = /^\/event\/([a-zA-Z0-9:._-]{1,100})$/ udpApi.checkaccess = authorize('publish') udpApi.on 'message', (msg, rinfo) -> zlib.unzip msg, (err, msg) => if err or not msg.toString() logger.error("UDP Cannot decode message: #{err}") return [method, msg] = msg.toString().split(/\s+/, 2) if not msg then [msg, method] = [method, 'POST'] req = url.parse(msg ? '', true) method = method.toUpperCase() # emulate an express route middleware call @checkaccess {socket: remoteAddress: rinfo.address}, {json: -> logger.info("UDP/#{method} #{req.pathname} 403")}, -> status = 404 if m = req.pathname?.match(event_route) try event = new Event(redis, m[1]) status = 204 switch method when 'POST' then eventPublisher.publish(event, req.query) when 'DELETE' then event.delete() else status = 404 catch error logger.error(error.stack) return logger.info("UDP/#{method} #{req.pathname} #{status}") if settings.server?.access_log port = settings?.server?.udp_port if port? udpApi.bind port logger.info "Listening on udp port #{port}"