var debug = require('debug')('jpush') var pkg = require('../../package') var JError = require('./JPushError') var Request = require('request-promise') var headers = { 'User-Agent': 'JPush-API-NodeJS-Client ' + pkg.version, 'Connection': 'Keep-Alive', 'Charset': 'UTF-8', 'Content-Type': 'application/json' } var JModel = require('./PushPayloadAsync') var PUSH_API_URL = 'https://api.jpush.cn/v3/push' var GROUP_API_URL = 'https://api.jpush.cn/v3/grouppush' var REPORT_API_URL = 'https://report.jpush.cn/v3' var SCHEDULE_API_URL = 'https://api.jpush.cn/v3/schedules' // 定时任务 var REPORT_RECEIVED = '/received' var REPORT_USER = '/users' var REPORT_MESSAGE = '/messages' var HOST_NAME_SSL = 'https://device.jpush.cn' var DEVICE_PATH = '/v3/devices' var TAG_PATH = '/v3/tags/' var ALIAS_PATH = '/v3/aliases' var VALIDATE = '/validate' var DEFAULT_MAX_RETRY_TIMES = 3 var READ_TIMEOUT = 30 * 1000 // Pattern var PUSH_PATTERNS = /^[a-zA-Z0-9]{24}/ var MSG_IDS_PATTERNS = /[^\d,]/ exports.buildClient = function (appKey, masterSecret, retryTimes, isDebug, readTimeOut, proxy, isGroup = false) { if (arguments.length === 1 && typeof arguments[0] === 'object') { var options = arguments[0] return new JPushClient(options.appKey, options.masterSecret, options.retryTimes, options.isDebug, options.readTimeOut, options.proxy, options.isGroup ) } else { return new JPushClient(appKey, masterSecret, retryTimes, isDebug, readTimeOut, proxy, isGroup) } } function JPushClient (appKey, masterSecret, retryTimes, isDebug, readTimeOut = null, proxy = null, isGroup) { if (!appKey || !masterSecret) { throw JError.InvalidArgumentError('Key and Secret are both required.') } this.isGroup = isGroup; if (typeof appKey !== 'string' || typeof masterSecret !== 'string' || !PUSH_PATTERNS.test(appKey) || !PUSH_PATTERNS.test(masterSecret)) { throw new JError.InvalidArgumentError('appKey and masterSecret format is incorrect. ' + 'They should be 24 size, and be composed with alphabet and numbers. ' + 'Please confirm that they are coming from JPush Web Portal.') } this.appkey = isGroup ? 'group-' + appKey : appKey this.masterSecret = masterSecret if (retryTimes) { if (typeof retryTimes !== 'number') { throw JError.InvalidArgumentError('Invalid retryTimes.') } this.retryTimes = retryTimes } else { this.retryTimes = DEFAULT_MAX_RETRY_TIMES } if (isDebug != null) { this.isDebug = isDebug } else { this.isDebug = true } if (readTimeOut != null) { this.readTimeOut = readTimeOut } else { this.readTimeOut = READ_TIMEOUT } if (proxy != null) { this.proxy = proxy } } /** * create a push payload * @returns {exports.PushPayload} */ function push () { return new JModel.PushPayload(this) } async function sendPush (payload) { return _request(this, this.isGroup === true ? GROUP_API_URL : PUSH_API_URL, payload, 'POST') } async function getReportReceiveds (msgIds) { if (MSG_IDS_PATTERNS.test(msgIds)) { throw new JError.InvalidArgumentError( 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') } var url = REPORT_API_URL + REPORT_RECEIVED + '?msg_ids=' + msgIds return _request(this, url, null, 'GET') } async function getReportMessages (msgIds) { if (MSG_IDS_PATTERNS.test(msgIds)) { throw new JError.InvalidArgumentError( 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') } var url = REPORT_API_URL + REPORT_MESSAGE + '?msg_ids=' + msgIds return _request(this, url, null, 'GET') } async function getReportUsers (timeUnit, start, duration) { var url = REPORT_API_URL + REPORT_USER + '?time_unit=' + timeUnit + '&start=' + start + '&duration=' + duration return _request(this, url, null, 'GET') } /** * device api * * @param registrationId */ async function getDeviceTagAlias (registrationId) { var url = HOST_NAME_SSL + DEVICE_PATH + '/' + registrationId return _request(this, url, null, 'GET') } // 结合短信业务使用,需要先调用该方法将用户的手机号码与设备的 registration id 匹配。 async function setMobile (registrationId, mobileNumber) { var json = {} json['mobile'] = mobileNumber var url = HOST_NAME_SSL + DEVICE_PATH + '/' + registrationId return _request(this, url, json, 'POST') } async function updateDeviceTagAlias (registrationId, alias, clearTag, tagsToAdd, tagsToRemove) { var url = HOST_NAME_SSL + DEVICE_PATH + '/' + registrationId if (tagsToAdd instanceof Array && tagsToRemove instanceof Array) { var json = {} var tags = {} if (alias != null) { json['alias'] = alias } if (clearTag) { json['tags'] = '' } else { if (tagsToAdd != null && tagsToAdd.length > 0) { tags['add'] = tagsToAdd } if (tagsToRemove != null && tagsToRemove.length > 0) { tags['remove'] = tagsToRemove } json['tags'] = tags debug(json) } } else { throw new JError.InvalidArgumentError('tagsToAdd or tagsToRemove type should be array') } return _request(this, url, json, 'POST') } async function getTagList () { var url = HOST_NAME_SSL + TAG_PATH return _request(this, url, null, 'GET') } async function isDeviceInTag (theTag, registrationID) { var url = HOST_NAME_SSL + TAG_PATH + '/' + theTag + '/registration_ids/' + registrationID return _request(this, url, null, 'GET') } async function addRemoveDevicesFromTag (theTag, toAddUsers, toRemoveUsers) { var url = HOST_NAME_SSL + TAG_PATH + theTag var registrationIds = {} if (toAddUsers != null && toAddUsers.length > 0) { registrationIds['add'] = toAddUsers } if (toRemoveUsers != null && toRemoveUsers.length > 0) { registrationIds['remove'] = toRemoveUsers } var json = {} json['registration_ids'] = registrationIds debug(json['registration_ids']) return _request(this, url, json, 'POST') } async function deleteTag (theTag, platform) { var url = HOST_NAME_SSL + TAG_PATH + '/' + theTag if (platform != null) { url += ('/?platform=' + platform) } return _request(this, url, null, 'delete') } async function getAliasDeviceList (alias, platform) { var url = HOST_NAME_SSL + ALIAS_PATH + '/' + alias if (platform != null) { url += '/?platform=' + platform } return _request(this, url, null, 'GET') } async function deleteAlias (alias, platform) { var url = HOST_NAME_SSL + ALIAS_PATH + '/' + alias if (platform != null) { url += '/?platform=' + platform } return _request(this, url, null, 'delete') } async function validate (payload) { return _request(this, PUSH_API_URL + VALIDATE, payload, 'POST') } // 定时任务 start async function setSchedule (payload) { return _request(this, SCHEDULE_API_URL, payload, 'POST') } async function updateSchedule (scheduleId, payload) { var url = SCHEDULE_API_URL + '/' + scheduleId return _request(this, url, payload, 'PUT') } // 获取有效的定时任务列表。 async function getScheduleList (page) { if (typeof page !== 'number') { throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the Number.') } var url = SCHEDULE_API_URL + '?page=' + page return _request(this, url, null, 'GET') } // 获取指定的定时任务。 async function getSchedule (scheduleId) { if (typeof scheduleId !== 'string') { throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the String.') } var url = SCHEDULE_API_URL + '/' + scheduleId return _request(this, url, null, 'GET') } // 删除指定的定时任务。 async function delSchedule (scheduleId) { if (typeof scheduleId !== 'string') { throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the String.') } var url = SCHEDULE_API_URL + '/' + scheduleId return _request(this, url, null, 'DELETE') } // 获取定时任务对应的所有 msg_id async function getScheduleMsgIds (scheduleId, callback) { if (typeof scheduleId !== 'string') { throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the String.') } var url = SCHEDULE_API_URL + '/' + scheduleId + '/msg_ids' return _request(this, url, null, 'GET', callback) } // 定时任务 end // Proxy start // Proxy end async function _request (client, url, body, method, times = 1) { if (client.isDebug) { debug('Push URL :' + url) if (body) { debug('Body :' + body) } // debug("Auth :" + JSON.stringify(auth)) debug('Method :' + method) debug('Times/MaxTryTimes : ' + times + '/' + client.maxTryTimes) } var options = { method: method.toUpperCase(), json: true, uri: url, body: body, auth: { user: client.appkey, pass: client.masterSecret }, timeout: client.readTimeOut, proxy: client.proxy }; try { return await Request(options); } catch (err) { if (err.error.code === 'ETIMEDOUT' && err.error.syscall !== 'connect') { // response timeout throw new JError.APIConnectionError( 'Response timeout. Your request to the server may have already received, please check whether or not to push', true) } else if (err.error.code === 'ENOTFOUND') { // unknown host throw new JError.APIConnectionError('Unknown host : ' + url) } else if (times < client.maxTryTimes) { return _request(client, url, body, method, times + 1) } else { if (client.isDebug) { debug('Fail, HttpStatusCode: ' + err.statusCode + ' result: ' + JSON.stringify(err.error)) } throw new JError.APIRequestError(err.statusCode, JSON.stringify(err.error)) } } } JPushClient.prototype.sendPush = sendPush JPushClient.prototype.getReportReceiveds = getReportReceiveds JPushClient.prototype.push = push JPushClient.prototype.setMobile = setMobile JPushClient.prototype.getDeviceTagAlias = getDeviceTagAlias JPushClient.prototype.updateDeviceTagAlias = updateDeviceTagAlias JPushClient.prototype.getTagList = getTagList JPushClient.prototype.isDeviceInTag = isDeviceInTag JPushClient.prototype.addRemoveDevicesFromTag = addRemoveDevicesFromTag JPushClient.prototype.deleteTag = deleteTag JPushClient.prototype.getAliasDeviceList = getAliasDeviceList JPushClient.prototype.deleteAlias = deleteAlias JPushClient.prototype.validate = validate JPushClient.prototype.getReportMessages = getReportMessages JPushClient.prototype.getReportUsers = getReportUsers JPushClient.prototype.getScheduleList = getScheduleList JPushClient.prototype.getSchedule = getSchedule JPushClient.prototype.delSchedule = delSchedule JPushClient.prototype.setSchedule = setSchedule JPushClient.prototype.updateSchedule = updateSchedule // exports constants and methods exports.ALL = JModel.ALL exports.tag = JModel.tag exports.tag_and = JModel.tag_and exports.tag_not = JModel.tag_not exports.alias = JModel.alias exports.registration_id = JModel.registration_id exports.segment = JModel.segment exports.abtest = JModel.abtest exports.ios = JModel.ios exports.android = JModel.android exports.winphone = JModel.winphone // error exports.APIConnectionError = JError.APIConnectionError exports.APIRequestError = JError.APIRequestError exports.InvalidArgumentError = JError.InvalidArgumentError