UNPKG

@barchart/common-node-js

Version:

Common classes, utilities, and functions for building Node.js servers

400 lines (350 loc) 14.1 kB
const log4js = require('log4js'); const assert = require('@barchart/common-js/lang/assert'), Enum = require('@barchart/common-js/lang/Enum'), Disposable = require('@barchart/common-js/lang/Disposable'); const EndpointBuilder = require('@barchart/common-js/api/http/builders/EndpointBuilder'), ErrorInterceptor = require('@barchart/common-js/api/http/interceptors/ErrorInterceptor'), FailureReason = require('@barchart/common-js/api/failures/FailureReason'), FailureType = require('@barchart/common-js/api/failures/FailureType'), Gateway = require('@barchart/common-js/api/http/Gateway'), ProtocolType = require('@barchart/common-js/api/http/definitions/ProtocolType'), RequestInterceptor = require('@barchart/common-js/api/http/interceptors/RequestInterceptor'), ResponseInterceptor = require('@barchart/common-js/api/http/interceptors/ResponseInterceptor'), VerbType = require('@barchart/common-js/api/http/definitions/VerbType'); const JwtProvider = require('./security/JwtProvider'); module.exports = (() => { 'use strict'; const logger = log4js.getLogger('common-node/push/PushNotificationProvider'); /** * A wrapper for the Push Notification Service. * * @public * @param {String} protocol - The protocol of the of the Push Notification service (either http or https). * @param {String} host - The hostname of the Push Notification service. * @param {Number} port - The TCP port number of the Push Notification service. */ class PushNotificationProvider extends Disposable { constructor(protocol, host, port) { super(); assert.argumentIsRequired(protocol, 'protocol', String); assert.argumentIsRequired(host, 'host', String); assert.argumentIsRequired(port, 'port', Number); const protocolType = Enum.fromCode(ProtocolType, protocol.toUpperCase()); const requestInterceptorForRegister = getRequestInterceptorForJwtForRegister.call(this); const requestInterceptorForSend = getRequestInterceptorForJwtForSend.call(this); this._protocol = protocol; this._host = host; this._port = port; this._jwtProviderRegister = null; this._jwtProviderSend = null; this._started = true; this._sendNotificationEndpoint = EndpointBuilder.for('send-notification', 'send notification') .withVerb(VerbType.POST) .withProtocol(protocolType) .withHost(host) .withPort(port) .withPathBuilder((pb) => pb.withLiteralParameter('version', 'v2') .withLiteralParameter('send', 'send') ) .withBody() .withRequestInterceptor(requestInterceptorForSend) .withResponseInterceptor(ResponseInterceptor.DATA) .withErrorInterceptor(ErrorInterceptor.GENERAL) .endpoint; this._registerDeviceEndpoint = EndpointBuilder.for('register-device', 'register device') .withVerb(VerbType.POST) .withProtocol(protocolType) .withHost(host) .withPort(port) .withPathBuilder((pb) => pb.withLiteralParameter('version', 'v2') .withLiteralParameter('register', 'register') ) .withBody() .withRequestInterceptor(requestInterceptorForRegister) .withResponseInterceptor(ResponseInterceptor.DATA) .withErrorInterceptor(ErrorInterceptor.GENERAL) .endpoint; this._unregisterDeviceEndpoint = EndpointBuilder.for('unregister-device', 'unregister device') .withVerb(VerbType.POST) .withProtocol(protocolType) .withHost(host) .withPort(port) .withPathBuilder((pb) => pb.withLiteralParameter('version', 'v2') .withLiteralParameter('unregister', 'unregister') ) .withBody() .withRequestInterceptor(requestInterceptorForRegister) .withResponseInterceptor(ResponseInterceptor.DATA) .withErrorInterceptor(ErrorInterceptor.GENERAL) .endpoint; } /** * Attempts to establish a connection to the backend. This function should be invoked * immediately following instantiation. Once the resulting promise resolves, a * connection has been established and other instance methods can be used. * * @public * @param {JwtProvider=} jwtProviderRegister - Your implementation of {@link JwtProvider} for {@link registerDevice} and {@link unregisterDevice} functions. * @param {JwtProvider=} jwtProviderSend - Your implementation of {@link JwtProvider} for {@link send} function. * @returns {Promise<PushNotificationProvider>} */ start(jwtProviderRegister, jwtProviderSend) { return Promise.resolve() .then(() => { assert.argumentIsOptional(jwtProviderRegister, 'jwtProviderRegister', JwtProvider, 'JwtProvider'); assert.argumentIsOptional(jwtProviderSend, 'jwtProviderSend', JwtProvider, 'JwtProvider'); this._jwtProviderRegister = jwtProviderRegister; this._jwtProviderSend = jwtProviderSend; this._started = true; return Promise.resolve(true) .then(() => { return Promise.resolve(this); }).catch((e) => { return Promise.reject(`Unable to connect to server using HTTP adapter [ ${this._host} ] [ ${this._port} ] [ ${this._protocol} ]`); }); }); } /** * Registers a device. * * @public * @param {Object} query - The query object * @param {Object} query.user - An object contains user data * @param {String} query.user.id - A user id * @param {String} query.user.context - A user context * @param {Object?} query.apns - An object contains APNS data * @param {String} query.apns.device - Unique device token * @param {String} query.apns.bundle - An application bundle name * @param {Object?} query.fcm - An object contains FCM data * @param {String} query.fcm.token - Unique device token * @param {String} query.fcm.package - An application package name * @param {String} query.fcm.iid - Firebase IID of device * @param {String} query.provider - Provider name * @returns {Promise<Object>} */ registerDevice(query) { return Promise.resolve() .then(() => { checkStatus(this, 'registerDevice'); return Promise.resolve() .then(() => { assert.argumentIsRequired(query, 'query', Object); assert.argumentIsRequired(query.user, 'query.user', Object); assert.argumentIsRequired(query.user.id, 'query.user.id', String); assert.argumentIsRequired(query.user.context, 'query.user.context', String); assert.argumentIsRequired(query.provider, 'query.provider', String); if (!query.apns && !query.fcm) { throw new Error('One of the arguments [ query.apns, query.fcm ] must be provided'); } if (query.apns) { assert.argumentIsRequired(query.apns, 'query.apns', Object); assert.argumentIsRequired(query.apns.device, 'query.apns.device', String); assert.argumentIsRequired(query.apns.bundle, 'query.apns.bundle', String); } if (query.fcm) { assert.argumentIsRequired(query.fcm, 'query.fcm', Object); assert.argumentIsRequired(query.fcm.iid, 'query.fcm.iid', String); assert.argumentIsRequired(query.fcm.package, 'query.fcm.package', String); assert.argumentIsRequired(query.fcm.token, 'query.fcm.token', String); } return Gateway.invoke(this._registerDeviceEndpoint, query); }); }); } /** * Unregisters a device. * * @public * @param {Object} query - The query object * @param {Object} query.user - An object contains user data * @param {String} query.user.id - A user id * @param {String} query.user.context - A user context * @param {Object} query.device - An object contains APNS or FCM data * @param {String} query.device.device - APNS device token or FCM IID * @param {String} query.device.bundle - Bundle or Package name of the application * @returns {Promise<Object>} */ unregisterDevice(query) { return Promise.resolve() .then(() => { checkStatus(this, 'unregisterDevice'); return Promise.resolve() .then(() => { assert.argumentIsRequired(query, 'query', Object); assert.argumentIsRequired(query.user, 'query.user', Object); assert.argumentIsRequired(query.user.id, 'query.user.id', String); assert.argumentIsRequired(query.user.context, 'query.user.context', String); assert.argumentIsRequired(query.device, 'query.device', Object); assert.argumentIsRequired(query.device.device, 'query.device.device', String); assert.argumentIsRequired(query.device.bundle, 'query.device.bundle', String); return Gateway.invoke(this._unregisterDeviceEndpoint, { user: query.user.id, context: query.user.context, device: query.device.device, bundle: query.device.bundle, }); }); }); } /** * Sends a Push Notifications by application bundle or package name. * * @public * @param {Object} query - The query object * @param {String} query.bundle - An application bundle or package name * @param {Object} query.notification - An notification object * @param {Boolean?} query.development - Forces APNS to send notifications in the development mode * @returns {Promise<Object>} */ sendByBundle(query) { return Promise.resolve() .then(() => { checkStatus(this, 'sendByBundle'); return Promise.resolve() .then(() => { assert.argumentIsRequired(query, 'query', Object); assert.argumentIsRequired(query.bundle, 'query.bundle', String); return send.call(this, { bundle: query.bundle, notification: query.notification, development: query.development }); }); }); } /** * Sends a Push Notifications by user. * * @public * @param {Object} query - The query object * @param {Object} query.user - A user object * @param {String} query.user.id - A user id * @param {String} query.user.context - A user context * @param {String} query.bundle - An application bundle or package name * @param {Object} query.notification - An notification object * @param {Boolean?} query.development - Forces APNS to send notifications in the development mode * @returns {Promise<Object>} */ sendByUser(query) { return Promise.resolve() .then(() => { checkStatus(this, 'sendByUser'); return Promise.resolve() .then(() => { assert.argumentIsRequired(query, 'query', Object); assert.argumentIsRequired(query.bundle, 'query.bundle', String); assert.argumentIsRequired(query.user.id, 'query.user.id', String); assert.argumentIsRequired(query.user.context, 'query.user.context', String); return send.call(this, { bundle: query.bundle, user: query.user.id, context: query.user.context, notification: query.notification, development: query.development }); }); }); } /** * Sends a Push Notifications by device. * * @public * @param {Object} query - The query object * @param {String} query.device - An APNS device token or FCM IID * @param {Object} query.notification - An notification object * @param {Boolean?} query.development - Forces APNS to send notifications in the development mode * @returns {Promise<Object>} */ sendByDevice(query) { return Promise.resolve() .then(() => { checkStatus(this, 'sendByDevice'); return Promise.resolve() .then(() => { assert.argumentIsRequired(query, 'query', Object); assert.argumentIsRequired(query.device, 'query.device', String); return send.call(this, { device: query.device, notification: query.notification, development: query.development }); }); }); } _onDispose() { logger.debug('Push Notification provider disposed'); this._jwtProviderRegister = null; this._started = false; } toString() { return '[PushNotificationProvider]'; } } function getRequestInterceptorForJwtForRegister() { return RequestInterceptor.fromDelegate((options, endpoint) => { const getFailure = (e) => { return FailureReason.forRequest({endpoint: endpoint}) .addItem(FailureType.REQUEST_IDENTITY_FAILURE) .format(); }; if (this._jwtProviderRegister === null) { return Promise.reject(getFailure()); } return this._jwtProviderRegister.getToken() .then((token) => { options.headers = options.headers || {}; options.headers.Authorization = `Bearer ${token}`; return options; }).catch((e) => { return Promise.reject(getFailure(e)); }); }); } function getRequestInterceptorForJwtForSend() { return RequestInterceptor.fromDelegate((options, endpoint) => { const getFailure = (e) => { return FailureReason.forRequest({endpoint: endpoint}) .addItem(FailureType.REQUEST_IDENTITY_FAILURE) .format(); }; if (this._jwtProviderSend === null) { return Promise.reject(getFailure()); } return this._jwtProviderSend.getToken() .then((token) => { options.headers = options.headers || {}; options.headers.Authorization = `Bearer ${token}`; return options; }).catch((e) => { return Promise.reject(getFailure(e)); }); }); } function send(query) { return Promise.resolve().then(() => { assert.argumentIsRequired(query.notification, 'notification', Object); assert.argumentIsRequired(query.notification.title, 'notification.title', String); assert.argumentIsRequired(query.notification.body, 'notification.body', String); assert.argumentIsOptional(query.development, 'query.development', Boolean); return Gateway.invoke(this._sendNotificationEndpoint, { ...query, development: query.development === true }); }); } function checkDispose(provider, operation) { if (provider.getIsDisposed()) { throw new Error(`Unable to perform ${operation}, the Push Notification Provider has been disposed`); } } function checkStatus(provider, operation) { checkDispose(provider, operation); if (provider._started !== true) { throw new Error(`Unable to perform ${operation}, the Push Notification Provider has not connected to the server`); } } return PushNotificationProvider; })();