├── .gitignore ├── .travis.yml ├── README.md ├── push-client ├── .gitignore ├── .npmignore ├── README.md ├── index.js ├── lib │ └── localStorage.js ├── package.json ├── push-client-1.0.js ├── push-client-1.0.min.js ├── webpack.config.js └── webpack.sh ├── push-server ├── .gitignore ├── .npmignore ├── 2idc_socket.io-push-new.png ├── PUSH-API.md ├── README.md ├── bin │ ├── betaPublish.sh │ ├── generate-push-server-config │ ├── node-push-server │ ├── publish.sh │ ├── push-server │ └── push-server-kill ├── cert │ ├── com.xuduo.pushtest.p8 │ ├── com.xuduopushtest.p8 │ ├── entrust_2048_ca.cer │ └── https │ │ ├── cert.pem │ │ └── key.pem ├── config-admin.js ├── config-api.js ├── config-apn-proxy.js ├── config-log.js ├── config-proxy.js ├── coverage │ ├── coverage.json │ └── lcov.info ├── index.js ├── lib │ ├── admin.js │ ├── admin │ │ └── adminServer.js │ ├── api.js │ ├── api │ │ └── restApi.js │ ├── apnProxy.js │ ├── mongo │ │ └── mongo.js │ ├── proxy.js │ ├── redis │ │ └── uidStore.js │ ├── server │ │ └── proxyServer.js │ ├── service │ │ ├── apiRouter.js │ │ ├── apnProvider.js │ │ ├── deviceService.js │ │ ├── fcmProvider.js │ │ ├── huaweiProvider.js │ │ ├── notificationProviderFactory.js │ │ ├── notificationService.js │ │ ├── packetService.js │ │ ├── ttlService.js │ │ ├── umengProvider.js │ │ └── xiaomiProvider.js │ ├── stats │ │ ├── arrivalStats.js │ │ ├── moving-sum.js │ │ ├── redisIncrBuffer.js │ │ ├── stats.js │ │ └── topicOnline.js │ └── util │ │ ├── generateConfig.js │ │ ├── infiniteArray.js │ │ ├── ip.js │ │ ├── paramParser.js │ │ └── versionCompare.js ├── package-lock.json ├── package.json ├── static │ ├── arrivalRate │ │ └── index.html │ ├── client │ │ ├── draw.html │ │ ├── httpProxy.html │ │ ├── index.html │ │ ├── main.js │ │ └── style.css │ ├── handleStatsBase │ │ └── index.html │ ├── index.html │ ├── js │ │ ├── Chart.Core.js │ │ ├── Chart.Core.min.js │ │ ├── Chart.Core.min.js.gzip │ │ ├── Chart.Core.min.js.map │ │ ├── Chart.Scatter.js │ │ ├── Chart.Scatter.min.js │ │ ├── Chart.Scatter.min.js.gzip │ │ ├── Chart.Scatter.min.js.map │ │ ├── Chart.Scatter.sln │ │ ├── jquery-1.8.0.min.js │ │ ├── jquery.cookie.js │ │ ├── jquery.mobile-1.2.0.min.css │ │ ├── jquery.mobile-1.2.0.min.js │ │ ├── push-client-1.0.js │ │ └── push-client-1.0.min.js │ ├── notification │ │ └── index.html │ ├── push │ │ └── index.html │ ├── stats │ │ └── index.html │ └── uid │ │ └── index.html └── test │ ├── apiAuth.js │ ├── apiRouterTest.js │ ├── apnProviderSendAllTest.js │ ├── apnProviderSendOneTest.js │ ├── arrivalStatsTest.js │ ├── bindUidTest.js │ ├── defaultSetting.js │ ├── deleteDeviceTest.js │ ├── duplicateTokenTest.js │ ├── fcmProviderTest.js │ ├── huaweiProviderTest.js │ ├── infiniteArrayTest.js │ ├── mocha.opts │ ├── moving-sum.js │ ├── notificationTagFilterTest.js │ ├── notificationTest.js │ ├── pushHttpsTest.js │ ├── pushMixTest.js │ ├── pushTest.js │ ├── restApiParamCheck.js │ ├── restApiTest.js │ ├── setTagsTest.js │ ├── setTokenTest.js │ ├── statsTest.js │ ├── topicOnline.js │ ├── ttlServiceSingleTest.js │ ├── ttlServiceTopicTest.js │ ├── ttlServiceUidTest.js │ ├── uidPlatformTest.js │ ├── uidTest.js │ ├── umengProviderTest.js │ ├── unsubscribeTest.js │ ├── versionCompareTest.js │ └── xiaomiProviderTest.js ├── readmes ├── arrive-rate.md ├── bench-mark.md ├── broadcast.gif ├── notification-keng.md ├── notification.gif └── topic.md ├── socket.io-push-redis ├── .gitignore ├── .npmignore ├── adapter.js ├── cluster.js ├── config-log.js ├── emitter.js ├── package-lock.json ├── package.json ├── test.js ├── test │ ├── adapterTest.js │ ├── clusterTest.js │ ├── emitterTest.js │ └── utilTest.js └── util.js └── winston-proxy ├── .npmignore ├── config-log.js ├── index.js ├── package-lock.json ├── package.json └── test └── logTest.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | local.properties 3 | *.iml 4 | node_modules 5 | *.log 6 | dump.rdb 7 | log.txt 8 | pid 9 | pids 10 | pid_debug 11 | .gradle 12 | build -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6.1' 4 | services: 5 | - redis-server 6 | - mongodb 7 | before_install: 8 | - cd push-server 9 | after_success: 10 | - npm install -g istanbul 11 | - npm install coveralls 12 | - istanbul cover node_modules/.bin/_mocha --report lcovonly -- -R spec 13 | - ./node_modules/.bin/coveralls < ./coverage/lcov.info 14 | - rm -rf ./coverage 15 | deploy: 16 | provider: npm 17 | email: xuudoo@gmail.com 18 | api_key: 19 | secure: NTnnY0qfth+e7KyPQITiU95uA5vTeFiHdoM+CEcQmkXRJ6ptiutrJ9WWLuECcyJBKGV+HblOUcAgXX6W63+TBETSyF4r1T6yxmxdFGFzTFCp2Y4cgdQ2XDN7gSNygZkpGtHiRo7W/c1+UqBDHeE+ciDOO8XEgKKcswXWFJcGHzd6bMyX88e3V08CTpOl/i6rW4xIeH25Z1X/du6vpSBg4cFNMRDOJTBYMlYYXWrxR4/EsVj1VxF7/kDGQrz1ttzsRXTZXNOAvDMEDrPAwDsdhasjz7ve4LTR7+WvG0iyvQZE79QR3hluPZMLWz4Vs3GdA62rrvJIgtJNfM89+o4ylzJ1JwGzYTYjm8+l4N+podqK+94ZRDVb1OCpGZwbrumSbilnnULGPKg3XuT3KjIaZ8cRJwMy21SsZItPvS06BDWzF7sRe01ypmgoJaetSHnkBIKj4eklFP8aimOkatJOg+nbPB6GOEqunbuKmkZu43BlSrJebMiYUMCvwmWm+zW4q6KzVTJBYqE04o3Mh8J+vXgxmgifFUrJ2vVGd0cfIwzLPRiMUcrpOww1ONgiNn2ughE/9FBviulispVHD/GuWR3ugaGh25d8EbdKQCSI/Ub+Eei/HG0r9EN8uMjeKadJ70kS3erGizXgh31S9ii78GbWyarxX4p/yXNP5mF2lr4= 20 | on: 21 | tags: true 22 | repo: xuduo/socket.io-push 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | socket.io-push [![Build Status](https://travis-ci.org/xuduo/socket.io-push.svg?branch=master)](https://travis-ci.org/xuduo/socket.io-push) [![Coverage Status](https://coveralls.io/repos/github/xuduo/socket.io-push/badge.svg?branch=master&a=1)](https://coveralls.io/github/xuduo/socket.io-push?branch=master&a=1) 2 | ======================= 3 | 整合了小米,华为,友盟,苹果推送的统一解决方案 4 | 5 | 更有应用内超低延迟顺序(生产环境平均200MS以下)透传功能,支持websocket 6 | 7 | ![白板](https://github.com/xuduo/socket.io-push/raw/master/readmes/broadcast.gif) 8 | 9 | 广播可支持同频道10w以上在线,每台前端(proxy)可支持5W以上长连接(取决于你的推送量) 10 | 11 | ![notification](https://github.com/xuduo/socket.io-push/raw/master/readmes/notification.gif) 12 | 13 | 目前处于只修bug,不会更新新功能状态 14 | 15 | [视频介绍](http://www.bilibili.com/video/av8531451/) 16 | 17 | [![NPM](https://nodei.co/npm/socket.io-push.png?compact=true)](https://npmjs.org/package/socket.io-push) 18 | 19 | ### 为什么做这个东西? 20 | 21 | 我们Java后端开发,以提供HTTP API的方式,做了几个简单的增删改查式的APP,但是在开发一款直播类APP的时候遇到了问题 22 | 23 | * 直播间内的公屏,礼物广播,如何以最低延迟发给直播间内的用户? 24 | * 频道里都有谁在线? 都是哪些人? 25 | * 主播断线,或者与主播连麦的人断线了,如何知晓? 26 | * 如何推送离线系统通知(IOS,Android,小米,华为)? 27 | 28 | 29 | 参考其他一些团队的解决方案,大多是用c++写长连接服务器,甚至用Java的netty。开发维护成本太高,所以就有了这套推送系统,我们服务器依旧使用HTTP提供服务,下行推送,也只需要调用一个简单的HTTP接口,如 http://localhost:11001/api/push?topic=abc&json=hello 向abc这个直播间发送一条透传。 30 | 31 | 32 | ### 特点 33 | * 厂商通道: 透明集成了小米, 华为push,第三方推送由于政策问题,无法做到 34 | * 非厂商通道: 友盟推送 + socket.io 双通道客户端sdk去重, 可以享受友盟号称上万APP互相拉起, 大概可以增加非厂商通道50%以上送达率 35 | * IOS推送实现了可以部署代理节点, 可以通过国内专线->香港->苹果服务器, 极大提升成功率和吞吐能力 36 | * 支持浏览器, 微信小程序, Unity3D (完成度很低) 37 | * 与业务服务同机房部署,第三方服务无法比拟 38 | 39 | ### 性能 40 | * 目前使用的最高日活的一款APP有350W日活, 评估目前架构至少可以支持千万日活APP 41 | * 可以部署70台(实测)或者更多机器, 支持百万以上同时在线 42 | * 单机广播速度可以达到[10W条/秒](bench-mark.md),如果只使用系统通知功能,单机支撑一个10W左右日活的APP,平均1W以上同时在线,几乎不占机器负载 43 | * 从推送接口调用,到客户端收到回包给服务器,RTT只有280m(线上平均延迟) 44 | 45 | ### 更新日志 46 | * 0.9.14 api增加删除设备的接口 47 | * 0.9.13 支持按uid,pushId查询推送送达率 48 | * 0.9.12 支持谷歌的fcm推送 49 | * 0.9.11 去掉apiRouter中的buffer机制,避免拥堵延迟 50 | * 0.9.10 更新调用华为新版接口(华为开发者后台需要配置apk的sha256指纹) 51 | * 0.9.9 修复自动重启进程的问题,更新node-apn版本 52 | * 0.9.7 修正小米notify_foreground配置未生效 53 | * 0.9.6 修正 push uid ttl未生效 54 | * 0.9.5 修正notificationBuffer删错方向bug 55 | * 0.9.4 修正demo后台bug 56 | * 0.9.3 notfication加个type用于统计 57 | * 0.9.3 删除redis-adapter一个指数级的调用 58 | * 0.9.1 重构redis-adapter,优化批量推送大量uid的性能 59 | * 0.8.97 接口支持post100mb以上数据 60 | 61 | ### 基本功能和实现原理 62 | 63 | #### 1. push (在线透传) 64 | 65 | ###### 目标: 66 | 67 | * pushId: 对某个设备 68 | * topic: 已经订阅了某topic的设备列表,订阅关系在redis和socket.io实例里保存,如socket.io服务重启,会丢失,客户端重连上来会自动重新订阅建立关系 69 | * uid: 已绑定的设备列表,设备连接后读一次数据库,然后此关系以topic的方式实现 70 | 71 | ###### 实现原理: 72 | 73 | 使用socket.io通道,只针对当时在线,也可以通过制定timeToLive参数实现重传, 无论push给任何目标,只有一次redis pub操作,不走数据库,可靠性,速度非常高 74 | 75 | #### 2. notification(手机系统通知栏) 76 | 77 | 78 | ###### 目标: 79 | 80 | * pushId: 对某个设备 81 | * uid: 已绑定的设备列表 82 | * tags: 绑定了某tag的设备列表,存储在数据库持久化 83 | * pushAll: 推送所有设备 84 | 85 | ###### 实现原理: 86 | 87 | 无论给哪个目标发,都要查一次mongodb,用于确定目标设备的类型和token。 88 | 89 | * ios设备,走苹果apn推送。 90 | * 小米和华为,走该厂商通道。 91 | * 其它设备(安卓或者浏览器等),走socket.io push通道。 如有上报umeng token,会调用友盟推送再发一次。安卓客户端有可能收到两次,SDK层做去重保证手机只弹出一次。 92 | 93 | 94 | ### Quick Start 95 | * [5分钟搭建一个单机服务器](push-server) 96 | * [服务器推送Api文档](push-server/PUSH-API.md) 97 | * [Android SDK & Demo](https://github.com/xuduo/socket.io-push-android) 98 | * [IOS SDK & Demo](https://github.com/xuduo/socket.io-push-ios) 99 | * [Browser sdk](push-client) 100 | * QQ技术支持群 128769919 101 | 102 | ### 高级功能文档 103 | * [topic相关(用于实现如直播间观众列表,弹幕,礼物,支持同直播间20W人以上在线)](readmes/topic.md) 104 | * 多机器/机房部署 105 | * 苹果推送代理服务器(用于解决api服务器连接美国网络较差问题) 106 | * 统计相关 107 | 108 | ### 相关文章 109 | * [推送中的坑](readmes/notification-keng.md) 110 | * [送达率计算](readmes/arrive-rate.md) 111 | 112 | 113 | ### Q&A 114 | * 相比第三方推送有什么优劣? 115 | 116 | 优势: 117 | 118 | 1. 同机房调用, 成功率100% vs 第三方 99.2%(我们调用小米接口成功率) 99.6%(我们调用华为接口成功率) 119 | 2. 测试,正式环境,可以分开部署, 完全隔离 120 | 3. 支持苹果推送多bundleId, 开发,发布,马甲版, 都可以自动匹配推送 121 | 4. 苹果推送进程可以独立部署在香港/国外 122 | 123 | 劣势 124 | 125 | 1. 需要自己运维部署服务器 126 | 2. 如果需要扩容, 需要自己来评估, 第三方推送通常是给钱就可以了 127 | ======= 128 | 129 | ### 名词 130 | * `push-server` 推送服务器, 提供客户端长连接, http api接口 131 | * `业务服务器` push-server api的调用方 132 | * `客户端` 业务app服务器 133 | * `长连接` 客户端到push-server之间的socket.io连接 134 | * `notification` 发送通知栏消息, ios走apns通道, 华为,小米走厂商通道(如配置开启), 浏览器/android手机走长连接 135 | * `push` 协议透传, 走长连接通道. app主进程存活的时候才能收到.主要应用场景如直播间聊天,送礼物,股价实时推送 136 | * `topic` 服务器push广播的对象,类似于直播间/频道的概念, 客户端进入某直播间(id=001)后(topic="room001"),业务服务器可以向此topic发聊天push,subscribe了这个topic的客户端即可收到push 137 | * `pushId` 某个设备的唯一标识, app安装后生成的随机字符串, 用于服务器单播 138 | * `uid` 业务服务器用于标识某个用户的id,字符串类型.可以通过push-server的接口进行绑定,通过客户端SDK解除绑定 139 | * `timeToLive` 过期时间 140 | -------------------------------------------------------------------------------- /push-client/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | local.properties 3 | *.iml 4 | node_modules 5 | *.log 6 | dump.rdb 7 | log.txt 8 | pid 9 | pids 10 | pid_debug 11 | logs 12 | ip 13 | localstorage/ -------------------------------------------------------------------------------- /push-client/.npmignore: -------------------------------------------------------------------------------- 1 | config.js -------------------------------------------------------------------------------- /push-client/README.md: -------------------------------------------------------------------------------- 1 | #### node 依赖 2 | 3 | ``` 4 | npm install socket.io-push-client --save 5 | ``` 6 | 7 | #### js sdk 8 | [source](../../static/js/push-client-1.0.js) 9 | 10 | [minified](../../static/js/push-client-1.0.min.js) 11 | 12 | 13 | #### 初始化 14 | 15 | ```javascript 16 | var pushClient = PushClient('https://spush.yy.com', { 17 |            transports: ['websocket'], //使用的tranport, proxy如果是多进程模式, 不支持polling 18 |            useNotification: true //是否接收notification 19 | }); 20 | ``` 21 | 22 | #### 获取pushId 23 | ```javascript 24 | var pushId = pushClient.pushId 25 | ``` 26 | 27 | #### subscribe topic 28 | 29 | ```javascript 30 | var topic = 'topic'; 31 | pushClient.subscribeTopic(topic); 32 | pushClient.unsubscribeTopic(topic); 33 | ``` 34 | 35 | #### push callback 36 | 37 | ```javascript 38 | pushClient.on('push',function(data){ 39 | 40 | }); 41 | ``` 42 | 43 | #### notification callback 44 | 45 | ```javascript 46 | pushClient.on('notification',function(notification){ 47 | console.log('',notification.title,notification.message,notification.payload); 48 | }); 49 | ``` 50 | 51 | #### connect callback 52 | 53 | ```javascript 54 | pushClient.on('connect',function(data){ 55 | console.log('',data.uid); 56 | }); 57 | 58 | pushClient.on('disconnect',function(){ 59 | 60 | }); 61 | ``` 62 | -------------------------------------------------------------------------------- /push-client/lib/localStorage.js: -------------------------------------------------------------------------------- 1 | module.exports = exports = LocalStorage; 2 | 3 | function LocalStorage() { 4 | if (!(this instanceof LocalStorage)) return new LocalStorage(); 5 | this.cache = {}; 6 | } 7 | 8 | // private 9 | LocalStorage.prototype.getItem = function (key) { 10 | return this.cache[key]; 11 | } 12 | 13 | // private 14 | LocalStorage.prototype.setItem = function (key, val) { 15 | this.cache[key] = val; 16 | } -------------------------------------------------------------------------------- /push-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-push-client", 3 | "version": "0.8.2", 4 | "description": "socket.io-push client by xuduo", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:xuduo/socket.io-push.git" 9 | }, 10 | "dependencies": { 11 | "socket.io-client": "1.7.2", 12 | "wxapp-socket-io": "1.0.0" 13 | }, 14 | "devDependencies": { 15 | "babel-cli": "^6.10.1", 16 | "babel-core": "^6.10.4", 17 | "babel-eslint": "^6.1.0", 18 | "babel-loader": "^6.2.10", 19 | "babel-plugin-add-module-exports": "^0.2.1", 20 | "babel-plugin-external-helpers": "^6.8.0", 21 | "babel-plugin-syntax-async-functions": "^6.8.0", 22 | "babel-plugin-transform-runtime": "^6.9.0", 23 | "babel-preset-es2015": "^6.16.0", 24 | "babel-preset-es2015-rollup": "^1.2.0", 25 | "babel-preset-eslatest-node6": "^1.0.1", 26 | "babel-register": "^6.9.0", 27 | "debug": "^2.2.0", 28 | "eslint": "^3.0.0", 29 | "eslint-config-airbnb": "^9.0.1", 30 | "eslint-config-standard": "^5.3.1", 31 | "eslint-plugin-babel": "^3.3.0", 32 | "eslint-plugin-import": "^1.10.2", 33 | "eslint-plugin-jsx-a11y": "^1.5.3", 34 | "eslint-plugin-promise": "^1.3.2", 35 | "eslint-plugin-react": "^5.2.2", 36 | "eslint-plugin-standard": "^1.3.2", 37 | "nodemon": "^1.9.2", 38 | "rollup": "^0.36.3", 39 | "rollup-plugin-babel": "^2.6.1", 40 | "rollup-plugin-commonjs": "^5.0.4", 41 | "rollup-plugin-eslint": "^3.0.0", 42 | "rollup-plugin-node-resolve": "^2.0.0", 43 | "rollup-plugin-replace": "^1.1.1", 44 | "rollup-plugin-uglify": "^1.0.1", 45 | "webpack": "^1.14.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /push-client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const ENV = process.env.NODE_ENV; 2 | const webpack = require('webpack'); 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: './index.js', 7 | output: { 8 | library: 'PushClient', 9 | libraryTarget: 'umd', 10 | path: './', 11 | filename: 'push-client-1.0.js' 12 | }, 13 | externals: { 14 | global: glob() 15 | }, 16 | devtool: '#source-map', 17 | module: { 18 | loaders: [ 19 | { 20 | test: /\.js$/, 21 | include: path.resolve(__dirname, './'), 22 | loaders: ['babel'], 23 | }], 24 | }, 25 | plugins: [ 26 | new webpack.DefinePlugin({ 27 | 'process.env.NODE_ENV': JSON.stringify(ENV), 28 | }), 29 | ] 30 | }; 31 | 32 | /** 33 | * Populates `global`. 34 | * 35 | * @api private 36 | */ 37 | 38 | function glob() { 39 | return 'typeof self !== "undefined" ? self : ' + 40 | 'typeof window !== "undefined" ? window : ' + 41 | 'typeof global !== "undefined" ? global : {}'; 42 | } 43 | -------------------------------------------------------------------------------- /push-client/webpack.sh: -------------------------------------------------------------------------------- 1 | webpack --optimize-minimize --output-file push-client-1.0.min.js 2 | webpack --output.filename push-client-1.0.js -------------------------------------------------------------------------------- /push-server/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | local.properties 3 | *.iml 4 | node_modules 5 | *.log 6 | dump.rdb 7 | log.txt 8 | pid 9 | pids 10 | pid_debug 11 | logs 12 | ip 13 | localstorage/ 14 | num_processes 15 | server_master_pid 16 | 17 | -------------------------------------------------------------------------------- /push-server/.npmignore: -------------------------------------------------------------------------------- 1 | log 2 | ip 3 | num_processes 4 | pid 5 | 2idc_socket.io-push.png 6 | server_master_pid -------------------------------------------------------------------------------- /push-server/2idc_socket.io-push-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuduo/socket.io-push/3771dc3e9a6c657da1293e5ed30a2d8d3df07de7/push-server/2idc_socket.io-push-new.png -------------------------------------------------------------------------------- /push-server/README.md: -------------------------------------------------------------------------------- 1 | push-server 5分钟搭建指南 2 | ======================= 3 | 一台普通配置的服务器, 如我们使用的16G内存 CPU Xeon E5-2620, 可以很轻松的支撑20万日活APP的推送任务. 4 | 5 | ## install 6 | 7 | * 如果使用苹果apns推送,由于node-http2兼容性问题 node版本必须是6.2.2 8 | 9 | https://nodejs.org/en/download/package-manager/ 10 | 11 | #### 安装/启动redis 12 | ``` 13 | redis-server & 14 | mongod & 15 | ``` 16 | 17 | #### 安装/更新socket.io-push 18 | ``` 19 | sudo npm install -g socket.io-push@version 20 | ##npm安装运行, 也可以直接clone代码, 在push-server目录运行node . 21 | ``` 22 | 23 | #### 新建工作目录, 用于存储日志, 配置文件 24 | ``` 25 | mkdir push-server 26 | cd push-server 27 | generate-push-server-config 28 | ``` 29 | 30 | # run 31 | ``` 32 | push-server -f 33 | -f foreground启动,不指定会后台启动 34 | ``` 35 | 36 | #### 配置 37 | 38 | * [config-proxy.js](config-proxy.js), 存在则启动proxy进程. 39 | 40 | * [config-api.js](config-api.js), 存在则启动api进程. 41 | 42 | * [config-apn-proxy.js](config-apn-proxy.js) apn代理进程配置, 存在则启动apn代理. 只有在 国内api->香港apn-proxy->苹果 这种部署模式下需要, 否则api进程即可直接调用苹果 43 | 44 | * [config-admin.js](config-admin.js) 管理后台配置,存在则启动admin进程. 45 | 46 | * [config-log.js](config-log.js) 日志配置 47 | 48 | 49 | # 默认地址 50 | 51 | * 管理后台 [https://localhost:12001](https://localhost:12001) 52 | 53 | * 客户端SDK连接HTTP地址: [http://localhost:10001](http://localhost:10001) 54 | 55 | * 客户端SDK连接HTTPS地址: [https://localhost:10443](https://localhost:10443) 56 | 57 | * API HTTP地址: [http://localhost:11001](http://localhost:11001/) 58 | 59 | * API HTTPS地址: [https://localhost:11443](https://localhost:11443)  支持http2 spdy 60 | 61 | 62 | # Q&A 63 | 64 | 1. 如何多机部署? 65 | 66 | proxy,api,apn-proxy均可以独立部署。只需保证配置中连接同一个mongodb,同一组redis。建议在调用机器上直接部署api进程。proxy前端机器独立部署。 67 | 68 | proxy,api,apn-proxy启动多少个进程,由各自config中instance控制,一般api只需要一个即可,proxy cpu数量/2。 69 | 70 | 2. 多个proxy前端情况下,如何负载均衡? 71 | 72 | 依靠dns解析到所有前端ip,当然也可以使用lvs。Nginx是不行的,由于长连接是一对一,nginx最多能把机器出口ip占完6w多个连接,远远不能满足需要 73 | 74 | 3. 如何指定只发安卓或者ios? 75 | 76 | notification接口里只传ios或者android,即可指定单一平台发送 77 | -------------------------------------------------------------------------------- /push-server/bin/betaPublish.sh: -------------------------------------------------------------------------------- 1 | npm publish ./ --tag=beta && cnpm sync socket.io-push -------------------------------------------------------------------------------- /push-server/bin/generate-push-server-config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require(__dirname + '/../lib/util/generateConfig.js'); -------------------------------------------------------------------------------- /push-server/bin/node-push-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require(__dirname + '/../index.js'); -------------------------------------------------------------------------------- /push-server/bin/publish.sh: -------------------------------------------------------------------------------- 1 | npm publish ./ && cnpm sync socket.io-push -------------------------------------------------------------------------------- /push-server/bin/push-server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -f "pid" ] 4 | then 5 | kill `cat pid` 6 | echo "killing `cat pid`" 7 | while [[ $? == 0 ]] # Repeat until the process has terminated. 8 | do 9 | sleep 0.1 # Wait a bit before testing. 10 | ps -p `cat pid` >/dev/null # Check if the process has terminated. 11 | done 12 | rm -rf pid 13 | fi 14 | 15 | if [ ! -f ip ]; then 16 | echo "ip file not found!" 17 | ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p' | head -1 > ip 18 | fi 19 | 20 | # A POSIX variable 21 | OPTIND=1 # Reset in case getopts has been used previously in the shell. 22 | FOREGROUND=0 23 | NODE='' 24 | while getopts "f?n:" opt; do 25 | case "$opt" in 26 | f) FOREGROUND=1 27 | ;; 28 | n) NODE=$OPTARG 29 | ;; 30 | esac 31 | done 32 | 33 | BASEDIR=$(dirname $0) 34 | node_script="$NODE $BASEDIR/node-push-server" 35 | 36 | if [ $FOREGROUND = 1 ]; 37 | then 38 | echo "starting push-server in foreground" 39 | $node_script 40 | else 41 | echo "starting push-server in background" 42 | mkdir -p log 43 | nohup $node_script >> log/console.log 2>&1 & 44 | echo $! > pid 45 | fi -------------------------------------------------------------------------------- /push-server/bin/push-server-kill: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f "pid" ] 4 | then 5 | kill `cat pid` 6 | echo "killing `cat pid`" 7 | while [[ $? == 0 ]] # Repeat until the process has terminated. 8 | do 9 | sleep 0.1 # Wait a bit before testing. 10 | ps -p `cat pid` >/dev/null # Check if the process has terminated. 11 | done 12 | rm -rf pid 13 | fi 14 | -------------------------------------------------------------------------------- /push-server/cert/com.xuduo.pushtest.p8: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgeJIAAuZ9eVmBbtJa500kPknsbTrssagbRjsG9uPlVcagCgYIKoZIzj0DAQehRANCAASbFlZoIbooBqBp7SCOl0yMq+rhLFLXmWAWnShAmI874bVYFpHDXbKUfNI1eDS5vBnztPlIoJ1IfYyEfLNpMTUO 3 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /push-server/cert/com.xuduopushtest.p8: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgyYdyYW/SVg3DU5P4Dy2P4OYetxsXT0hI2EQGlbFuji6gCgYIKoZIzj0DAQehRANCAARf3uejwCsijNjqcRTPLAcDoB3LucaKHNpYUmSClnNVCzeLGbEjCaVJC47beh+pNQkhHv8zBPIyQ/xJt0uH7pR5 3 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /push-server/cert/entrust_2048_ca.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u 3 | ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp 4 | bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV 5 | BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx 6 | NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 7 | d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl 8 | MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u 9 | ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 10 | MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL 11 | Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr 12 | hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW 13 | nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi 14 | VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E 15 | BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ 16 | KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy 17 | T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf 18 | zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT 19 | J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e 20 | nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= 21 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /push-server/cert/https/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDUDCCAjgCCQCVvntR3X3OfTANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJD 3 | TjELMAkGA1UECAwCU1oxCzAJBgNVBAcMAlNaMRIwEAYDVQQKDAlwdXNoLXRlc3Qx 4 | EjAQBgNVBAsMCXB1c2gtdGVzdDEYMBYGA1UEAwwPKi5wdXNoLXRlc3QuY29tMB4X 5 | DTE2MTExMDA2MjIxMloXDTI2MTEwODA2MjIxMlowazELMAkGA1UEBhMCQ04xCzAJ 6 | BgNVBAgMAlNaMQswCQYDVQQHDAJTWjESMBAGA1UECgwJcHVzaC10ZXN0MRIwEAYD 7 | VQQLDAlwdXNoLXRlc3QxGjAYBgNVBAMMEXd3dy5wdXNoLXRlc3QuY29tMIIBIjAN 8 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3bK+1bcvU9bD1nwXKsIULksA2i9u 9 | ezxnqhXkqT4MwtZ7Tjjw+i5nGsMfZc7CEPlKPDgoeWUqvL4xh1/WSUPXMbCuM837 10 | JVx9rKK+fasp0CcFL+Bk67PVvQKdQgGD68UOAX7ZLqW7iP3ceR7rRk5B2cYbzmzT 11 | Xr32p9/vYVRfGnekGhpG4vnYEwzhQiQkzYIXdEWpHC7gaU8NUeBddQGYKGTF909W 12 | LtTnB1HTOzWsN6e9KwzqDG677yBsEt9tTLSBOPD6X/MUDPaAoV4jEbO+6wC9Kqh3 13 | jk+rES6L3H5ljTLP/Oxa4sHR2puUkoCtbtxZ+fqPxOGFR9mRjtsMsYSKdQIDAQAB 14 | MA0GCSqGSIb3DQEBBQUAA4IBAQCM/mtthXvZLHF7cCRiXs1B12V1j/pfQT25ve9g 15 | lkcivfFq7VsscCmzc11ipcFo+3Hy7VHMtNV3YprH5QbikSegpvg0g5pHDKmLM7qy 16 | loMLBvoQJmUbiLbrxklyN1rzytTgMlwDv7VUC+VLjcXPvD2o8rSQ8DvVnUvyTRCF 17 | ppeNgoXrfR4GiPkRuI89lj+Z2XrYPU68RsmLCb59eVXlMpni2eVCJC7ArkhbyN0D 18 | CetNeKFVnHK/ZOkWrlBIBTH4Fh0QJ4PvsfQqV/QMxVM2EZKs3WYjVjF6oWTh3VOp 19 | 4kRAA8ICiGWm5PiHh1mIyNOmy5RVKhEPjwpYkLWp8RVxFLGz 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /push-server/cert/https/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA3bK+1bcvU9bD1nwXKsIULksA2i9uezxnqhXkqT4MwtZ7Tjjw 3 | +i5nGsMfZc7CEPlKPDgoeWUqvL4xh1/WSUPXMbCuM837JVx9rKK+fasp0CcFL+Bk 4 | 67PVvQKdQgGD68UOAX7ZLqW7iP3ceR7rRk5B2cYbzmzTXr32p9/vYVRfGnekGhpG 5 | 4vnYEwzhQiQkzYIXdEWpHC7gaU8NUeBddQGYKGTF909WLtTnB1HTOzWsN6e9Kwzq 6 | DG677yBsEt9tTLSBOPD6X/MUDPaAoV4jEbO+6wC9Kqh3jk+rES6L3H5ljTLP/Oxa 7 | 4sHR2puUkoCtbtxZ+fqPxOGFR9mRjtsMsYSKdQIDAQABAoIBAGgxhtEIrByaFiZq 8 | 4bR9zGBJbdgdbK5MoGfmrDYCbJrxlLgZuN/MCTcht1LwznKNxx7wQc10B5b3SG3Y 9 | U+zRDoVTBTxhtYREqOgHAMItPJSQkjaDHstMFMslPGV2MtsPHNsFE+uBhtcGgKUI 10 | LAk55GcGXbExolGntBIIBAhZT+0up5wIfWzmEzKkB4uei2bTz1Ydod8lx1sVR6gS 11 | cdAihxG6wnz6Ze1WnBeT/Bne858wRQSgagZhfaiCb4lcTRuRpGjag9jxwuj4UKsQ 12 | d11ZjUwAc9fol8p1ql8kZ4NjusQxUAzEQqZsQk/PpkA54Y1ZSUsvf6X5STpLLS3i 13 | 8hwDmoECgYEA84mX06Bf70Nc/ASQCHTsDOf8z+moXDvJkumfbuudacSq+7gmkPSk 14 | 3c0L6cxpY7js8mDkiLDCpYnnpuoktPF+JEVWJR/uVKZVKJEAId/JnMvEBl9o//fY 15 | D1eJFgtKGGLvOhQ8QSSy26Wimj08glX0y4KU1HNiYqanC41ReRxnqdUCgYEA6QsN 16 | Y26W4h1Y21PzwALrJhWW5qwIcc1rZAnNRWLddbDqUY8Mr9o+lfR7QdeyKiP8isSG 17 | /fjLmIK5/MXCI2qwtzWff4n1zhCBZd21Pv3tZxGma4GQRfOXxl9nHn/Im+y74g63 18 | 5smpdOOURJR7WYXgemgykWiC8LK7KMSa+OpmDiECgYBga3pj/BvfY8E9uiS4ZGpM 19 | ldCfFHhmw/kDpGFODhTNmmLDtF34iBN6CUONNjQHmmCdecUhZTI7TgUcLZmI1g/G 20 | FnmLyz+V3iDbJcVyo+JzpK0ghMfSh/LP6C1LP/7+9rSWPwq4E+qIUeS1wkbhp1x6 21 | 8mEwV9p9ClQam8HE/H0CXQKBgQCd6ullqbkA/JYL+jXAtbmXEyYwpf8K7wKIxcOd 22 | 80LSz9HY9rDIMYIbp5Imqf2HZN9rQYWgcIm2jU9vJKtLyK57WBD/IgO5YaHt33FE 23 | gFnxcBpv21pirJD60gdUuY69aoxewU6JS7pTBSyh1GRJ49ib2iP6OPISHWuKUsAZ 24 | UO1bQQKBgQCYJOrvq8tDNzEnsYjz0Lu6XPNo8H9Yl0LaMT7m4EMTUuzLEo89eEmw 25 | 92WgjL/XJNiZTFdrdWdvRF9+3+sWGL+8F+Wf+oG5HQN+oY24vXhod+HpYt3HHEj7 26 | GCVpX3HXzzraF0aZxenONOZDusyLAX3w5ajIU4D6Su/zvN+0ndnoJw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /push-server/config-admin.js: -------------------------------------------------------------------------------- 1 | //后台页面配置, 只启动一个进程 2 | var config = {}; 3 | 4 | config.https_port = 12001; //admin port 5 | 6 | config.api_url = "http://localhost:11001";// api url 7 | 8 | config.https_key = process.cwd() + "/cert/https/key.pem"; 9 | config.https_cert = process.cwd() + "/cert/https/cert.pem"; 10 | 11 | module.exports = config; -------------------------------------------------------------------------------- /push-server/config-api.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | config.http_port = 11001; //api端口, 可选. 不配置,不提供api接口 4 | config.https_port = 11443; 5 | config.host = 'localhost'; //不填或留空表示listen所有interface 6 | 7 | config.prefix = 'test'; // 数据库表名/redis pub/sub prefix,用于多个系统公用redis和mongo的情况 8 | 9 | config.instances = 3; 10 | 11 | config.pushAllInterval = 1 * 1000; // 各个全网推送渠道调用间隔 防止蜂拥 12 | 13 | config.https_key = process.cwd() + '/cert/https/key.pem'; 14 | config.https_cert = process.cwd() + '/cert/https/cert.pem'; 15 | 16 | config.statsCommitThreshold = 5000; //ms,统计缓存commit间隔, 生产环境建议10秒以上 17 | 18 | config.topicThreshold = {}; 19 | 20 | 21 | //google play 推送 可选 22 | config.fcm = { 23 | serviceAccount: { 24 | "type": "service_account", 25 | "project_id": "socketiodemo", 26 | "private_key_id": "3d896d67b4323d4195d5432f3834e7d7b184cc40", 27 | "private_key": `-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAD7iZRgxQLNv3\nZcQvgHxwsPvwmexVrTzXlbMdR6El+77vVwHp+N5lkDCQeL6OrX2xZE9ez6fGbRlP\nRJOEjCdsWz1kAZOR8ZAOHPEmyp0Tnw6PfNCMbYKwoHxaHZFqXKKapYS8jaZr26nF\nY41sInoALIBdI4izkUlhTw5PKODJ5rjfB2fZhx6hxmyixMa+zGPi9J/58fUys6LX\nEOzXmiQCQtjGrGk/voxvydN9MHFm0X0OpkaQI0bCsKsVMFsM/rKAhWZ8grabYX1E\nm0+pR4JUcIsgpTlgzaqPUEDkxG+W8S+zPII9M8RDyGcmbWoQuOTkNNRrttpQBkHr\nFsoGTcoDAgMBAAECggEAUp7QsfrctCbAD3qTPT4ACjhQgQ2uCaNG/5Sx4yAbtivI\nVMxwkdaR0U4IXjXa/6SpZAS7UhVxXp4zG5LsBMKH+Qh87cbx1P/+ENwpbx8NGFI9\noMM4MZiwdkvrgpaipgcome8nHTewRkjODRBI16IzKlz6cVamaVzQHNC13p6+qItc\n9h+yXQvHnmcXOkb+akAk1TF4bLD1zrH4odNRr1sqnslgZ0aJwK++yOEt+DGXPP13\n9BIuXe0YOHIqtANlh0VYBzQoWP9RKaWet9hvvWnGJ9oC7CHAZdaNtNAIOkQQ/gJ+\nS73l924sVlzmkF0OanXulZrZ8vjXZxix2J4Wmp3XKQKBgQD08kC6hDtH6sLWbP3R\nquLKzgFZzavtLpzEvNGxP5BRi84VXYIDIc6IgJFYTxmKhmSPUM4sSwAdKuJYrXjt\nonJ8cgoiIT2jq3cSNj3Nm+EEqlhw+6Vf+zAym5zkvbdGM9OjLiHGMFFLbpHwa9iP\nzkD8ykv4vwpotXRyLS1Ai73GiQKBgQDIuoO0WaPScghaURbPEu+RTlFUAD+Gc1FI\nly9g1e+7+I5VA5xSWxuerpdLvGblgmL+xYU7C37BY+Fqq8OsB14s2mYmZz/8y3yr\nNtXYezI8puae0T/AoRZ7qHHEYlNmpvJE0G8UrnFAD8sAikSXuP8hyfGBuIjYVx+x\nl4xMEzWpKwKBgQDc184kXDRWkwM38OynrTrtPu9Y2Ga6YdxWRSeKd5TW8QXNnZEq\n4cAkskZZKHgOvTzNOj2pEbX4lkGdUkpFdsFiEi+wteetOVsRwHXYe0JVwoAa3cgs\n0XyTJFpAogwr725RIbaxyb6CFB7gdVu7zGorgPkePKBV58QlbTXvjA5+gQKBgFFs\nFvCZS/KZfvnj2rS0oaj3c9X3I82OCXLAoN9O6Kf+8v1ZMZfWjSWY/JYkHjkK4s0l\noh2JVCluMonqkry9YF4hWT5Ks5H/mNp6q9PcZUxlBzd0+b9RmKUgdsWKfPouzidL\nxUNGX3n07guSCrDgwd0a5XQRPrFC5gBL0QUq5aFxAoGBALV5n/hpkMR5WP9Q0JCg\nuhsIUmIdj0deA6i2f94AznhLS8CYxpqA59T/Vof3IUldB4BB7NKae4KwlBzvZDQ0\n5E1A9JuBPsg9T2S1nTkKDKRkMGUY9/x6beoRpB0TE8v002Csl+oID64GTChVRH8A\ntDf0xs7Zg+PgllqObnY/yz7o\n-----END PRIVATE KEY-----\n`, 28 | "client_email": "firebase-adminsdk-85bnp@socketiodemo.iam.gserviceaccount.com", 29 | "client_id": "111316098587525102725", 30 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 31 | "token_uri": "https://accounts.google.com/o/oauth2/token", 32 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 33 | "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-85bnp%40socketiodemo.iam.gserviceaccount.com" 34 | }, 35 | databaseURL: "https://socketiodemo.firebaseio.com" 36 | } 37 | 38 | //apns推送配置,可选 39 | config.apns = [{ 40 | production: false, 41 | bundleId: 'com.xuduo.pushtest', 42 | token: { 43 | key: process.cwd() + '/cert/com.xuduo.pushtest.p8', 44 | keyId: 'E75AZZM4Z8', 45 | teamId: 'PVE2WH4PE2' 46 | } 47 | }, 48 | { 49 | production: false, 50 | bundleId: 'com.xuduo.pushtest2', 51 | token: { 52 | key: process.cwd() + '/cert/com.xuduo.pushtest.p8', 53 | keyId: 'E75AZZM4Z8', 54 | teamId: 'PVE2WH4PE2' 55 | } 56 | }, 57 | { 58 | production: false, 59 | bundleId: 'com.xuduopushtest', 60 | token: { 61 | key: process.cwd() + '/cert/com.xuduopushtest.p8', 62 | keyId: "TA7E8SBDZA", 63 | teamId: "RA5DTC26D2" 64 | } 65 | } 66 | ]; 67 | 68 | //华为推送配置,可选, 由于华为不支持多包名,需要像apn一样配置一个数组 69 | config.huawei = [{ 70 | package_name: 'com.yy.misaka.demo', 71 | client_id: 10513719, 72 | client_secret: '9l7fwfxt0m37qt61a1rh3w0lg9hjza1l' 73 | }, { 74 | package_name: 'com.yy.misaka.demo2', 75 | client_id: 10578747, 76 | client_secret: '43b37a2893af873910eb38b3417d8855' 77 | }]; 78 | 79 | //小米推送配置,可选, 小米内建支持多包名, 一个配置就可以 80 | config.xiaomi = { 81 | app_secret: 'ynJJ6b+MkCLyw1cdrg/72w==', 82 | notify_foreground: 0 //前台不通知 83 | }; 84 | 85 | //友盟推送配置,可选, 友盟内建支持多包名, 一个配置就可以 86 | config.umeng = { 87 | appKey: '59229cabf29d982ebd000b4b', 88 | masterSecret: 'bjgv1ttgt2herpgqxrvmsupazzsumobq' 89 | }; 90 | 91 | //api调用鉴权,可选 92 | const ipList = ['127.0.0.1']; 93 | 94 | config.apiAuth = function(opts, callback) { 95 | var ip = opts.req.connection.remoteAddress; 96 | if (ip.length >= 15) ip = ip.slice(7); 97 | opts.logger.info('%s caller ip %s', opts.req.originalUrl, ip); 98 | if (opts.req.p.pushAll == 'true' || opts.req.p.tag) { 99 | console.log(' check auth ' + ipList.indexOf(ip) != -1); 100 | callback(ipList.indexOf(ip) != -1); 101 | } else { 102 | callback(true); 103 | } 104 | }; 105 | 106 | config.mongo_log = false; 107 | 108 | /** 109 | 存储设备信息,统计数据等使用 110 | */ 111 | config.mongo = { 112 | default: 'mongodb://localhost/socketiopush', 113 | arrival: 'mongodb://localhost/socketiopush_arrival' 114 | }; 115 | 116 | /** 117 | * 透传使用 118 | * 数组表示hash切片,可以配置多个redis实例,分担流量/cpu负载 119 | * pubs 广播pub redis,二维数组 第一级表示redis分组 第二季表示hash切片 120 | * sub 订阅接收 redis 121 | * event 客户端断线,连接事件pub的redis.功能可能以后会改,不推荐使用 122 | */ 123 | config.redis = { 124 | pubs: [ 125 | [{ 126 | host: '127.0.0.1', 127 | port: 6379 128 | }] 129 | ] 130 | }; 131 | 132 | config.notificationBatchSize = 1000; //如果单次调用notification超过1000个uid, 将会分成多个批次任务 133 | 134 | config.notificationBufferSize = 10000; // buffer里最多有多少个分批次任务,超过会清空buffer 默认0, 无限制 135 | 136 | config.apnApiUrls = [ 137 | 'http://localhost:13001', 138 | 'https://localhost:13443' 139 | ]; // 香港/国外代理,用于apn推送 140 | 141 | module.exports = config; -------------------------------------------------------------------------------- /push-server/config-apn-proxy.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | config.http_port = 13001; //api端口, 可选. 不配置,不提供api接口 4 | config.https_port = 13443; 5 | 6 | config.instances = 3; 7 | 8 | config.socketTimeout = 60 * 1000; 9 | 10 | config.https_key = process.cwd() + '/cert/https/key.pem'; 11 | config.https_cert = process.cwd() + '/cert/https/cert.pem'; 12 | 13 | //apns推送配置,可选 14 | config.apns = [{ 15 | production: false, 16 | bundleId: 'com.xuduo.pushtest', 17 | token: { 18 | key: process.cwd() + '/cert/com.xuduo.pushtest.p8', 19 | keyId: 'E75AZZM4Z8', 20 | teamId: 'PVE2WH4PE2' 21 | } 22 | }, 23 | { 24 | production: false, 25 | bundleId: 'com.xuduo.pushtest2', 26 | token: { 27 | key: process.cwd() + '/cert/com.xuduo.pushtest.p8', 28 | keyId: 'E75AZZM4Z8', 29 | teamId: 'PVE2WH4PE2' 30 | } 31 | }, 32 | { 33 | production: false, 34 | bundleId: 'com.xuduopushtest', 35 | token: { 36 | key: process.cwd() + '/cert/com.xuduopushtest.p8', 37 | keyId: "TA7E8SBDZA", 38 | teamId: "RA5DTC26D2" 39 | } 40 | } 41 | ]; 42 | 43 | module.exports = config; 44 | -------------------------------------------------------------------------------- /push-server/config-log.js: -------------------------------------------------------------------------------- 1 | let config = {}; 2 | //输出的最低日志级别 debug < info < warn < error, 默认info 3 | config.level = "debug"; 4 | 5 | //可选 简单日志文件配置,配合formatter和timestamp可以接入ELK 6 | //config.filename = 'log/history.log'; 7 | 8 | //可选 轮转日志文件配置 9 | config.rotate_dir = 'log'; 10 | 11 | 12 | //是否输出到console, 默认不输出 13 | config.console = true; 14 | 15 | module.exports = config; 16 | -------------------------------------------------------------------------------- /push-server/config-proxy.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | //socket.io 长连接端口 4 | // http 端口,不配置不启用http 5 | config.http_port = 10001; 6 | // https端口,不配置不启用https, 证书&密钥位置: cert/https/*.pem ; 7 | config.https_port = 10443; 8 | config.https_key = process.cwd() + '/cert/https/key.pem'; 9 | config.https_cert = process.cwd() + '/cert/https/cert.pem'; 10 | //config.host = 'localhost'; //无此选项, 表示listen所有interface 11 | 12 | config.prefix = 'test'; // 数据库表名/redis pub/sub prefix,用于多个系统公用redis和mongo的情况 13 | 14 | config.instances = 3; 15 | 16 | config.pingTimeout = 25000; // 心跳timeout 17 | config.pingInterval = 90000; // 心跳间隔 18 | config.disconnect_delay = 10000; //disconnect事件延迟处理 19 | 20 | config.statsCommitThreshold = 50000; //ms,统计缓存commit间隔, 生产环境建议10秒以上 21 | 22 | config.topicOnlineFilter = { 23 | chatRoom: 'devices', 24 | drawTopic: 'count' 25 | }; 26 | //在线统计功能, 以chatRoom开头的topic会进行统计在线, 并提供查询接口 27 | // devices -- 统计设备列表 count -- 只统计总数 28 | 29 | config.packetDropThreshold = 0; 30 | 31 | config.mongo_log = false; 32 | 33 | config.mongo = { 34 | default: 'mongodb://localhost/socketiopush', 35 | arrival: 'mongodb://localhost/socketiopush_arrival' 36 | }; 37 | 38 | /** 39 | * 数组表示hash切片,可以配置多个redis实例,分担流量/cpu负载 40 | * pubs 广播pub redis,二维数组 第一级表示redis分组 第二季表示hash切片 41 | * sub 订阅接收 redis 42 | * write 数据存储主库 43 | * read 数据读从库 44 | * event 客户端断线,连接事件pub的redis.功能可能以后会改,不推荐使用 45 | */ 46 | config.redis = { 47 | sub: [{ 48 | host: '127.0.0.1', 49 | port: 6379 50 | }], 51 | event: [{ 52 | host: '127.0.0.1', 53 | port: 6379 54 | }] 55 | }; 56 | 57 | config.bindUid = (data, callback, logger) => { 58 | logger.info("bind uid log from config ", data); 59 | callback(data.uid); // 不验证直接绑定 60 | }; 61 | 62 | module.exports = config; -------------------------------------------------------------------------------- /push-server/index.js: -------------------------------------------------------------------------------- 1 | let logger = require('winston-proxy')('Index'); 2 | let cluster = require('cluster'); 3 | let net = require('net'); 4 | let fs = require('fs'); 5 | let proxy = {}; 6 | try { 7 | proxy = require(process.cwd() + "/config-proxy"); 8 | } catch (ex) { 9 | logger.warn('config-proxy exception: ' + ex); 10 | } 11 | proxy.instances = proxy.instances || 0; 12 | 13 | 14 | let api = {}; 15 | try { 16 | api = require(process.cwd() + "/config-api"); 17 | } catch (ex) { 18 | logger.warn('config-api exception: ' + ex); 19 | } 20 | api.instances = api.instances || 0; 21 | 22 | let apnProxy = {}; 23 | try { 24 | apnProxy = require(process.cwd() + "/config-apn-proxy"); 25 | } catch (ex) { 26 | logger.warn('config-apn-proxy exception: ' + ex); 27 | } 28 | apnProxy.instances = apnProxy.instances || 0; 29 | 30 | let admin = {}; 31 | try { 32 | admin = require(process.cwd() + "/config-admin"); 33 | if (admin.https_port && admin.https_cert && admin.https_key) { 34 | admin.instances = 1; 35 | } 36 | } catch (ex) { 37 | logger.warn('config-admin exception: ' + ex); 38 | } 39 | admin.instances = admin.instances || 0; 40 | 41 | if (cluster.isMaster) { 42 | let totalWorker = proxy.instances + api.instances + admin.instances + apnProxy.instances; 43 | require('fs').writeFile(process.cwd() + '/num_processes', totalWorker, (err) => { 44 | if (err) { 45 | logger.error("fail to write num of processes"); 46 | } 47 | }); 48 | logger.info('total worker: ' + totalWorker); 49 | 50 | let ip; 51 | let spawn = (processType, count) => { 52 | if (count > 0) { 53 | const env = { 54 | processType, 55 | ip 56 | }; 57 | for (let i = 0; i < count; i++) { 58 | const worker = cluster.fork(env); 59 | worker.on('exit', (code, signal) => { 60 | logger.error('worker(%s) exit, code:%s, signal:%s', worker.id, code, signal); 61 | setTimeout(() => { 62 | logger.info('respawn worker'); 63 | spawn(processType, 1); 64 | }, 5000); 65 | }); 66 | } 67 | } 68 | } 69 | 70 | ip = require('./lib/util/ip')(); 71 | logger.info('master get ip', ip); 72 | spawn('proxy', proxy.instances); 73 | spawn('api', api.instances); 74 | spawn('admin', admin.instances); 75 | spawn('apnProxy', apnProxy.instances); 76 | 77 | } else { 78 | let createServers = (config, httpsType) => { 79 | let httpServer; 80 | if (config.http_port) { 81 | httpServer = require('http').createServer(); 82 | if (config.host) { 83 | httpServer.listen(config.http_port, config.host); 84 | } else { 85 | httpServer.listen(config.http_port); 86 | } 87 | } 88 | let httpsServer; 89 | if (config.https_port && config.https_key && config.https_cert) { 90 | try { 91 | let https_key = fs.readFileSync(config.https_key); 92 | let https_cert = fs.readFileSync(config.https_cert); 93 | httpsServer = require(httpsType).createServer({ 94 | key: https_key, 95 | cert: https_cert 96 | }); 97 | if (config.host) { 98 | httpsServer.listen(config.https_port, config.host); 99 | } else { 100 | httpsServer.listen(config.https_port); 101 | } 102 | } catch (e) { 103 | logger.error('error happened when start https ', config, e); 104 | process.exit(-1); 105 | } 106 | } 107 | return { 108 | httpServer, 109 | httpsServer 110 | }; 111 | }; 112 | if (process.env.processType) { 113 | let socketTimeout = 0; 114 | if (process.env.processType == 'proxy') { 115 | let IoServer = require('socket.io'); 116 | socketTimeout = proxy.pingTimeout + proxy.pingInterval + 10 * 1000; 117 | let io = new IoServer(); 118 | const opts = { 119 | pingTimeout: proxy.pingTimeout, 120 | pingInterval: proxy.pingInterval, 121 | transports: ['websocket', 'polling'] 122 | } 123 | const servers = createServers(proxy, 'https'); 124 | if (servers.httpServer) { 125 | io.attach(servers.httpServer, opts); 126 | } 127 | if (servers.httpsServer) { 128 | io.attach(servers.httpsServer, opts); 129 | } 130 | require('./lib/proxy')(io, proxy); 131 | } else if (process.env.processType == 'api') { 132 | const servers = createServers(api, 'spdy'); 133 | require('./lib/api')(servers.httpServer, servers.httpsServer, api); 134 | } else if (process.env.processType == 'apnProxy') { 135 | const servers = createServers(apnProxy, 'spdy'); 136 | require('./lib/apnProxy')(servers.httpServer, servers.httpsServer, apnProxy); 137 | } else if (process.env.processType == 'admin') { 138 | require('./lib/admin')(admin); 139 | } 140 | } 141 | } 142 | 143 | process.on('uncaughtException', function (err) { 144 | logger.error('uncaughtException:', err); 145 | process.exit(1) 146 | }) 147 | -------------------------------------------------------------------------------- /push-server/lib/admin.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | return new Admin(config); 3 | } 4 | 5 | class Admin { 6 | 7 | constructor(config) { 8 | console.log(`start admin on port ${config.https_port} #${process.pid}`); 9 | this.admin = require('./admin/adminServer')(config); 10 | } 11 | 12 | close() { 13 | this.admin.close(); 14 | } 15 | } -------------------------------------------------------------------------------- /push-server/lib/admin/adminServer.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | return new AdminServer(config); 3 | }; 4 | const logger = require('winston-proxy')('AdminServer'); 5 | const express = require("express"); 6 | const request = require('request'); 7 | const fs = require('fs'); 8 | 9 | class AdminServer { 10 | 11 | constructor(config) { 12 | this.config = config; 13 | this.interval = 10 * 60 * 1000; 14 | setInterval(()=> { 15 | this.onlineStatsJob(); 16 | }, this.interval); 17 | const app = express(); 18 | let options = { 19 | key: fs.readFileSync(config.https_key), 20 | cert: fs.readFileSync(config.https_cert) 21 | }; 22 | require('spdy').createServer(options, app).listen(config.https_port, (error) => { 23 | if (error) { 24 | console.error(error) 25 | return process.exit(1) 26 | } else { 27 | console.log('Listening on port: ' + config.https_port + '.') 28 | } 29 | }); 30 | 31 | console.log("serving static ", __dirname); 32 | let proxy = {}; 33 | try { 34 | proxy = require(process.cwd() + "/config-proxy"); 35 | } catch (ex) { 36 | logger.warn('config-proxy exception: ' + ex); 37 | } 38 | let api = {}; 39 | try { 40 | api = require(process.cwd() + "/config-api"); 41 | } catch (ex) { 42 | logger.warn('config-proxy exception: ' + ex); 43 | } 44 | app.use(express.static(__dirname + '/../../static', { 45 | setHeaders: (res) => { 46 | res.set('Set-Cookie', [`api_url = ${config.api_url}`, `proxy_port = ${proxy.https_port}`, `api_port = ${api.https_port}`]); 47 | } 48 | })); 49 | } 50 | 51 | onlineStatsJob() { 52 | request({ 53 | method: "post", 54 | url: `${this.config.api_url}/api/stats/onlineJob`, 55 | form: {interval: this.interval} 56 | }, (error, response, body) => { 57 | if (error) { 58 | logger.error("onlineStatsJob ", error, body); 59 | } 60 | }); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /push-server/lib/api.js: -------------------------------------------------------------------------------- 1 | module.exports = function(httpServer, spdyServer, config) { 2 | return new Api(httpServer, spdyServer, config); 3 | }; 4 | 5 | class Api { 6 | 7 | constructor(httpServer, spdyServer, config) { 8 | 9 | console.log(`start api on port http:${config.http_port} https:${config.https_port} #${process.pid}`); 10 | const cluster = require('socket.io-push-redis/cluster')(config.redis); 11 | this.mongo = require('./mongo/mongo')(config.prefix, config.mongo, config.mongo_log); 12 | this.io = require('socket.io-push-redis/emitter')(cluster, { 13 | key: config.prefix 14 | }); 15 | 16 | const redisIncrBuffer = require('./stats/redisIncrBuffer')(this.mongo, config.statsCommitThreshold); 17 | this.stats = require('./stats/stats')(this.mongo, 0, redisIncrBuffer); 18 | const topicOnline = require('./stats/topicOnline')(this.mongo); 19 | this.arrivalStats = require('./stats/arrivalStats')(this.mongo, redisIncrBuffer, topicOnline); 20 | this.uidStore = require('./redis/uidStore')(config.prefix, cluster, this.mongo); 21 | this.deviceService = require('./service/deviceService')(this.mongo, this.uidStore); 22 | this.ttlService = require('./service/ttlService')(this.io, this.mongo, this.stats, this.arrivalStats); 23 | 24 | this.notificationService = require('./service/notificationService')(config.apns, this.mongo, this.ttlService); 25 | 26 | const providerFactory = require('./service/notificationProviderFactory')(config.pushAllInterval); 27 | this.notificationService.providerFactory = providerFactory; 28 | if (config.apns != undefined) { 29 | this.apnService = require('./service/apnProvider')(config.apns, config.apnApiUrls, this.mongo, this.arrivalStats, this.deviceService, this.stats); 30 | providerFactory.addProvider(this.apnService); 31 | } 32 | providerFactory.addProvider(this.ttlService); 33 | if (config.huawei) { 34 | this.huaweiProvider = require('./service/huaweiProvider')(config.huawei, this.stats, this.mongo); 35 | providerFactory.addProvider(this.huaweiProvider); 36 | } 37 | if (config.xiaomi) { 38 | this.xiaomiProvider = require('./service/xiaomiProvider')(config.xiaomi, this.arrivalStats, this.stats); 39 | providerFactory.addProvider(this.xiaomiProvider); 40 | this.arrivalStats.xiaomiProvider = this.xiaomiProvider; 41 | } 42 | if (config.umeng) { 43 | this.umengProvider = require('./service/umengProvider')(config.umeng, this.arrivalStats, this.stats); 44 | providerFactory.addProvider(this.umengProvider); 45 | this.arrivalStats.umengProvider = this.umengProvider; 46 | } 47 | if (config.fcm) { 48 | this.fcmProvider = require('./service/fcmProvider')(config.fcm, this.arrivalStats, this.stats); 49 | providerFactory.addProvider(this.fcmProvider); 50 | } 51 | this.apiRouter = require('./service/apiRouter')(this.deviceService, this.notificationService, this.ttlService, config.notificationBatchSize, config.routerApiUrls, this.stats, this.arrivalStats); 52 | this.restApi = require('./api/restApi')(httpServer, spdyServer, this.apiRouter, topicOnline, this.stats, config, this.apnService, config.apiAuth, this.deviceService, this.arrivalStats); 53 | } 54 | 55 | close() { 56 | this.restApi.close(); 57 | this.mongo.close(); 58 | } 59 | } -------------------------------------------------------------------------------- /push-server/lib/apnProxy.js: -------------------------------------------------------------------------------- 1 | module.exports = function(httpServer, spdyServer, config) { 2 | return new ApnProxy(httpServer, spdyServer, config); 3 | }; 4 | const express = require('express'); 5 | const bodyParser = require('body-parser'); 6 | const paramParser = require('./util/paramParser'); 7 | 8 | class ApnProxy { 9 | 10 | constructor(httpServer, spdyServer, config) { 11 | 12 | console.log(`start apnProxy on port http:${config.http_port} https:${config.https_port} #${process.pid}`); 13 | 14 | const apnService = require('./service/apnProvider')(config.apns, config.apnApiUrls); 15 | 16 | const app = express(); 17 | app.disable('etag'); 18 | app.use("/api", bodyParser.urlencoded({ // to support URL-encoded bodies 19 | extended: true 20 | })); 21 | app.use("/api", bodyParser.json()); 22 | app.use("/api", (req, res, next) => { 23 | res.set("Access-Control-Allow-Origin", "*"); 24 | req.p = {}; 25 | for (const param in req.body) { 26 | req.p[param] = req.body[param]; 27 | } 28 | for (const param in req.query) { 29 | req.p[param] = req.query[param]; 30 | } 31 | next(); 32 | }); 33 | 34 | if (httpServer) { 35 | this.httpServer = httpServer; 36 | httpServer.on('request', app); 37 | } 38 | if (spdyServer) { 39 | this.spdyServer = spdyServer; 40 | spdyServer.on('request', app); 41 | } 42 | const router = express.Router(); 43 | app.use("/api", router); 44 | 45 | router.all('/apn', (req, res, next) => { 46 | apnService.callLocal(JSON.parse(req.p.notification), req.p.bundleId, paramParser.parseArrayParam(req.p.tokens), req.p.pattern, (result) => { 47 | result.code = "success"; 48 | res.json(result); 49 | return next(); 50 | }); 51 | }); 52 | } 53 | 54 | close() { 55 | console.log('closing apnProxy'); 56 | if (this.httpServer) { 57 | this.httpServer.close(); 58 | } 59 | if (this.spdyServer) { 60 | this.spdyServer.close(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /push-server/lib/proxy.js: -------------------------------------------------------------------------------- 1 | module.exports = function(ioServer, config) { 2 | return new Proxy(ioServer, config); 3 | }; 4 | 5 | class Proxy { 6 | 7 | constructor(ioServer, config) { 8 | this.httpServer = ioServer.hs; 9 | this.httpsServer = ioServer.hss; 10 | this.mongo = require('./mongo/mongo')(config.prefix, config.mongo, config.mongo_log); 11 | this.io = ioServer; 12 | console.log(`start proxy on port ${config.http_port} ${config.https_port} #${process.pid}`); 13 | if (this.io) { 14 | const cluster = require('socket.io-push-redis/cluster')(config.redis); 15 | const nodeCluster = require('cluster'); 16 | let id = 0; 17 | if (nodeCluster.worker) { 18 | id = nodeCluster.worker.id; 19 | } 20 | const redisIncrBuffer = require('./stats/redisIncrBuffer')(this.mongo, config.statsCommitThreshold); 21 | this.stats = require('./stats/stats')(this.mongo, id, redisIncrBuffer, config.packetDropThreshold); 22 | this.topicOnline = require('./stats/topicOnline')(this.mongo, this.io, this.stats.id, config.topicOnlineFilter); 23 | this.arrivalStats = require('./stats/arrivalStats')(this.mongo, redisIncrBuffer); 24 | const socketIoRedis = require('socket.io-push-redis/adapter')({ 25 | pubClient: cluster, 26 | subClient: cluster, 27 | key: config.prefix 28 | }, null, this.stats); 29 | 30 | this.io.adapter(socketIoRedis); 31 | 32 | let packetService; 33 | if (config.redis.event) { 34 | packetService = require('./service/packetService')(cluster); 35 | } 36 | this.uidStore = require('./redis/uidStore')(config.prefix, cluster, this.mongo, this.io); 37 | this.deviceService = require('./service/deviceService')(this.mongo, this.uidStore); 38 | this.ttlService = require('./service/ttlService')(this.io, this.mongo, this.stats, this.arrivalStats); 39 | 40 | this.proxyServer = require('./server/proxyServer')(this.io, this.stats, packetService, this.deviceService, 41 | this.ttlService, this.arrivalStats, config); 42 | } else { 43 | console.log('start proxy failed!'); 44 | } 45 | } 46 | 47 | close() { 48 | if (this.io) { 49 | this.io.close(); 50 | } 51 | if (this.httpServer) { 52 | this.httpServer.close(); 53 | } 54 | if (this.httpsServer) { 55 | this.httpsServer.close(); 56 | } 57 | this.mongo.close(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /push-server/lib/redis/uidStore.js: -------------------------------------------------------------------------------- 1 | module.exports = (prefix, redis, mongo, io) => { 2 | return new UidStore(prefix, redis, mongo, io); 3 | }; 4 | const logger = require('winston-proxy')('UidStore'); 5 | 6 | class UidStore { 7 | 8 | constructor(prefix, redis, mongo, io) { 9 | this.redis = redis; 10 | this.mongo = mongo; 11 | this.bindUidCmd = prefix + ':cmd:bindUid'; 12 | this.unbindUidCmd = prefix + ':cmd:unbindUid'; 13 | if (io) { 14 | this.redis.subscribe(this.bindUidCmd); 15 | this.redis.subscribe(this.unbindUidCmd); 16 | this.redis.on('message', (channel, message) => { 17 | if (channel == this.bindUidCmd) { 18 | let json = JSON.parse(message); 19 | io.nsps['/'].adapter.doSocketInRoom(io.nsps['/'], json.pushId, (socket) => { 20 | logger.debug('redis bindUid join ', json); 21 | socket.setUid(json.uid); 22 | }); 23 | } else if (channel == this.unbindUidCmd) { 24 | let json = JSON.parse(message); 25 | let room = json.pushId || 'uid:' + json.uid; 26 | 27 | io.nsps['/'].adapter.doSocketInRoom(io.nsps['/'], room, (socket) => { 28 | logger.debug('redis unbindUid leave ', message); 29 | socket.setUid(null); 30 | }); 31 | } 32 | }); 33 | } 34 | } 35 | 36 | publishBindUid(pushId, uid) { 37 | this.redis.publish(this.bindUidCmd, JSON.stringify({ 38 | pushId: pushId, 39 | uid: uid 40 | })); 41 | } 42 | 43 | publishUnbindUid(pushId, uid) { 44 | this.redis.publish(this.unbindUidCmd, JSON.stringify({ 45 | pushId: pushId, 46 | uid: uid 47 | })); 48 | } 49 | 50 | bindUid(pushId, uid, platform, platformLimit = 0) { 51 | logger.debug('bindUid pushId %s %s', uid, pushId, platformLimit); 52 | const device = { 53 | uid, 54 | updateTime: Date.now() 55 | }; 56 | if (platform) { 57 | device.platform = platform; 58 | } 59 | 60 | this.mongo.device.update({ 61 | _id: pushId 62 | }, device, { 63 | upsert: true 64 | }, (err) => { 65 | logger.debug('update uid success ', pushId, uid); 66 | if (!err) { 67 | if (platformLimit > 0) { 68 | let query = { 69 | uid 70 | }; 71 | if (platform != 'all') { 72 | query.platform = platform; 73 | } 74 | this.mongo.device.find(query).sort('-updateTime').exec((err, devices) => { 75 | if (!err && devices) { 76 | for (let i = 0; i < devices.length; i++) { 77 | if (i >= platformLimit) { 78 | logger.debug('remove other binded uid', uid, devices[i]._id); 79 | devices[i].uid = undefined; 80 | devices[i].updateTime = Date.now(); 81 | devices[i].save(); 82 | } 83 | } 84 | } 85 | }); 86 | } 87 | } 88 | }); 89 | } 90 | 91 | removePushId(pushId) { 92 | logger.debug('removePushId pushId %s', pushId); 93 | this.mongo.device.update({ 94 | _id: pushId 95 | }, { 96 | $unset: { 97 | uid: 1 98 | } 99 | }, (err) => { 100 | if (err) { 101 | logger.error('mongodb removePushId error', err); 102 | } 103 | }); 104 | } 105 | 106 | removeUid(uid) { 107 | this.mongo.device.update({ 108 | uid 109 | }, { 110 | $unset: { 111 | uid: 1 112 | } 113 | }, { 114 | multi: true 115 | }, (err) => { 116 | logger.debug('removeUid uid ', uid, err); 117 | if (err) { 118 | logger.error('mongodb removePushId error', err); 119 | } 120 | }); 121 | } 122 | 123 | getUidByPushId(pushId, callback) { 124 | this.mongo.device.findById(pushId, (err, device) => { 125 | logger.debug('getUidByPushId %s %s', pushId, device); 126 | if (!err && device) { 127 | callback(device.uid); 128 | } else { 129 | callback(); 130 | } 131 | }); 132 | } 133 | 134 | getPushIdByUid(uid, callback) { 135 | this.mongo.device.find({ 136 | uid: uid 137 | }, (err, devices) => { 138 | const pushIds = []; 139 | logger.debug('getPushIdByUid %s %s %s', uid, err, devices); 140 | if (!err && devices) { 141 | devices.forEach((device) => { 142 | pushIds.push(device.id); 143 | }); 144 | } 145 | callback(pushIds); 146 | }); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /push-server/lib/service/fcmProvider.js: -------------------------------------------------------------------------------- 1 | module.exports = (config, arrivalStats, stats) => { 2 | return new FcmProvider(config, arrivalStats, stats); 3 | }; 4 | 5 | const logger = require('winston-proxy')('FcmProvider'); 6 | 7 | const util = require('socket.io-push-redis/util'); 8 | const admin = require('firebase-admin'); 9 | 10 | class FcmProvider { 11 | 12 | constructor(config, arrivalStats, stats) { 13 | this.arrivalStats = arrivalStats; 14 | this.stats = stats; 15 | try { 16 | admin.initializeApp({ 17 | credential: admin.credential.cert(config.serviceAccount), 18 | databaseURL: config.databaseURL 19 | }); 20 | } catch (err) {} 21 | this.type = "fcm"; 22 | this.notify_foreground = (config.notify_foreground === 0) ? 0 : 1; 23 | } 24 | 25 | sendMany(notification, tokenDataList, timeToLive, callback) { 26 | if (notification.android.title) { 27 | 28 | var message = this.getMessage(notification, timeToLive); 29 | 30 | const tokens = tokenDataList.map((tokenData) => { 31 | message.token = tokenData.token; 32 | admin.messaging().send(message) 33 | .then((response) => { 34 | // Response is a message ID string. 35 | logger.debug('Successfully sent message:', response); 36 | }) 37 | .catch((error) => { 38 | logger.error('Error sending message:', error); 39 | }); 40 | }); 41 | 42 | this.stats.addTotal(this.type, tokens.count); 43 | 44 | logger.debug('admin send message', message); 45 | 46 | } 47 | } 48 | 49 | getMessage(notification, timeToLive) { 50 | logger.debug("getPostData notification ", notification, this.notify_foreground); 51 | var message = { 52 | android: { 53 | ttl: timeToLive || 0, // 1 hour in milliseconds 54 | priority: 'high', 55 | notification: { 56 | title: notification.android.title, 57 | body: notification.android.message 58 | } 59 | }, 60 | data: { 61 | payload: JSON.stringify({ 62 | title: notification.android.title, 63 | message: notification.android.message, 64 | payload: notification.android.payload, 65 | id: notification.id 66 | }) 67 | } 68 | }; 69 | return message; 70 | } 71 | 72 | sendAll(notification, timeToLive, callback) { 73 | if (notification.android.title) { 74 | this.stats.addTotal(this.type + "All"); 75 | var message = this.getMessage(notification, timeToLive); 76 | 77 | message.topic = "all"; 78 | admin.messaging().send(message) 79 | .then((response) => { 80 | // Response is a message ID string. 81 | logger.debug('Successfully sent message:', response); 82 | }) 83 | .catch((error) => { 84 | logger.error('Error sending message:', error); 85 | }); 86 | 87 | logger.debug('admin send message', message); 88 | } 89 | } 90 | 91 | 92 | trace(packetInfo, callback) { 93 | if (packetInfo.xiaomi_msg_id) { 94 | request.get({ 95 | url: traceUrl, 96 | qs: { 97 | msg_id: packetInfo.xiaomi_msg_id 98 | }, 99 | headers: this.headers, 100 | timeout: timeout, 101 | maxAttempts: 2, 102 | retryDelay: 5000, 103 | retryStrategy: request.RetryStrategies.NetworkError 104 | }, (error, response, body) => { 105 | logger.info("trace result", error, response && response.statusCode, body); 106 | try { 107 | const result = JSON.parse(body); 108 | if (result.data && result.data.data) { 109 | delete packetInfo.xiaomi_msg_id; 110 | if (result.data.data.resolved > 0) { 111 | packetInfo.xiaomi = result.data.data; 112 | } 113 | } 114 | } catch (e) {} 115 | callback(packetInfo); 116 | }); 117 | } else { 118 | callback(packetInfo); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /push-server/lib/service/notificationProviderFactory.js: -------------------------------------------------------------------------------- 1 | module.exports = (pushAllInterval) => { 2 | return new NotificationProviderFactory(pushAllInterval); 3 | }; 4 | 5 | const logger = require('winston-proxy')('NotificationProviderFactory'); 6 | 7 | class NotificationProviderFactory { 8 | 9 | constructor(pushAllInterval = 0) { 10 | this.providers = {}; 11 | this.keys = []; 12 | this.pushAllInterval = pushAllInterval; 13 | } 14 | 15 | addProvider(provider) { 16 | this.providers[provider.type] = provider; 17 | this.keys.push(provider.type); 18 | } 19 | 20 | sendMany(notification, mapTypeToTokenList, timeToLive) { 21 | for (const type in mapTypeToTokenList) { 22 | const provider = this.providers[type || "apn"]; 23 | logger.debug("sendMany %s", type); 24 | if (provider) { 25 | provider.sendMany(notification, mapTypeToTokenList[type], timeToLive); 26 | } 27 | } 28 | } 29 | 30 | sendAll(notification, timeToLive, callback) { 31 | logger.info('sendAll task interval', this.pushAllInterval, this.keys, notification); 32 | for (let i = 0; i < this.keys.length; i++) { 33 | const key = this.keys[i]; 34 | const provider = this.providers[key]; 35 | setTimeout(() => { 36 | logger.info('sendAll task ', key, notification); 37 | provider.sendAll(notification, timeToLive); 38 | }, this.pushAllInterval * i); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /push-server/lib/service/notificationService.js: -------------------------------------------------------------------------------- 1 | module.exports = (providerFactory, mongo, ttlService) => { 2 | return new NotificationService(providerFactory, mongo, ttlService); 3 | }; 4 | 5 | const logger = require('winston-proxy')('NotificationService'); 6 | const async = require('async'); 7 | 8 | class NotificationService { 9 | 10 | constructor(providerFactory, mongo, ttlService) { 11 | this.mongo = mongo; 12 | this.ttlService = ttlService; 13 | this.providerFactory = providerFactory; 14 | } 15 | 16 | sendByDevices(devices, timeToLive, notification, type) { 17 | const mapTypeToToken = {}; 18 | let sendViaTtlService = 0; 19 | for (const device of devices) { 20 | if (!device.token || device.type == 'umeng') { 21 | logger.debug("send notification in socket.io, connection", device); 22 | sendViaTtlService++; 23 | this.ttlService.emitPacket(device._id, 'noti', notification); 24 | if (!device.socketId) { 25 | this.ttlService.addTTL(device._id, 'noti', timeToLive, notification, true); 26 | } 27 | } 28 | if (device.type && device.token && device.package_name) { 29 | const tokenList = mapTypeToToken[device.type] || []; 30 | mapTypeToToken[device.type] = tokenList; 31 | tokenList.push(device); 32 | } 33 | } 34 | this.providerFactory.sendMany(notification, mapTypeToToken, timeToLive); 35 | return sendViaTtlService; 36 | } 37 | 38 | sendAll(notification, timeToLive) { 39 | this.providerFactory.sendAll(notification, timeToLive); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /push-server/lib/service/packetService.js: -------------------------------------------------------------------------------- 1 | module.exports = (redis) => { 2 | return new PacketService(redis); 3 | }; 4 | const logger = require('winston-proxy')('PacketService'); 5 | const randomstring = require("randomstring"); 6 | 7 | class PacketService { 8 | 9 | constructor(redis) { 10 | this.redis = redis; 11 | } 12 | 13 | publishPacket(data) { 14 | const path = data.path; 15 | const pushId = data.pushId; 16 | if (path && pushId) { 17 | if (!data.sequenceId) { 18 | data.sequenceId = randomstring.generate(16); 19 | } 20 | data.timestamp = Date.now(); 21 | const strData = JSON.stringify(data); 22 | this.redis.publish("event#client", strData); 23 | } 24 | } 25 | 26 | publishDisconnect(socket) { 27 | const data = {pushId: socket.pushId, path: "/socketDisconnect"}; 28 | if (socket.uid) { 29 | data.uid = socket.uid; 30 | } 31 | this.publishPacket(data); 32 | } 33 | 34 | publishConnect(socket) { 35 | const data = {pushId: socket.pushId, path: "/socketConnect"}; 36 | if (socket.uid) { 37 | data.uid = socket.uid; 38 | } 39 | this.publishPacket(data); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /push-server/lib/service/ttlService.js: -------------------------------------------------------------------------------- 1 | module.exports = (io, mongo, stats, arrivalStats) => { 2 | return new TTLService(io, mongo, stats, arrivalStats); 3 | }; 4 | 5 | const logger = require('winston-proxy')('TTLService'); 6 | const randomstring = require("randomstring"); 7 | const maxTllPacketPerTopic = -10; 8 | 9 | class TTLService { 10 | 11 | constructor(io, mongo, stats, arrivalStats) { 12 | this.mongo = mongo; 13 | this.io = io; 14 | this.stats = stats; 15 | this.arrivalStats = arrivalStats; 16 | this.type = 'TTLService'; 17 | } 18 | 19 | addTTL(topic, event, timeToLive = 0, packet, unicast) { 20 | if (timeToLive > 0) { 21 | if (!packet.id) { 22 | packet.id = randomstring.generate(12); 23 | } 24 | logger.debug("addPacket %s %s %s", topic, event, timeToLive); 25 | packet.ttl = 1; 26 | packet.topic = topic; 27 | if (unicast) { 28 | packet.unicast = 1; 29 | } 30 | packet.timestampValid = Date.now() + timeToLive; 31 | packet.event = event; 32 | this.mongo.ttl.findByIdAndUpdate(topic, { 33 | $setOnInsert: { 34 | expireAt: packet.timestampValid 35 | }, 36 | $push: { 37 | packetsMixed: { 38 | $each: [packet], 39 | $slice: maxTllPacketPerTopic 40 | } 41 | }, 42 | }, { 43 | upsert: true 44 | }, (err, doc) => { 45 | if (!err && doc) { 46 | if (!doc.expireAt || doc.expireAt < packet.timestampValid) { 47 | doc.expireAt = packet.timestampValid; 48 | doc.save(); 49 | } 50 | } 51 | if (err) { 52 | logger.error("update ttl error", err); 53 | } 54 | }); 55 | 56 | } 57 | } 58 | 59 | /* 60 | ttlTopics = { 61 | noti: {lastPacketId:'qweljkasd',unicast: false}, 62 | qweLJKoiu1U: {lastPacketId:'doamc',unicast: true} 63 | } 64 | */ 65 | getPackets(socket, ttlTopics) { 66 | const topics = Object.keys(ttlTopics); 67 | if (topics.length) { 68 | this.mongo.ttl.find({ 69 | _id: { 70 | $in: topics 71 | } 72 | }, (err, ttls) => { 73 | if (!err && ttls && ttls.length) { 74 | for (const ttl of ttls) { 75 | const topic = ttl._id; 76 | const list = ttl.packetsMixed; 77 | const lastId = ttlTopics[topic].lastPacketId; 78 | const unicast = ttlTopics[topic].unicast; 79 | if (list && list.length > 0) { 80 | var lastFound = false; 81 | var now = Date.now(); 82 | 83 | list.forEach((packet) => { 84 | if (packet.id == lastId) { 85 | lastFound = true; 86 | logger.debug("lastFound %s %s", topic, lastId); 87 | } else if (lastFound == true && packet.timestampValid > now) { 88 | logger.debug("call emitPacket %s %s", packet.id, lastId); 89 | this.emitToSocket(socket, packet.event, packet); 90 | this.arrivalStats.addArrivalInfo(packet.id, { 91 | target_android: 1 92 | }); 93 | } 94 | }); 95 | 96 | if (unicast) { 97 | this.mongo.ttl.remove({ 98 | _id: topic 99 | }); 100 | } 101 | 102 | if (!lastFound) { 103 | logger.debug('topic %s lastId %s not found send all packets', topic, lastId); 104 | list.forEach((packet) => { 105 | if (packet.timestampValid > now) { 106 | this.emitToSocket(socket, packet.event, packet); 107 | this.arrivalStats.addArrivalInfo(packet.id, { 108 | target_android: 1 109 | }); 110 | } 111 | }); 112 | } 113 | } 114 | } 115 | } 116 | }); 117 | } 118 | } 119 | 120 | emitPacket(topic, event, packet) { 121 | let target = this.io; 122 | if (topic.constructor === Array) { 123 | for (const t of topic) { 124 | target = target.to(t); 125 | } 126 | } else { 127 | target = target.to(topic); 128 | } 129 | this.emitToSocket(target, event, packet); 130 | } 131 | 132 | emitToSocket(socket, event, packet) { 133 | delete packet.event; 134 | delete packet.timestampValid; 135 | if (packet.timestamp) { 136 | packet.timestamp = Date.now(); 137 | } 138 | if (event == "push") { 139 | if (packet.ttl) { 140 | socket.emit("p", packet.j, [packet.topic, packet.id, packet.unicast || 0]); 141 | } else { 142 | socket.emit("p", packet.j); 143 | } 144 | } else { 145 | socket.emit(event, packet); 146 | } 147 | } 148 | 149 | sendAll(notification, timeToLive) { 150 | if (notification.android.title) { 151 | this.addTTL("noti", 'noti', timeToLive, notification, false); 152 | // 小米,华为,苹果不订阅 "noti" 153 | this.emitPacket("noti", 'noti', notification); 154 | } 155 | } 156 | 157 | sendMany() { 158 | 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /push-server/lib/service/xiaomiProvider.js: -------------------------------------------------------------------------------- 1 | module.exports = (config, arrivalStats, stats) => { 2 | return new XiaomiProvider(config, arrivalStats, stats); 3 | }; 4 | 5 | const logger = require('winston-proxy')('XiaomiProvider'); 6 | 7 | const util = require('socket.io-push-redis/util'); 8 | const request = require('requestretry'); 9 | const sendOneUrl = "https://api.xmpush.xiaomi.com/v3/message/regid"; 10 | const sendAllUrl = "https://api.xmpush.xiaomi.com/v3/message/all"; 11 | const traceUrl = "https://api.xmpush.xiaomi.com/v1/trace/message/status"; 12 | const timeout = 5000; 13 | 14 | class XiaomiProvider { 15 | 16 | constructor(config, arrivalStats, stats) { 17 | this.arrivalStats = arrivalStats; 18 | this.stats = stats; 19 | this.headers = { 20 | 'Authorization': 'key=' + config.app_secret 21 | }; 22 | this.type = "xiaomi"; 23 | this.notify_foreground = (config.notify_foreground === 0) ? 0 : 1; 24 | } 25 | 26 | sendMany(notification, tokenDataList, timeToLive, callback) { 27 | if (notification.android.title) { 28 | this.stats.addTotal(this.type); 29 | request.post({ 30 | url: sendOneUrl, 31 | form: this.getPostData(notification, tokenDataList, timeToLive), 32 | headers: this.headers, 33 | timeout: timeout, 34 | maxAttempts: 2, 35 | retryDelay: 5000, 36 | retryStrategy: request.RetryStrategies.NetworkError, 37 | time: true 38 | }, (error, response, body) => { 39 | logger.debug("sendOne result", error, response && response.statusCode, body); 40 | if (this.success(error, response, body, callback, notification.id)) { 41 | this.stats.addSuccess(this.type, 1, response.elapsedTime); 42 | return; 43 | } 44 | logger.error("sendOne error", error, response && response.statusCode, body); 45 | }) 46 | } 47 | } 48 | 49 | getPostData(notification, tokenDataList, timeToLive) { 50 | logger.debug("getPostData notification ", notification, this.notify_foreground); 51 | const postData = { 52 | title: notification.android.title, 53 | description: notification.android.message, 54 | notify_id: util.hash(notification.id), 55 | "extra.notify_foreground": this.notify_foreground, 56 | payload: JSON.stringify({ 57 | android: notification.android, 58 | id: notification.id 59 | }) 60 | }; 61 | if (tokenDataList) { 62 | postData.registration_id = tokenDataList.map((tokenData) => { 63 | return tokenData.token; 64 | }).join(); 65 | } 66 | if (timeToLive > 0) { 67 | postData.time_to_live = timeToLive; 68 | } else { 69 | postData.time_to_live = 0; 70 | } 71 | return postData; 72 | } 73 | 74 | sendAll(notification, timeToLive, callback) { 75 | if (notification.android.title) { 76 | this.stats.addTotal(this.type + "All"); 77 | request.post({ 78 | url: sendAllUrl, 79 | form: this.getPostData(notification, 0, timeToLive), 80 | headers: this.headers, 81 | timeout: timeout, 82 | maxAttempts: 2, 83 | retryDelay: 5000, 84 | retryStrategy: request.RetryStrategies.NetworkError, 85 | time: true 86 | }, (error, response, body) => { 87 | logger.info("sendAll result", error, response && response.statusCode, body); 88 | if (this.success(error, response, body, callback, notification.id)) { 89 | this.stats.addSuccess(this.type + "All", 1, response.elapsedTime); 90 | return; 91 | } 92 | logger.error("sendAll error", error, response && response.statusCode, body); 93 | }); 94 | } 95 | } 96 | 97 | success(error, response, body, callback, notificationId) { 98 | if (callback) { 99 | callback(error); 100 | } 101 | if (!error && response && response.statusCode == 200) { 102 | const result = JSON.parse(body); 103 | logger.debug("response result ", result); 104 | if (result.data && result.data.id) { 105 | this.arrivalStats.addArrivalInfo(notificationId, {}, { 106 | xiaomi_msg_id: result.data.id 107 | }); 108 | } 109 | if (result.code == 0 || result.code == 20301) { 110 | return true; 111 | } 112 | } 113 | return false; 114 | } 115 | 116 | trace(packetInfo, callback) { 117 | if (packetInfo.xiaomi_msg_id) { 118 | request.get({ 119 | url: traceUrl, 120 | qs: { 121 | msg_id: packetInfo.xiaomi_msg_id 122 | }, 123 | headers: this.headers, 124 | timeout: timeout, 125 | maxAttempts: 2, 126 | retryDelay: 5000, 127 | retryStrategy: request.RetryStrategies.NetworkError 128 | }, (error, response, body) => { 129 | logger.info("trace result", error, response && response.statusCode, body); 130 | try { 131 | const result = JSON.parse(body); 132 | if (result.data && result.data.data) { 133 | delete packetInfo.xiaomi_msg_id; 134 | if (result.data.data.resolved > 0) { 135 | packetInfo.xiaomi = result.data.data; 136 | } 137 | } 138 | } catch (e) {} 139 | callback(packetInfo); 140 | }); 141 | } else { 142 | callback(packetInfo); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /push-server/lib/stats/moving-sum.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return new MovingSum(); 3 | }; 4 | class MovingSum { 5 | 6 | constructor() { 7 | this.stamps = []; 8 | } 9 | 10 | push(timestamp) { 11 | this.stamps.push(timestamp); 12 | } 13 | 14 | sum(timespans) { 15 | const starts = []; 16 | const current = Date.now(); 17 | timespans.forEach((span) => { 18 | starts.push(current - span); 19 | }); 20 | const sum_ret = []; 21 | let spliceIndex = 0; 22 | const totalLength = this.stamps.length; 23 | this.stamps.forEach((stamp, stampIndex) => { 24 | starts.forEach((start, sumIndex) => { 25 | if (stamp >= start && !sum_ret[sumIndex]) { 26 | sum_ret[sumIndex] = totalLength - stampIndex; 27 | if (spliceIndex == 0) { 28 | spliceIndex = stampIndex; 29 | } 30 | } 31 | }); 32 | }); 33 | if (spliceIndex > 0) { 34 | this.stamps = this.stamps.slice(spliceIndex, totalLength); 35 | } 36 | return sum_ret; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /push-server/lib/stats/redisIncrBuffer.js: -------------------------------------------------------------------------------- 1 | module.exports = (mongo, commitThreshHold) => { 2 | return new RedisIncrBuffer(mongo, commitThreshHold); 3 | }; 4 | 5 | const mSecPerHour = 60 * 60 * 1000; 6 | const expire = 30 * 24 * mSecPerHour; 7 | 8 | class RedisIncrBuffer { 9 | 10 | constructor(mongo, commitThreshHold) { 11 | this.mongo = mongo; 12 | this.collectionMap = {}; 13 | let commitThresHold = commitThreshHold || 20 * 1000; 14 | setInterval(() => { 15 | this.commit(); 16 | }, commitThresHold); 17 | } 18 | 19 | incr(key, by, collection = 'stat') { 20 | let map = this.collectionMap[collection]; 21 | if (!map) { 22 | map = {}; 23 | this.collectionMap[collection] = map; 24 | } 25 | const currentIncr = map[key] || 0; 26 | if (!map[key]) { 27 | map[key] = by; 28 | } else { 29 | for (const field in by) { 30 | if (!map[key][field]) { 31 | map[key][field] = 0; 32 | } 33 | map[key][field] += by[field]; 34 | } 35 | } 36 | } 37 | 38 | strip(timestamp, interval = mSecPerHour) { 39 | return Math.floor(timestamp / interval) * interval; 40 | } 41 | 42 | commit() { 43 | const timestamp = this.strip(Date.now()); 44 | const expireAt = timestamp + expire; 45 | for (const collection in this.collectionMap) { 46 | const map = this.collectionMap[collection]; 47 | for (const key in map) { 48 | let _id = key; 49 | if (collection == 'stat') { 50 | _id = { 51 | key, 52 | timestamp 53 | }; 54 | } 55 | this.mongo[collection].update({ 56 | _id 57 | }, { 58 | $inc: map[key], 59 | $setOnInsert: { 60 | expireAt 61 | } 62 | }, { 63 | upsert: true 64 | }, (err, doc) => { 65 | 66 | }); 67 | } 68 | } 69 | this.collectionMap = {}; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /push-server/lib/stats/topicOnline.js: -------------------------------------------------------------------------------- 1 | module.exports = (mongo, io, id, filterTopics) => { 2 | return new TopicOnline(mongo, io, id, filterTopics); 3 | }; 4 | const logger = require('winston-proxy')('TopicOnline'); 5 | 6 | class TopicOnline { 7 | 8 | constructor(mongo, io, id, filterTopics) { 9 | this.mongo = mongo; 10 | this.id = id; 11 | if (!filterTopics) { 12 | this.filters = {}; 13 | } else if (filterTopics.isArray) { 14 | this.filters = {}; 15 | for (const prefix of filterTopics) { 16 | this.filters[prefix] = "count"; 17 | } 18 | } else { 19 | this.filters = filterTopics; 20 | } 21 | this.filters['noti'] = "count"; 22 | this.interval = 10000; 23 | this.timeValidWithIn = this.interval * 2; 24 | this.expire = this.interval * 2; 25 | if (io) { 26 | this.io = io; 27 | setInterval(this.flush.bind(this), this.interval); 28 | } 29 | } 30 | 31 | flush() { 32 | if (this.io.nsps) { 33 | const result = this.io.nsps['/'].adapter.rooms; 34 | this.writeTopicOnline(result); 35 | } 36 | } 37 | 38 | filterTopic(topic) { 39 | if (!topic) { 40 | return false; 41 | } 42 | for (const prefix in this.filters) { 43 | if (topic.startsWith(prefix)) { 44 | return this.filters[prefix]; 45 | } 46 | } 47 | return false; 48 | } 49 | 50 | writeTopicOnline(data) { 51 | const expireAt = Date.now() + this.timeValidWithIn; 52 | for (const key in data) { 53 | if (data[key].length > 0) { 54 | const type = this.filterTopic(key); 55 | if (type) { 56 | const json = { 57 | count: data[key].length, 58 | expireAt 59 | }; 60 | if (type == 'devices') { 61 | json.devices = []; 62 | for (const socketId in data[key].sockets) { 63 | const socket = this.io.sockets.connected[socketId]; 64 | if (socket) { 65 | json.devices.push({ 66 | pushId: socket.pushId, 67 | uid: socket.uid, 68 | platform: socket.platform 69 | }); 70 | } 71 | } 72 | } 73 | this.mongo.topicOnline.update({ 74 | _id: { 75 | serverId: this.id, 76 | topic: key 77 | } 78 | }, 79 | json, { 80 | upsert: true 81 | }, (err, doc) => { 82 | if (err) { 83 | logger.error('topicOnline.update ', err); 84 | } 85 | }); 86 | } 87 | } 88 | } 89 | } 90 | 91 | getTopicOnline(topic, callback) { 92 | let count = 0; 93 | this.mongo.topicOnline.find({ 94 | '_id.topic': topic 95 | }).select('-devices').exec((err, docs) => { 96 | if (!err && docs) { 97 | for (const doc of docs) { 98 | if (doc.expireAt.getTime() > Date.now()) { 99 | count = count + doc.count; 100 | logger.debug('topicOnline.find ', doc, new Date(), count); 101 | } 102 | } 103 | } 104 | callback(count); 105 | }); 106 | } 107 | 108 | getTopicDevices(topic, callback) { 109 | const json = { 110 | topic: topic, 111 | devices: [], 112 | total: 0 113 | }; 114 | this.mongo.topicOnline.find({ 115 | '_id.topic': topic 116 | }, (err, docs) => { 117 | if (!err && docs) { 118 | for (const doc of docs) { 119 | if (doc.expireAt.getTime() > Date.now()) { 120 | if (doc.devices) { 121 | for (const device of doc.devices) { 122 | json.devices.push(device); 123 | } 124 | } 125 | } 126 | json.total = json.devices.length; 127 | } 128 | } 129 | callback(json); 130 | }); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /push-server/lib/util/generateConfig.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const ncp = require('ncp'); 3 | 4 | fs.createReadStream(__dirname + '/../../config-proxy.js').pipe(fs.createWriteStream('config-proxy.js')); 5 | fs.createReadStream(__dirname + '/../../config-api.js').pipe(fs.createWriteStream('config-api.js')); 6 | fs.createReadStream(__dirname + '/../../config-admin.js').pipe(fs.createWriteStream('config-admin.js')); 7 | fs.createReadStream(__dirname + '/../../config-log.js').pipe(fs.createWriteStream('config-log.js')); 8 | 9 | ncp(__dirname + '/../../cert', "./cert", function (err) { 10 | if (err) { 11 | return console.error(err); 12 | } 13 | console.log('done!'); 14 | }); -------------------------------------------------------------------------------- /push-server/lib/util/infiniteArray.js: -------------------------------------------------------------------------------- 1 | module.exports = (array) => { 2 | return new InfiniteArray(array); 3 | }; 4 | 5 | class InfiniteArray { 6 | 7 | constructor(array = []) { 8 | this.array = array; 9 | this.index = 0; 10 | } 11 | 12 | next() { 13 | if (!this.array || this.array.length == 0) { 14 | return; 15 | } 16 | const ret = this.array[this.index]; 17 | if (++this.index == this.array.length) { 18 | this.index = 0; 19 | } 20 | return ret; 21 | } 22 | 23 | hasNext() { 24 | return this.array && this.array.length > 0; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /push-server/lib/util/ip.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | var os = require('os'); 3 | var ifaces = os.networkInterfaces(); 4 | let ip = 'unknown'; 5 | let ipEth0; 6 | Object.keys(ifaces).forEach(function(ifname) { 7 | var alias = 0; 8 | ifaces[ifname].forEach(function(iface) { 9 | if ('IPv4' !== iface.family || iface.internal !== false) { 10 | // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses 11 | return; 12 | } 13 | 14 | if (alias >= 1) { 15 | // this single interface has multiple ipv4 addresses 16 | console.log(ifname + ':' + alias, iface.address); 17 | } else { 18 | // this interface has only one ipv4 adress 19 | console.log(ifname, iface.address); 20 | if (ifname == 'en0' || ifname == 'eth0') { 21 | ipEth0 = iface.address; 22 | } 23 | } 24 | ip = iface.address; 25 | }); 26 | }); 27 | return ipEth0 || ip; 28 | }; 29 | -------------------------------------------------------------------------------- /push-server/lib/util/paramParser.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | moreThanOneTrue: function() { 4 | let count = 0; 5 | for (const item of arguments) { 6 | if (item) { 7 | count++; 8 | } 9 | } 10 | return count > 1; 11 | }, 12 | 13 | parseArrayParam: function(param) { 14 | let arr; 15 | if (typeof param === 'string') { 16 | if (param.startsWith('[')) { 17 | arr = JSON.parse(param); 18 | } else if (param) { 19 | arr = [param]; 20 | } 21 | } else if (typeof param === 'number') { 22 | arr = [param]; 23 | } else { 24 | arr = param; 25 | } 26 | return arr; 27 | }, 28 | 29 | parseNumber: function(param) { 30 | return parseInt(param) || 0; 31 | } 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /push-server/lib/util/versionCompare.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compares two software version numbers (e.g. "1.7.1" or "1.2b"). 3 | * 4 | * This function was born in http://stackoverflow.com/a/6832721. 5 | * 6 | * @param {string} v1 The first version to be compared. 7 | * @param {string} v2 The second version to be compared. 8 | * @param {object} [options] Optional flags that affect comparison behavior: 9 | * 20 | * @returns {number|NaN} 21 | * 27 | * 28 | * @copyright by Jon Papaioannou (["john", "papaioannou"].join(".") + "@gmail.com") 29 | * @license This function is in the public domain. Do what you want with it, no strings attached. 30 | */ 31 | function versionCompare(v1, v2, options) { 32 | var lexicographical = options && options.lexicographical, 33 | zeroExtend = options && options.zeroExtend, 34 | v1parts = v1.split('.'), 35 | v2parts = v2.split('.'); 36 | 37 | function isValidPart(x) { 38 | return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); 39 | } 40 | 41 | if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { 42 | return NaN; 43 | } 44 | 45 | if (zeroExtend) { 46 | while (v1parts.length < v2parts.length) v1parts.push("0"); 47 | while (v2parts.length < v1parts.length) v2parts.push("0"); 48 | } 49 | 50 | if (!lexicographical) { 51 | v1parts = v1parts.map(Number); 52 | v2parts = v2parts.map(Number); 53 | } 54 | 55 | for (var i = 0; i < v1parts.length; ++i) { 56 | if (v2parts.length == i) { 57 | return 1; 58 | } 59 | 60 | if (v1parts[i] == v2parts[i]) { 61 | continue; 62 | } else if (v1parts[i] > v2parts[i]) { 63 | return 1; 64 | } else { 65 | return -1; 66 | } 67 | } 68 | 69 | if (v1parts.length != v2parts.length) { 70 | return -1; 71 | } 72 | 73 | return 0; 74 | } 75 | 76 | module.exports = versionCompare; 77 | -------------------------------------------------------------------------------- /push-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-push", 3 | "version": "0.9.15", 4 | "description": "socket.io-push server by xuduo", 5 | "main": "index.js", 6 | "bin": { 7 | "push-server": "./bin/push-server", 8 | "node-push-server": "./bin/node-push-server", 9 | "generate-push-server-config": "./bin/generate-push-server-config" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:xuduo/socket.io-push.git" 14 | }, 15 | "dependencies": { 16 | "apn": "2.1.5", 17 | "async": "0.9.0", 18 | "body-parser": "1.15.2", 19 | "dateformat": "^2.0.0", 20 | "express": "~4.14.0", 21 | "firebase-admin": "^5.10.0", 22 | "md5": "^2.2.1", 23 | "mongoose": "4.11.3", 24 | "ncp": "2.0.0", 25 | "randomstring": "1.1.3", 26 | "request": "2.67.0", 27 | "requestretry": "^1.12.0", 28 | "restify": "4.0.3", 29 | "socket.io": "1.7.2", 30 | "socket.io-push-redis": "^1.0.10", 31 | "spdy": "^3.4.4", 32 | "superagent": "^1.8.0", 33 | "winston-proxy": "1.0.6" 34 | }, 35 | "devDependencies": { 36 | "socket.io-client": "1.7.2", 37 | "socket.io-push-client": "0.8.2", 38 | "expect.js": "0.3.1", 39 | "chai-spies": "^0.7.1", 40 | "mocha": "*", 41 | "chai": "^3.5.0" 42 | }, 43 | "scripts": { 44 | "test": "mocha" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /push-server/static/arrivalRate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Packets arrival status 6 | 7 | 8 |
各包的到达率情况:
9 |
10 |
--
11 | 12 | 13 | 25 | 26 | -------------------------------------------------------------------------------- /push-server/static/client/httpProxy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Socket.IO Chat Example 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 | 16 | 37 | 38 | -------------------------------------------------------------------------------- /push-server/static/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Socket.IO Chat Example 6 | 7 | 8 | 9 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /push-server/static/client/style.css: -------------------------------------------------------------------------------- 1 | /* Fix user-agent */ 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-weight: 300; 9 | -webkit-font-smoothing: antialiased; 10 | } 11 | 12 | html, input { 13 | font-family: 14 | "HelveticaNeue-Light", 15 | "Helvetica Neue Light", 16 | "Helvetica Neue", 17 | Helvetica, 18 | Arial, 19 | "Lucida Grande", 20 | sans-serif; 21 | } 22 | 23 | html, body { 24 | height: 100%; 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | ul { 30 | list-style: none; 31 | word-wrap: break-word; 32 | } 33 | 34 | /* Pages */ 35 | 36 | .pages { 37 | height: 100%; 38 | margin: 0; 39 | padding: 0; 40 | width: 100%; 41 | } 42 | 43 | .page { 44 | height: 100%; 45 | position: absolute; 46 | width: 100%; 47 | } 48 | 49 | /* Login Page */ 50 | 51 | .login.page { 52 | background-color: #000; 53 | } 54 | 55 | .login.page .form { 56 | height: 100px; 57 | margin-top: 150px; 58 | 59 | text-align: center; 60 | top: 50%; 61 | width: 100%; 62 | } 63 | 64 | .login.page .form .usernameInput { 65 | background-color: transparent; 66 | border: none; 67 | border-bottom: 2px solid #fff; 68 | outline: none; 69 | padding-bottom: 15px; 70 | text-align: center; 71 | width: 600px; 72 | } 73 | 74 | .login.page .title { 75 | font-size: 200%; 76 | } 77 | 78 | .login.page .usernameInput { 79 | font-size: 200%; 80 | letter-spacing: 3px; 81 | } 82 | 83 | .login.page .title, .login.page .usernameInput { 84 | color: #fff; 85 | font-weight: 100; 86 | } 87 | 88 | /* Chat page */ 89 | 90 | .chat.page { 91 | display: none; 92 | } 93 | 94 | /* Font */ 95 | 96 | .messages { 97 | font-size: 150%; 98 | } 99 | 100 | .inputMessage { 101 | font-size: 100%; 102 | } 103 | 104 | .log { 105 | color: gray; 106 | font-size: 70%; 107 | margin: 5px; 108 | text-align: center; 109 | } 110 | 111 | /* Messages */ 112 | 113 | .chatArea { 114 | height: 100%; 115 | padding-bottom: 60px; 116 | } 117 | 118 | .messages { 119 | height: 100%; 120 | margin: 0; 121 | overflow-y: scroll; 122 | padding: 10px 20px 10px 20px; 123 | } 124 | 125 | .message.typing .messageBody { 126 | color: gray; 127 | } 128 | 129 | .username { 130 | font-weight: 700; 131 | overflow: hidden; 132 | padding-right: 15px; 133 | text-align: right; 134 | } 135 | 136 | /* Input */ 137 | 138 | .inputMessage { 139 | border: 10px solid #000; 140 | bottom: 0; 141 | height: 60px; 142 | left: 0; 143 | outline: none; 144 | padding-left: 10px; 145 | position: absolute; 146 | right: 0; 147 | width: 100%; 148 | } 149 | -------------------------------------------------------------------------------- /push-server/static/handleStatsBase/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 34 | 35 | 36 | 37 | 38 |

Server Status

39 | 40 |
41 | 42 | IP    : 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
IPTotalIOSAndroidpacketDroppacketDropThreshold
62 |
63 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /push-server/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Welcome to socket.io-push 6 | 7 | 8 |

GITHUB

9 |

js chat demo

10 |

js draw demo

11 |

PUSH API

12 |

NOTIFICATION API

13 |

UID API

14 |

STATISTICS

15 |

SERVER STATUS

16 |

PACKETS ARRIVAL STATUS

17 | 18 | 19 | -------------------------------------------------------------------------------- /push-server/static/js/Chart.Core.min.js.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuduo/socket.io-push/3771dc3e9a6c657da1293e5ed30a2d8d3df07de7/push-server/static/js/Chart.Core.min.js.gzip -------------------------------------------------------------------------------- /push-server/static/js/Chart.Scatter.min.js.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuduo/socket.io-push/3771dc3e9a6c657da1293e5ed30a2d8d3df07de7/push-server/static/js/Chart.Scatter.min.js.gzip -------------------------------------------------------------------------------- /push-server/static/js/Chart.Scatter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5EC72027-6CC3-4338-A076-15833808929C}" 5 | ProjectSection(SolutionItems) = preProject 6 | bower.json = bower.json 7 | Chart.Core.js = Chart.Core.js 8 | Chart.Core.min.js = Chart.Core.min.js 9 | Chart.Scatter.js = Chart.Scatter.js 10 | Chart.Scatter.min.js = Chart.Scatter.min.js 11 | index.html = index.html 12 | package.json = package.json 13 | README.md = README.md 14 | test.html = test.html 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionProperties) = preSolution 19 | HideSolutionNode = FALSE 20 | EndGlobalSection 21 | EndGlobal 22 | -------------------------------------------------------------------------------- /push-server/static/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.1 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2013 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD 11 | define(['jquery'], factory); 12 | } else if (typeof exports === 'object') { 13 | // CommonJS 14 | factory(require('jquery')); 15 | } else { 16 | // Browser globals 17 | factory(jQuery); 18 | } 19 | }(function ($) { 20 | 21 | var pluses = /\+/g; 22 | 23 | function encode(s) { 24 | return config.raw ? s : encodeURIComponent(s); 25 | } 26 | 27 | function decode(s) { 28 | return config.raw ? s : decodeURIComponent(s); 29 | } 30 | 31 | function stringifyCookieValue(value) { 32 | return encode(config.json ? JSON.stringify(value) : String(value)); 33 | } 34 | 35 | function parseCookieValue(s) { 36 | if (s.indexOf('"') === 0) { 37 | // This is a quoted cookie as according to RFC2068, unescape... 38 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); 39 | } 40 | 41 | try { 42 | // Replace server-side written pluses with spaces. 43 | // If we can't decode the cookie, ignore it, it's unusable. 44 | // If we can't parse the cookie, ignore it, it's unusable. 45 | s = decodeURIComponent(s.replace(pluses, ' ')); 46 | return config.json ? JSON.parse(s) : s; 47 | } catch(e) {} 48 | } 49 | 50 | function read(s, converter) { 51 | var value = config.raw ? s : parseCookieValue(s); 52 | return $.isFunction(converter) ? converter(value) : value; 53 | } 54 | 55 | var config = $.cookie = function (key, value, options) { 56 | 57 | // Write 58 | 59 | if (value !== undefined && !$.isFunction(value)) { 60 | options = $.extend({}, config.defaults, options); 61 | 62 | if (typeof options.expires === 'number') { 63 | var days = options.expires, t = options.expires = new Date(); 64 | t.setTime(+t + days * 864e+5); 65 | } 66 | 67 | return (document.cookie = [ 68 | encode(key), '=', stringifyCookieValue(value), 69 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 70 | options.path ? '; path=' + options.path : '', 71 | options.domain ? '; domain=' + options.domain : '', 72 | options.secure ? '; secure' : '' 73 | ].join('')); 74 | } 75 | 76 | // Read 77 | 78 | var result = key ? undefined : {}; 79 | 80 | // To prevent the for loop in the first place assign an empty array 81 | // in case there are no cookies at all. Also prevents odd result when 82 | // calling $.cookie(). 83 | var cookies = document.cookie ? document.cookie.split('; ') : []; 84 | 85 | for (var i = 0, l = cookies.length; i < l; i++) { 86 | var parts = cookies[i].split('='); 87 | var name = decode(parts.shift()); 88 | var cookie = parts.join('='); 89 | 90 | if (key && key === name) { 91 | // If second argument (value) is a function it's a converter... 92 | result = read(cookie, value); 93 | break; 94 | } 95 | 96 | // Prevent storing a cookie that we couldn't decode. 97 | if (!key && (cookie = read(cookie)) !== undefined) { 98 | result[name] = cookie; 99 | } 100 | } 101 | 102 | return result; 103 | }; 104 | 105 | config.defaults = {}; 106 | 107 | $.removeCookie = function (key, options) { 108 | if ($.cookie(key) === undefined) { 109 | return false; 110 | } 111 | 112 | // Must not alter options, thus extending a fresh object... 113 | $.cookie(key, '', $.extend({}, options, { expires: -1 })); 114 | return !$.cookie(key); 115 | }; 116 | 117 | })); 118 | -------------------------------------------------------------------------------- /push-server/static/uid/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 35 | 36 | 37 | 38 | 39 |

Add Uid

40 | 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
uid    :
pushId    :
62 |
63 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /push-server/test/apiAuth.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('apiAuth.js', () => { 7 | 8 | before(() => { 9 | global.apiServer = defSetting.getDefaultApiServer(); 10 | global.apiUrl = defSetting.getDefaultApiUrl(); 11 | }); 12 | 13 | after(() => { 14 | global.apiServer.close(); 15 | }); 16 | 17 | it('check should pass', done => { 18 | request({ 19 | url: apiUrl + '/api/push', 20 | method: "post", 21 | form: { 22 | pushId: '', 23 | pushAll: 'true', 24 | topic: 'message', 25 | data: 'test' 26 | } 27 | }, (error, response, body) => { 28 | expect(JSON.parse(body).code).to.be.equal("success"); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('check should not pass', (done) => { 34 | 35 | const apiCheckDenyAll = (opts, callback) => { 36 | callback(false); 37 | }; 38 | 39 | apiServer.restApi.apiAuth = apiCheckDenyAll; 40 | 41 | request({ 42 | url: apiUrl + '/api/push', 43 | method: "post", 44 | form: { 45 | pushId: '', 46 | pushAll: 'true', 47 | topic: 'message', 48 | data: 'test' 49 | } 50 | }, (error, response, body) => { 51 | expect(JSON.parse(body).code).to.be.equal("error"); 52 | request({ 53 | url: apiUrl + '/api/notification', 54 | method: "post", 55 | form: { 56 | pushId: '', 57 | pushAll: 'true', 58 | topic: 'message', 59 | data: 'test' 60 | } 61 | }, (error, response) => { 62 | expect(JSON.parse(response.body).code).to.be.equal("error"); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | 68 | 69 | it('check ip', (done) => { 70 | 71 | var ipList = ['127.0.0.1', '127.0.0.2']; 72 | var apiCheckIp = (opts, callback) => { 73 | var ip = opts.req.headers['x-real-ip'] || opts.req.connection.remoteAddress; 74 | opts.logger.debug("caller ip %s", ip); 75 | if (opts.req.p.pushAll == 'true') { 76 | callback(ipList.indexOf(ip) != -1); 77 | } else { 78 | callback(true); 79 | } 80 | }; 81 | 82 | apiServer.restApi.apiAuth = apiCheckIp; 83 | 84 | request({ 85 | url: apiUrl + '/api/push', 86 | method: "post", 87 | form: { 88 | pushId: '', 89 | pushAll: 'true', 90 | data: 'test' 91 | } 92 | }, (error, response, body) => { 93 | expect(JSON.parse(body).code).to.be.equal("error"); 94 | }); 95 | 96 | request({ 97 | url: apiUrl + '/api/push', 98 | method: "post", 99 | form: { 100 | pushId: 'test', 101 | data: 'test' 102 | } 103 | }, (error, response, body) => { 104 | expect(JSON.parse(body).code).to.be.equal("success"); 105 | }); 106 | 107 | 108 | request({ 109 | url: apiUrl + '/api/push', 110 | method: "post", 111 | headers: { 112 | 'X-Real-IP': '127.0.0.2' 113 | }, 114 | form: { 115 | pushId: '', 116 | topic: 'message', 117 | data: 'test' 118 | } 119 | }, (error, response, body) => { 120 | expect(JSON.parse(body).code).to.be.equal("success"); 121 | done(); 122 | }); 123 | }); 124 | 125 | }); 126 | -------------------------------------------------------------------------------- /push-server/test/apiRouterTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | var title = 'hello', 7 | message = 'hello world'; 8 | var data = { 9 | "android": { 10 | "title": title, 11 | "message": message 12 | }, 13 | "payload": { 14 | "wwww": "qqqq" 15 | } 16 | }; 17 | var str = JSON.stringify(data); 18 | 19 | describe('apiRouterTest', function() { 20 | 21 | before(function() { 22 | global.proxyServer = defSetting.getDefaultProxyServer(); 23 | global.apiServer = defSetting.getDefaultApiServer(); 24 | global.apnProxy = defSetting.getDefaultApnProxyServer(); 25 | global.apiServer.apiRouter.batchSize = 3; 26 | global.apiUrl = defSetting.getDefaultApiUrl(); 27 | global.pushClients = []; 28 | for (let i = 0; i < 7; i++) { 29 | global.pushClients.push(defSetting.getDefaultPushClient("abcdefghijsdjfk" + i)); 30 | } 31 | }); 32 | 33 | after(function() { 34 | global.proxyServer.close(); 35 | global.apiServer.close(); 36 | global.apnProxy.close(); 37 | global.pushClients.forEach((client) => { 38 | client.disconnect(); 39 | }); 40 | }); 41 | 42 | it('send notification', (done) => { 43 | global.apiServer.apiRouter.bufferSize = 2; 44 | let pushIds = []; 45 | pushClients.forEach((client) => { 46 | client.on('connect', () => { 47 | var notificationCallback = function(data) { 48 | expect(data.title).to.be.equal(title); 49 | expect(data.message).to.be.equal(message); 50 | expect(data.payload.wwww).to.be.equal("qqqq"); 51 | let index = pushIds.indexOf(client.pushId); 52 | expect(index).to.not.be.equal(-1); 53 | pushIds.splice(index, 1); 54 | if (pushIds.length == 0) { 55 | done(); 56 | } 57 | }; 58 | client.on('notification', notificationCallback); 59 | pushIds.push(client.pushId); 60 | if (pushIds.length == pushClients.length) { 61 | request({ 62 | url: apiUrl + '/api/notification', 63 | method: "post", 64 | headers: { 65 | 'Accept': 'application/json' 66 | }, 67 | form: { 68 | pushId: JSON.stringify(pushIds), 69 | notification: str 70 | } 71 | }, (error, response, body) => { 72 | expect(JSON.parse(body).code).to.be.equal("success"); 73 | }); 74 | } 75 | }); 76 | }); 77 | }); 78 | 79 | 80 | });; 81 | -------------------------------------------------------------------------------- /push-server/test/apnProviderSendAllTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('apn test', function() { 7 | 8 | before(function() { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apnProxy = defSetting.getDefaultApnProxyServer(); 12 | global.apiUrl = defSetting.getDefaultApiUrl(); 13 | global.pushClient = defSetting.getDefaultPushClient(); 14 | }); 15 | 16 | after(function() { 17 | global.proxyServer.close(); 18 | global.apiServer.close(); 19 | global.apnProxy.close(); 20 | global.pushClient.disconnect(); 21 | }); 22 | 23 | 24 | it('test send all', function(done) { 25 | pushClient.on('connect', function() { 26 | pushClient.socket.emit("token", { 27 | token: "ffffff", 28 | bundleId: "com.xuduo.pushtest", 29 | type: "apn" 30 | }); 31 | var data = { 32 | "apn": { 33 | alert: "wwwwAll" 34 | } 35 | } 36 | var str = JSON.stringify(data); 37 | 38 | pushClient.on('notification', function() { 39 | // expect("do not receive").to.be.false 40 | }); 41 | 42 | setTimeout(() => { 43 | request({ 44 | url: apiUrl + '/api/notification', 45 | method: "post", 46 | headers: { 47 | 'Accept': 'application/json' 48 | }, 49 | form: { 50 | pushAll: 'true', 51 | notification: str 52 | } 53 | }, (error, response, body) => { 54 | expect(JSON.parse(body).code).to.be.equal("success"); 55 | setTimeout(() => { 56 | apiServer.deviceService.getDeviceByPushId(pushClient.pushId, (device) => { 57 | expect(device.type).to.equal("apnNoToken"); 58 | done(); 59 | }); 60 | }, 5000); 61 | }); 62 | }); 63 | }, 100); 64 | }); 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /push-server/test/apnProviderSendOneTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('apn send one', function() { 7 | 8 | before(function() { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apnProxy = defSetting.getDefaultApnProxyServer(); 12 | global.apiUrl = defSetting.getDefaultApiUrl(); 13 | global.pushClient = defSetting.getDefaultPushClient(); 14 | global.pushClient2 = defSetting.getDefaultPushClient(); 15 | }); 16 | 17 | after(function() { 18 | global.proxyServer.close(); 19 | global.apiServer.close(); 20 | global.apnProxy.close(); 21 | global.pushClient.disconnect(); 22 | global.pushClient2.disconnect(); 23 | }); 24 | 25 | it('test send one', function(done) { 26 | pushClient.on('connect', function() { 27 | pushClient.socket.emit("token", { 28 | apnToken: "eeee", 29 | bundleId: "com.xuduo.pushtest", 30 | type: "apn" 31 | }); 32 | pushClient2.socket.emit("token", { 33 | apnToken: "", 34 | bundleId: "com.xuduo.pushtest2", 35 | type: "apn" 36 | }); 37 | 38 | var data = { 39 | "apn": { 40 | alert: "qqqqOne" 41 | } 42 | }; 43 | var str = JSON.stringify(data); 44 | 45 | pushClient.on('noti', function() { 46 | expect("do not receive").to.be.false 47 | }); 48 | request({ 49 | url: apiUrl + '/api/notification', 50 | method: "post", 51 | headers: { 52 | 'Accept': 'application/json' 53 | }, 54 | form: { 55 | pushId: [pushClient.pushId], 56 | notification: str 57 | } 58 | }, (error, response, body) => { 59 | const result = JSON.parse(body); 60 | expect(result.code).to.be.equal("success"); 61 | global.apiServer.deviceService.getDeviceByPushId(pushClient2.pushId, (device) => { 62 | console.log('======pushId=====' + pushClient2.pushId + ' data: ' + device); 63 | expect(device.token).to.be.equal("eexxee"); 64 | done(); 65 | }); 66 | setTimeout(() => { 67 | global.apiServer.deviceService.getDeviceByPushId(pushClient.pushId, (device) => { 68 | expect(device.token).to.be.undefined; 69 | }); 70 | }, 4000); 71 | 72 | }); 73 | }); 74 | 75 | }); 76 | 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /push-server/test/arrivalStatsTest.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var defSetting = require('./defaultSetting'); 4 | var randomstring = require("randomstring"); 5 | 6 | describe('arrivalStatsTest', () => { 7 | 8 | before(() => { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | global.arrivalStats = apiServer.arrivalStats; 13 | global.topicOnline = arrivalStats.topicOnline; 14 | }); 15 | 16 | after(() => { 17 | global.proxyServer.close(); 18 | global.apiServer.close(); 19 | }); 20 | 21 | it('arrivalRate test', (done) => { 22 | const packetId = randomstring.generate(12); 23 | let packet = { 24 | id: packetId, 25 | android: { 26 | title: 'test msg', 27 | message: 'content of test msg' 28 | } 29 | }; 30 | let topicOnlineData = { 31 | 'noti': { 32 | length: 99 33 | } 34 | }; 35 | topicOnline.writeTopicOnline(topicOnlineData); 36 | 37 | setTimeout(() => { 38 | arrivalStats.addPushAll(packet, 1000); 39 | arrivalStats.addArrivalInfo(packetId, { 40 | arrive_android: 98 41 | }); 42 | arrivalStats.addArrivalInfo(packetId, { 43 | target_android: 1 44 | }); 45 | arrivalStats.addArrivalInfo(packetId, { 46 | arrive_android: 1 47 | }); 48 | arrivalStats.incrBuffer.commit(); 49 | setTimeout(() => { 50 | arrivalStats.getRateStatusByType('pushAll', (stats) => { 51 | let item; 52 | for (const stat of stats) { 53 | if (stat.id == packetId) { 54 | item = stat; 55 | } 56 | } 57 | console.log(item); 58 | expect(item.id).to.be.equal(packetId); 59 | expect(item.android.arrive).to.be.equal(99); 60 | expect(item.android.target).to.be.equal(100); 61 | done(); 62 | }) 63 | }, 1000); 64 | }, 500); 65 | 66 | }) 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /push-server/test/bindUidTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('push test', () => { 7 | 8 | before(() => { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.pushClient = defSetting.getDefaultPushClient(); 11 | global.apiServer = defSetting.getDefaultApiServer(); 12 | global.apiUrl = defSetting.getDefaultApiUrl(); 13 | }); 14 | 15 | after(() => { 16 | global.proxyServer.close(); 17 | global.apiServer.close(); 18 | global.pushClient.disconnect(); 19 | }); 20 | 21 | it('connect', (done) => { 22 | pushClient.on('connect', (data) => { 23 | expect(data.uid).to.be.undefined; 24 | done(); 25 | }); 26 | }); 27 | 28 | it('bind uid', (done) => { 29 | pushClient.bindUid({ 30 | uid: "1234" 31 | }); 32 | setTimeout(() => { 33 | pushClient.on('connect', (data) => { 34 | expect(data.uid).to.be.equal("1234"); 35 | done(); 36 | }); 37 | pushClient.disconnect(); 38 | pushClient.connect(); 39 | }, 200); 40 | }); 41 | 42 | it('bind other uid', (done) => { 43 | pushClient.bindUid({ 44 | uid: "4321" 45 | }); 46 | setTimeout(() => { 47 | pushClient.on('connect', (data) => { 48 | expect(data.uid).to.be.equal("4321"); 49 | done(); 50 | }); 51 | pushClient.disconnect(); 52 | pushClient.connect(); 53 | }, 200); 54 | }); 55 | 56 | it('expect receive push once', (done) => { 57 | 58 | pushClient.bindUid({ 59 | uid: "123456" 60 | }); 61 | pushClient.bindUid({ 62 | uid: "1234567" 63 | }); 64 | let rec = 0; 65 | pushClient.on('push', function(data) { 66 | expect(data.message).to.be.equal('ok'); 67 | expect(++rec).to.be.equal(1); 68 | setTimeout(() => { 69 | done(); 70 | }, 100); 71 | }); 72 | 73 | 74 | request({ 75 | url: apiUrl + '/api/push', 76 | method: "post", 77 | form: { 78 | uid: '[1234, 4321,123456,1234567]', 79 | json: '{"message":"ok"}' 80 | } 81 | }, (error, response, body) => { 82 | console.log(body); 83 | expect(JSON.parse(body).code).to.be.equal("success"); 84 | }); 85 | 86 | }); 87 | 88 | it('unbind from client', (done) => { 89 | pushClient.on('push', function(data) { 90 | expect('do not receive').to.be.equal('ok'); 91 | }); 92 | pushClient.unbindUid(); 93 | setTimeout(() => { 94 | request({ 95 | url: apiUrl + '/api/push', 96 | method: "post", 97 | form: { 98 | uid: '["1234", "4321"]', 99 | json: '{"message":"ok"}' 100 | } 101 | }, (error, response, body) => { 102 | console.log(body); 103 | expect(JSON.parse(body).code).to.be.equal("success"); 104 | }); 105 | 106 | setTimeout(() => { 107 | pushClient.disconnect(); 108 | pushClient.connect(); 109 | pushClient.on('connect', (data) => { 110 | expect(data.uid).to.be.undefined; 111 | pushClient.connect(); 112 | done(); 113 | }); 114 | }, 100); 115 | }, 100); 116 | 117 | }); 118 | 119 | 120 | }); 121 | -------------------------------------------------------------------------------- /push-server/test/defaultSetting.js: -------------------------------------------------------------------------------- 1 | var DefaultSetting = {}; 2 | 3 | DefaultSetting.getDefaultPushClient = (pushId, platform) => { 4 | 5 | let port = require("../config-proxy").http_port + 1; 6 | return require('socket.io-push-client')('http://localhost:' + port, { 7 | pushId, 8 | platform, 9 | transports: ['websocket', 'polling'], 10 | useNotification: true 11 | }); 12 | }; 13 | 14 | DefaultSetting.getDefaultPushHttpsClient = (pushId) => { 15 | let port = require("../config-proxy").https_port + 1; 16 | return require('socket.io-push-client')('https://localhost:' + port, { 17 | pushId: pushId, 18 | transports: ['websocket', 'polling'], 19 | useNotification: true, 20 | rejectUnauthorized: false 21 | }); 22 | }; 23 | 24 | DefaultSetting.getDefaultApiUrl = () => { 25 | let port = require("../config-api").http_port; 26 | return 'http://localhost:' + port; 27 | }; 28 | 29 | DefaultSetting.getDefaultApiServer = () => { 30 | let apiConfig = require('../config-api'); 31 | apiConfig.pushAllInterval = 0; 32 | let apiHttpServer = require('http').createServer(); 33 | apiHttpServer.listen(apiConfig.http_port); 34 | return require('../lib/api')(apiHttpServer, null, apiConfig); 35 | }; 36 | 37 | DefaultSetting.getDefaultApnProxyServer = () => { 38 | let apnConfig = require('../config-apn-proxy'); 39 | let apiHttpServer = require('http').createServer(); 40 | apiHttpServer.listen(apnConfig.http_port); 41 | let fs = require('fs'); 42 | let https_key = fs.readFileSync(apnConfig.https_key); 43 | let https_cert = fs.readFileSync(apnConfig.https_cert); 44 | 45 | let httpsServer = require('spdy').createServer({ 46 | key: https_key, 47 | cert: https_cert 48 | }); 49 | httpsServer.listen(apnConfig.https_port); 50 | return require('../lib/apnProxy')(apiHttpServer, httpsServer, apnConfig); 51 | } 52 | 53 | DefaultSetting.getDefaultProxyServer = () => { 54 | let proxyConfig = require('../config-proxy'); 55 | let proxyHttpServer = require('http').createServer(); 56 | let fs = require('fs'); 57 | let proxyHttpsServer = require('https').createServer({ 58 | key: fs.readFileSync(__dirname + '/../cert/https/key.pem'), 59 | cert: fs.readFileSync(__dirname + '/../cert/https/cert.pem') 60 | }); 61 | let ioServer = require('socket.io'); 62 | let io = new ioServer({ 63 | pingTimeout: proxyConfig.pingTimeout, 64 | pingInterval: proxyConfig.pingInterval, 65 | transports: ['websocket', 'polling'] 66 | }); 67 | io.attach(proxyHttpServer); 68 | io.hs = proxyHttpServer; 69 | io.attach(proxyHttpsServer); 70 | io.hss = proxyHttpsServer; 71 | proxyHttpServer.listen(proxyConfig.http_port + 1); 72 | proxyHttpsServer.listen(proxyConfig.https_port + 1); 73 | proxyConfig.statsCommitThreshold = 500; 74 | return require('../lib/proxy')(io, proxyConfig); 75 | }; 76 | 77 | module.exports = DefaultSetting; 78 | -------------------------------------------------------------------------------- /push-server/test/deleteDeviceTest.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var request = require('request'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('deleteDevice', function() { 7 | 8 | before(function() { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | global.pushClient = defSetting.getDefaultPushClient(); 13 | }); 14 | 15 | after(function() { 16 | global.proxyServer.close(); 17 | global.apiServer.close(); 18 | global.pushClient.disconnect(); 19 | }); 20 | 21 | it('deleteDevice', function(done) { 22 | pushClient.on("connect", function() { 23 | 24 | setTimeout(() => { 25 | apiServer.deviceService.getDeviceByPushId(pushClient.pushId, (data) => { 26 | expect(data._id).to.be.equal(pushClient.pushId); 27 | apiServer.deviceService.deleteByPushId(pushClient.pushId); 28 | setTimeout(() => { 29 | apiServer.deviceService.getDeviceByPushId(pushClient.pushId, (data) => { 30 | expect(data._id).to.be.undefined; 31 | done(); 32 | }); 33 | }, 200); 34 | }) 35 | }, 100); 36 | 37 | }); 38 | }); 39 | 40 | 41 | }); -------------------------------------------------------------------------------- /push-server/test/duplicateTokenTest.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var defSetting = require('./defaultSetting'); 4 | const randomstring = require("randomstring"); 5 | 6 | describe('set token test', function() { 7 | 8 | before(function() { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | global.pushClient1 = defSetting.getDefaultPushClient( 13 | randomstring.generate(12), 'ioS' 14 | ); 15 | global.pushClient2 = defSetting.getDefaultPushClient( 16 | randomstring.generate(12), 'ioS' 17 | ); 18 | }); 19 | 20 | after(function() { 21 | global.proxyServer.close(); 22 | global.apiServer.close(); 23 | global.pushClient1.disconnect(); 24 | global.pushClient2.disconnect(); 25 | }); 26 | 27 | it('dulicateToken', function(done) { 28 | pushClient1.on('connect', function(data) { 29 | pushClient1.socket.emit("token", { 30 | token: "testToken", 31 | type: "testType", 32 | package_name: "testName" 33 | }); 34 | }); 35 | pushClient2.on('connect', function(data) { 36 | setTimeout(() => { 37 | pushClient2.socket.emit("token", { 38 | token: "testToken", 39 | type: "testType", 40 | package_name: "testName" 41 | }); 42 | setTimeout(() => { 43 | apiServer.deviceService.getDeviceByPushId(pushClient1.pushId, (device) => { 44 | expect(device._id).to.not.exist; 45 | apiServer.deviceService.getDeviceByPushId(pushClient2.pushId, (device2) => { 46 | expect(device2.token).to.be.equal('testToken'); 47 | done(); 48 | }); 49 | }); 50 | }, 200); 51 | }, 200); 52 | }); 53 | }); 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /push-server/test/fcmProviderTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('fcmProviderTest', function() { 7 | 8 | before(function() { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apnProxy = defSetting.getDefaultApnProxyServer(); 12 | global.apiUrl = defSetting.getDefaultApiUrl(); 13 | global.pushClient = defSetting.getDefaultPushClient(); 14 | global.pushClient2 = defSetting.getDefaultPushClient(); 15 | }); 16 | 17 | after(function() { 18 | global.proxyServer.close(); 19 | global.apiServer.close(); 20 | global.apnProxy.close(); 21 | global.pushClient.disconnect(); 22 | global.pushClient2.disconnect(); 23 | }); 24 | 25 | it('test send one', function(done) { 26 | pushClient2.on('connect', function() { 27 | pushClient2.socket.emit("token", { 28 | apnToken: "d6iIZtcmmZ4:APA91bFlmVFGrJWyrayinMz_p5jXeU08YgKrrOMhDKm5QkuKimuJdp9aTl2_CdYlsfTfgl6fc2ovbWnbxT1c8mKhURidf5EvquBUfBAFEfv7Io9znYu4-qjJfzWqWPIN3OZvGmrVtrOd", 29 | bundleId: "wwww", 30 | type: "fcm" 31 | }); 32 | }); 33 | 34 | pushClient.on('connect', function() { 35 | pushClient.socket.emit("token", { 36 | apnToken: "d6iIZtcmmZ4:APA91bFlmVFGrJWyrayinMF_p5jXeU08YgKrrOMhDKm5QkuKimuJdp9aTl2_CdYlsfTfgl6fc2ovbWnbxT1c8mKhURidf5EvquBUfBAFEfv7Io9znYu4-qjJfzWqWPIN3OZvGmrVtrOd", 37 | bundleId: "wwww", 38 | type: "fcm" 39 | }); 40 | 41 | var data = { 42 | android: { 43 | title: "send one", 44 | message: "send one Msg" 45 | }, 46 | payload: { 47 | test: "wwwwqqq" 48 | } 49 | }; 50 | var str = JSON.stringify(data); 51 | 52 | pushClient.on('noti', function() { 53 | expect("do not receive").to.be.false 54 | }); 55 | 56 | setTimeout(() => { 57 | request({ 58 | url: apiUrl + '/api/notification', 59 | method: "post", 60 | headers: { 61 | 'Accept': 'application/json' 62 | }, 63 | form: { 64 | pushId: [pushClient.pushId, pushClient2.pushId], 65 | notification: str 66 | } 67 | }, (error, response, body) => { 68 | const result = JSON.parse(body); 69 | expect(result.code).to.be.equal("success"); 70 | setTimeout(() => { 71 | done(); 72 | }, 1500); 73 | }); 74 | }); 75 | }, 300); 76 | 77 | 78 | }); 79 | 80 | 81 | it('test send all', function(done) { 82 | 83 | var data = { 84 | android: { 85 | title: "send all", 86 | message: "send all message" 87 | }, 88 | payload: { 89 | test: "wwwwqqq" 90 | } 91 | }; 92 | var str = JSON.stringify(data); 93 | 94 | request({ 95 | url: apiUrl + '/api/notification', 96 | method: "post", 97 | headers: { 98 | 'Accept': 'application/json' 99 | }, 100 | form: { 101 | pushAll: true, 102 | notification: str 103 | } 104 | }, (error, response, body) => { 105 | const result = JSON.parse(body); 106 | expect(result.code).to.be.equal("success"); 107 | setTimeout(() => { 108 | done(); 109 | }, 1500); 110 | }); 111 | 112 | }); 113 | 114 | 115 | }); -------------------------------------------------------------------------------- /push-server/test/huaweiProviderTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var defSetting = require('./defaultSetting'); 3 | 4 | describe('huawei test', function() { 5 | 6 | before(function() { 7 | global.config = require('../config-api'); 8 | global.apiServer = defSetting.getDefaultApiServer(); 9 | global.apiUrl = defSetting.getDefaultApiUrl(); 10 | global.huaweiProvider = apiServer.huaweiProvider; 11 | }); 12 | 13 | after(function() { 14 | global.apiServer.close(); 15 | }); 16 | 17 | it('huawei send all ', function(done) { 18 | var notificationAll = { 19 | id: "qwer", 20 | android: { 21 | title: "sendAll", 22 | message: "sendAll Msg", 23 | payload: { 24 | test: "wwwwqqq" 25 | } 26 | } 27 | }; 28 | var timeToLive = 10000; 29 | var doneCount = 0; 30 | huaweiProvider.sendAll(notificationAll, timeToLive); 31 | done(); 32 | }); 33 | 34 | it('huawei send one', function(done) { 35 | var notificationOne = { 36 | id: "qwer", 37 | android: { 38 | title: "sendOne", 39 | message: "sendOne Msg", 40 | payload: { 41 | test: "wwwwqqq" 42 | } 43 | } 44 | }; 45 | huaweiProvider.sendMany(notificationOne, [{ 46 | token: "0988774580439242232000001425000001", 47 | package_name: "com.yy.misaka.demo2" 48 | }]); 49 | done(); 50 | }); 51 | 52 | it('huawei send many', function(done) { 53 | var notificationOne = { 54 | id: "qwer", 55 | android: { 56 | title: "sendOne", 57 | message: "sendOne Msg", 58 | payload: { 59 | test: "wwwwqqq" 60 | } 61 | } 62 | }; 63 | var doneCount = 0; 64 | huaweiProvider.sendMany(notificationOne, [{ 65 | token: "0355911070660922200000142500CN01" 66 | }, { 67 | token: "0355911070660922200000142500CN01" 68 | }]); 69 | done(); 70 | }); 71 | 72 | it('huawei send many2', function(done) { 73 | var notificationOne = { 74 | id: "qwer", 75 | android: { 76 | title: "sendOne", 77 | message: "sendOne Msg", 78 | payload: { 79 | test: "wwwwqqq" 80 | } 81 | } 82 | }; 83 | var doneCount = 0; 84 | huaweiProvider.sendMany(notificationOne, [{ 85 | token: "0988774580439242232000001425000001", 86 | package_name: "com.yy.misaka.demo2" 87 | }, { 88 | token: "0355911070660922200000142500CN01" 89 | }], 60 * 60 * 1000); 90 | done(); 91 | 92 | 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /push-server/test/infiniteArrayTest.js: -------------------------------------------------------------------------------- 1 | var IA = require('../lib/util/infiniteArray'); 2 | var expect = require('chai').expect; 3 | 4 | describe('infiniteArrayTest', function () { 5 | 6 | it('baseTest', function (done) { 7 | const array = [1, 2, 3, 4]; 8 | const infiniteArray = IA(array); 9 | expect(infiniteArray.next()).to.equal(1); 10 | expect(infiniteArray.next()).to.equal(2); 11 | expect(infiniteArray.next()).to.equal(3); 12 | expect(infiniteArray.next()).to.equal(4); 13 | expect(infiniteArray.next()).to.equal(1); 14 | expect(infiniteArray.next()).to.equal(2); 15 | expect(infiniteArray.next()).to.equal(3); 16 | expect(infiniteArray.next()).to.equal(4); 17 | expect(infiniteArray.next()).to.equal(1); 18 | expect(infiniteArray.next()).to.equal(2); 19 | expect(infiniteArray.next()).to.equal(3); 20 | expect(infiniteArray.next()).to.equal(4); 21 | expect(infiniteArray.next()).to.be.ok; 22 | done(); 23 | }); 24 | 25 | it('emptyTest', function (done) { 26 | const array = []; 27 | const infiniteArray = IA(array); 28 | expect(infiniteArray.next()).to.equal(undefined); 29 | expect(infiniteArray.next()).to.not.be.ok; 30 | done(); 31 | }); 32 | 33 | it('undefinedTest', function (done) { 34 | const array = undefined; 35 | const infiniteArray = IA(array); 36 | expect(infiniteArray.next()).to.equal(undefined); 37 | expect(infiniteArray.next()).to.not.be.ok; 38 | done(); 39 | }); 40 | 41 | it('nullTest', function (done) { 42 | const infiniteArray = IA(); 43 | expect(infiniteArray.next()).to.equal(undefined); 44 | expect(infiniteArray.next()).to.not.be.ok; 45 | done(); 46 | }); 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /push-server/test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | -t 10000 -------------------------------------------------------------------------------- /push-server/test/moving-sum.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | , spies = require('chai-spies'); 3 | chai.use(spies); 4 | var expect = chai.expect; 5 | 6 | 7 | describe('test moving-sum', function () { 8 | 9 | 10 | it('base test', function (done) { 11 | var ms = require('../lib/stats/moving-sum.js')(); 12 | ms.push(Date.now()); 13 | ms.push(Date.now()); 14 | ms.push(Date.now()); 15 | ms.push(Date.now()); 16 | ms.push(Date.now()); 17 | expect(ms.sum([100000])).to.deep.equal([5]); 18 | done(); 19 | }); 20 | 21 | 22 | it('less test', function (done) { 23 | var ms = require('../lib/stats/moving-sum.js')(); 24 | ms.push(Date.now() - 1000); 25 | ms.push(Date.now()); 26 | ms.push(Date.now()); 27 | ms.push(Date.now()); 28 | ms.push(Date.now()); 29 | expect(ms.sum([500])).to.deep.equal([4]); 30 | done(); 31 | }); 32 | 33 | it('double test', function (done) { 34 | var ms = require('../lib/stats/moving-sum.js')(); 35 | ms.push(Date.now() - 10000); 36 | ms.push(Date.now() - 5000); 37 | ms.push(Date.now() - 4000); 38 | ms.push(Date.now() - 3000); 39 | ms.push(Date.now() - 2000); 40 | ms.push(Date.now()); 41 | expect(ms.sum([4500, 6000])).to.deep.equal([4, 5]); 42 | expect(ms.stamps.length).to.equal(5); 43 | expect(ms.sum([1500, 3500])).to.deep.equal([1, 3]); 44 | expect(ms.stamps.length).to.equal(3); 45 | done(); 46 | }); 47 | 48 | it('reverse test', function (done) { 49 | var ms = require('../lib/stats/moving-sum.js')(); 50 | ms.push(Date.now() - 10000); 51 | ms.push(Date.now() - 5000); 52 | ms.push(Date.now() - 4000); 53 | ms.push(Date.now() - 3000); 54 | ms.push(Date.now() - 2000); 55 | ms.push(Date.now()); 56 | expect(ms.sum([6000, 4500])).to.deep.equal([5, 4]); 57 | expect(ms.stamps.length).to.equal(5); 58 | expect(ms.sum([3500, 1500])).to.deep.equal([3, 1]); 59 | expect(ms.stamps.length).to.equal(3); 60 | done(); 61 | }); 62 | 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /push-server/test/notificationTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('notification', function() { 7 | 8 | before(function() { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | global.apnProxy = defSetting.getDefaultApnProxyServer(); 13 | global.pushClient = defSetting.getDefaultPushClient(); 14 | global.pushClient2 = defSetting.getDefaultPushClient(); 15 | }); 16 | 17 | after(function() { 18 | global.proxyServer.close(); 19 | global.apiServer.close(); 20 | global.apnProxy.close(); 21 | global.pushClient.disconnect(); 22 | global.pushClient2.disconnect(); 23 | }); 24 | 25 | it('connect', function(done) { 26 | pushClient.on('connect', function(data) { 27 | expect(data.pushId).to.be.equal(pushClient.pushId); 28 | done(); 29 | }); 30 | 31 | }); 32 | 33 | 34 | it('bind uid', function(done) { 35 | 36 | request({ 37 | url: apiUrl + '/api/uid/bind', 38 | method: "post", 39 | form: { 40 | pushId: pushClient.pushId, 41 | uid: 1 42 | } 43 | }, (error, response, body) => { 44 | expect(JSON.parse(body).code).to.be.equal("success"); 45 | done(); 46 | }); 47 | }); 48 | 49 | 50 | it('notification to pushId', function(done) { 51 | var title = 'hello', 52 | message = 'hello world'; 53 | var data = { 54 | "android": { 55 | "title": title, 56 | "message": message 57 | } 58 | } 59 | var str = JSON.stringify(data); 60 | 61 | var notificationCallback = function(data) { 62 | expect(data.title).to.be.equal(title); 63 | expect(data.message).to.be.equal(message); 64 | done(); 65 | } 66 | 67 | pushClient.on('notification', notificationCallback); 68 | 69 | request({ 70 | url: apiUrl + '/api/notification', 71 | method: "post", 72 | form: { 73 | pushId: pushClient.pushId, 74 | notification: str 75 | } 76 | }, (error, response, body) => { 77 | console.log('notification to pushId ', pushClient.pushId); 78 | expect(JSON.parse(body).code).to.be.equal("success"); 79 | }); 80 | 81 | }); 82 | 83 | it('Notification pushAll', function(done) { 84 | var title = 'hello', 85 | message = 'hello world'; 86 | var data = { 87 | android: { 88 | "title": title, 89 | "message": message 90 | }, 91 | payload: { 92 | "ppp": 123 93 | } 94 | } 95 | var str = JSON.stringify(data); 96 | proxyServer.topicOnline.flush(); 97 | 98 | var notificationCallback = function(data) { 99 | expect(data.title).to.be.equal(title); 100 | expect(data.message).to.be.equal(message); 101 | expect(data.payload.ppp).to.be.equal(123); 102 | apiServer.arrivalStats.incrBuffer.commit(); 103 | setTimeout(() => { 104 | apiServer.arrivalStats.getArrivalInfo(data.id, (result) => { 105 | expect(result.android.arrive).to.be.equal(2); 106 | done(); 107 | }); 108 | }, 500); 109 | } 110 | pushClient.on('notification', notificationCallback); 111 | 112 | request({ 113 | url: apiUrl + '/api/notification', 114 | method: "post", 115 | form: { 116 | pushAll: 'true', 117 | notification: str, 118 | timeToLive: 100 119 | } 120 | }, (error, response, body) => { 121 | expect(JSON.parse(body).code).to.be.equal("success"); 122 | }); 123 | }); 124 | 125 | }); 126 | -------------------------------------------------------------------------------- /push-server/test/restApiParamCheck.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('api param check', function() { 7 | 8 | before(() => { 9 | global.apiServer = defSetting.getDefaultApiServer(); 10 | global.apiUrl = defSetting.getDefaultApiUrl(); 11 | }); 12 | 13 | after(() => { 14 | global.apiServer.close(); 15 | }); 16 | 17 | it('topic is required', function(done) { 18 | 19 | request({ 20 | url: apiUrl + '/api/push', 21 | method: "post", 22 | form: { 23 | pushId: '', 24 | pushAll: 'true', 25 | data: 'test', 26 | topic: '' 27 | } 28 | }, (error, response, body) => { 29 | expect(JSON.parse(body).code).to.be.equal("error"); 30 | done(); 31 | }); 32 | 33 | }); 34 | 35 | it('push target should be single', function(done) { 36 | 37 | request({ 38 | url: apiUrl + '/api/push', 39 | method: "post", 40 | form: { 41 | pushId: 'www', 42 | data: 'test', 43 | topic: 'qqq' 44 | } 45 | }, (error, response, body) => { 46 | expect(JSON.parse(body).code).to.be.equal("error"); 47 | done(); 48 | }); 49 | 50 | }); 51 | 52 | it('push target should be single2', function(done) { 53 | 54 | request({ 55 | url: apiUrl + '/api/push', 56 | method: "post", 57 | form: { 58 | uid: 'www', 59 | data: 'test', 60 | topic: 'qqq' 61 | } 62 | }, (error, response, body) => { 63 | expect(JSON.parse(body).code).to.be.equal("error"); 64 | done(); 65 | }); 66 | 67 | }); 68 | 69 | it('push target should be single3', function(done) { 70 | 71 | request({ 72 | url: apiUrl + '/api/push', 73 | method: "post", 74 | form: { 75 | pushId: 'www', 76 | data: 'test', 77 | uid: 'qqq' 78 | } 79 | }, (error, response, body) => { 80 | expect(JSON.parse(body).code).to.be.equal("error"); 81 | done(); 82 | }); 83 | 84 | }); 85 | 86 | it('notification target should be single', function(done) { 87 | 88 | request({ 89 | url: apiUrl + '/api/notification', 90 | method: "post", 91 | form: { 92 | pushId: 'www', 93 | data: 'test', 94 | uid: 'qqq' 95 | } 96 | }, (error, response, body) => { 97 | expect(JSON.parse(body).code).to.be.equal("error"); 98 | done(); 99 | }); 100 | 101 | }); 102 | 103 | it('notification target should be single2', function(done) { 104 | 105 | request({ 106 | url: apiUrl + '/api/notification', 107 | method: "post", 108 | form: { 109 | pushId: 'www', 110 | data: 'test', 111 | pushAll: 'true' 112 | } 113 | }, (error, response, body) => { 114 | expect(JSON.parse(body).code).to.be.equal("error"); 115 | done(); 116 | }); 117 | 118 | }); 119 | 120 | it('data is required', function(done) { 121 | 122 | request({ 123 | url: apiUrl + '/api/push', 124 | method: "post", 125 | form: { 126 | pushId: '', 127 | topic: 'www', 128 | data: '' 129 | } 130 | }, (error, response, body) => { 131 | expect(JSON.parse(body).code).to.be.equal("error"); 132 | done(); 133 | }); 134 | 135 | }); 136 | 137 | it('pushId is required', function(done) { 138 | 139 | request({ 140 | url: apiUrl + '/api/push', 141 | method: "post", 142 | form: { 143 | pushId: '', 144 | topic: '', 145 | data: 'wwww' 146 | } 147 | }, (error, response, body) => { 148 | expect(JSON.parse(body).code).to.be.equal("error"); 149 | done(); 150 | }); 151 | 152 | }); 153 | 154 | it('notification target is required', function(done) { 155 | 156 | request({ 157 | url: apiUrl + '/api/notification', 158 | method: "post", 159 | form: { 160 | notification: JSON.stringify({ 161 | apn: { 162 | alert: 'restApiParam' 163 | } 164 | }) 165 | } 166 | }, (error, response, body) => { 167 | expect(JSON.parse(body).code).to.be.equal("error"); 168 | done(); 169 | }); 170 | 171 | }); 172 | 173 | it('notification all success', function(done) { 174 | 175 | request({ 176 | url: apiUrl + '/api/notification', 177 | method: "post", 178 | form: { 179 | notification: JSON.stringify({ 180 | apn: { 181 | alert: 'wwww' 182 | } 183 | }), 184 | pushAll: 'true' 185 | } 186 | }, (error, response, body) => { 187 | expect(JSON.parse(body).code).to.be.equal("success"); 188 | done(); 189 | }); 190 | 191 | }); 192 | 193 | it('notification tag success', function(done) { 194 | 195 | request({ 196 | url: apiUrl + '/api/notification', 197 | method: "post", 198 | form: { 199 | notification: JSON.stringify({ 200 | apn: { 201 | alert: 'wwww' 202 | } 203 | }), 204 | tag: 'abc' 205 | } 206 | }, (error, response, body) => { 207 | expect(JSON.parse(body).code).to.be.equal("success"); 208 | done(); 209 | }); 210 | 211 | }); 212 | 213 | it('notification no target error success', function(done) { 214 | 215 | request({ 216 | url: apiUrl + '/api/notification', 217 | method: "post", 218 | form: { 219 | notification: JSON.stringify({ 220 | apn: { 221 | alert: 'wwww' 222 | } 223 | }) 224 | } 225 | }, (error, response, body) => { 226 | expect(JSON.parse(body).code).to.be.equal("error"); 227 | done(); 228 | }); 229 | 230 | }); 231 | 232 | }); 233 | -------------------------------------------------------------------------------- /push-server/test/restApiTest.js: -------------------------------------------------------------------------------- 1 | let request = require('request'); 2 | let chai = require('chai'); 3 | let expect = chai.expect; 4 | let defSetting = require('./defaultSetting'); 5 | 6 | describe('rest api test', () => { 7 | before(() => { 8 | global.apiServer = defSetting.getDefaultApiServer(); 9 | global.apiUrl = defSetting.getDefaultApiUrl(); 10 | global.redis = apiServer.io.redis; 11 | }); 12 | after(() => { 13 | apiServer.close(); 14 | }); 15 | 16 | it('stat base', (done) => { 17 | request({ 18 | url: apiUrl + '/api/stats/base', 19 | method: "get", 20 | }, (error, response, body) => { 21 | let ret = JSON.parse(body); 22 | expect(ret.packetAverage10s).to.be.exist; 23 | expect(ret.packetDrop).to.be.exist; 24 | expect(ret.processCount).to.be.exist; 25 | done(); 26 | }); 27 | }); 28 | 29 | // it('heapdump', (done) => { 30 | // request({ 31 | // url: apiUrl + '/api/heapdump', 32 | // method: "post", 33 | // }, (error, response, body) => { 34 | // console.log(body); 35 | // let ret = JSON.parse(body); 36 | // expect(ret.code).to.be.equal('success'); 37 | // expect(ret.file).to.be.exist(); 38 | // expect(file(ret.file)).to.exist(); 39 | // done(); 40 | // }); 41 | // }); 42 | 43 | it('stat chart', (done) => { 44 | request({ 45 | url: apiUrl + '/api/stats/chart', 46 | method: 'get', 47 | form: { 48 | key: 'whatever' 49 | } 50 | }, (error, response, body) => { 51 | let ret = JSON.parse(body); 52 | expect(ret.totalCount).to.be.equal(0); 53 | expect(ret.totalSuccess).to.be.equal(0); 54 | done(); 55 | }) 56 | }); 57 | 58 | it('stat arrival rate', (done) => { 59 | request({ 60 | url: apiUrl + '/api/stats/arrival/pushall', 61 | method: 'get', 62 | }, (error, response, body) => { 63 | let ret = JSON.parse(body); 64 | expect(ret).to.be.array; 65 | done(); 66 | }) 67 | }); 68 | 69 | it('stat query data keys', (done) => { 70 | request({ 71 | url: apiUrl + '/api/stats/getQueryDataKeys', 72 | method: 'get' 73 | }, (error, response, body) => { 74 | let ret = JSON.parse(body); 75 | expect(ret.result).to.be.array; 76 | done(); 77 | }) 78 | }); 79 | 80 | it('config', (done) => { 81 | request({ 82 | url: apiUrl + '/api/config', 83 | method: 'get' 84 | }, (error, response, body) => { 85 | expect(body).to.equal(JSON.stringify(require('../config-api'))); 86 | done(); 87 | }) 88 | }) 89 | }); 90 | -------------------------------------------------------------------------------- /push-server/test/setTagsTest.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var request = require('request'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('tag', function() { 7 | 8 | before(function() { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | global.pushClient = defSetting.getDefaultPushClient(); 13 | }); 14 | 15 | after(function() { 16 | global.proxyServer.close(); 17 | global.apiServer.close(); 18 | global.pushClient.disconnect(); 19 | }); 20 | 21 | it('setTags', function(done) { 22 | pushClient.on("connect", function() { 23 | pushClient.setTags(["tag1", "tag3"]); 24 | setTimeout(function() { 25 | global.apiServer.deviceService.getTagsByPushId(pushClient.pushId, function(tags) { 26 | expect(tags).to.have.members(["tag1", "tag3"]); 27 | global.apiServer.deviceService.getPushIdsByTag("tag1", function(pushIds) { 28 | expect(pushIds).to.deep.include.members([pushClient.pushId]); 29 | pushClient.disconnect(); 30 | done(); 31 | }); 32 | }); 33 | }, 100); 34 | }); 35 | }); 36 | 37 | it('setTags 2', function(done) { 38 | pushClient.connect(); 39 | pushClient.on("connect", function() { 40 | pushClient.setTags(["tag3", "tag4"]); 41 | setTimeout(function() { 42 | global.apiServer.deviceService.getTagsByPushId(pushClient.pushId, function(tags) { 43 | expect(tags).to.have.members(["tag3", "tag4"]); 44 | global.apiServer.deviceService.getPushIdsByTag("tag4", function(pushIds) { 45 | expect(pushIds).to.deep.include.members([pushClient.pushId]); 46 | done(); 47 | }); 48 | }); 49 | }, 100); 50 | }); 51 | }); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /push-server/test/setTokenTest.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var defSetting = require('./defaultSetting'); 4 | const randomstring = require("randomstring"); 5 | 6 | describe('set token test', function() { 7 | 8 | before(function() { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | global.pushClient = defSetting.getDefaultPushClient( 13 | randomstring.generate(12), 'ioS' 14 | ); 15 | }); 16 | 17 | after(function() { 18 | global.proxyServer.close(); 19 | global.apiServer.close(); 20 | global.pushClient.disconnect(); 21 | }); 22 | 23 | it('set apn no token', function(done) { 24 | pushClient.on('connect', function(data) { 25 | console.log('connect ------------- ', data); 26 | expect(data.pushId).to.be.equal(pushClient.pushId); 27 | var deviceService = apiServer.deviceService; 28 | setTimeout(function() { 29 | deviceService.getDeviceByPushId(pushClient.pushId, function(token) { 30 | expect(token.type).to.equal("apnNoToken"); 31 | pushClient.socket.emit("token", { 32 | token: "testToken", 33 | type: "testType" 34 | }); 35 | setTimeout(function() { 36 | deviceService.getDeviceByPushId(pushClient.pushId, function(token) { 37 | expect(token.token).to.equal("testToken"); 38 | expect(token.type).to.equal("testType"); 39 | pushClient.disconnect(); 40 | pushClient.connect(); 41 | pushClient.on('connect', () => { 42 | setTimeout(() => { 43 | deviceService.getDeviceByPushId(pushClient.pushId, (token) => { 44 | expect(token.token).to.equal("testToken"); 45 | expect(token.type).to.equal("testType"); 46 | done(); 47 | }); 48 | }, 100); 49 | }); 50 | }); 51 | }, 100); 52 | }); 53 | }, 100); 54 | }); 55 | }); 56 | 57 | }); 58 | -------------------------------------------------------------------------------- /push-server/test/statsTest.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var request = require('request'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('statsTest', function() { 7 | 8 | before(function(done) { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | global.stats = proxyServer.stats; 13 | global.pushClient = defSetting.getDefaultPushClient(); 14 | stats.mongo.session.remove({}, done); 15 | }); 16 | 17 | after(function() { 18 | global.proxyServer.close(); 19 | global.apiServer.close(); 20 | global.pushClient.disconnect(); 21 | }); 22 | 23 | it('sessionCount test', function(done) { 24 | pushClient.on('connect', function() { 25 | stats.writeStatsToRedis(() => { 26 | stats.getSessionCount(function(data) { 27 | console.log(data); 28 | expect(data.total).to.be.equal(1); 29 | expect(data.processCount[0].count.total).to.be.equal(1); 30 | expect(data.processCount[0].id).to.be.equal(stats.id); 31 | done(); 32 | }); 33 | }); 34 | }); 35 | }); 36 | 37 | it('packetDrop test', function(done) { 38 | var i = 0; 39 | pushClient.on('push', function() { 40 | expect(i++).to.lessThan(3); 41 | if (i != 3) { 42 | push(); 43 | } else { 44 | stats.packetDropThreshold = 1; 45 | push(); 46 | push(); 47 | push(); 48 | setTimeout(function() { 49 | done(); 50 | }, 50); 51 | } 52 | }); 53 | 54 | push(); 55 | 56 | }); 57 | 58 | it('find toClientPacket > 0', function(done) { 59 | stats.redisIncrBuffer.commit(); 60 | setTimeout(() => { 61 | stats.find("toClientPacket", function(data) { 62 | expect(data.totalCount).to.greaterThan(0); 63 | done(); 64 | }); 65 | }, 50); 66 | }); 67 | 68 | it('find', function(done) { 69 | stats.getQueryDataKeys(function(data) { 70 | expect(data).to.contain("toClientPacket"); 71 | done(); 72 | }); 73 | }); 74 | 75 | }); 76 | 77 | function push() { 78 | 79 | request({ 80 | url: apiUrl + '/api/push', 81 | method: "post", 82 | form: { 83 | pushId: pushClient.pushId, 84 | json: "wwww" 85 | } 86 | }, (error, response, body) => { 87 | expect(JSON.parse(body).code).to.be.equal("success"); 88 | }); 89 | 90 | } 91 | -------------------------------------------------------------------------------- /push-server/test/topicOnline.js: -------------------------------------------------------------------------------- 1 | let request = require('request'); 2 | var io = require('socket.io'); 3 | const mongo = require('../lib/mongo/mongo')(require('../config-api').prefix, require('../config-api').mongo); 4 | var topicOnline = require('../lib/stats/topicOnline.js')(mongo, io, 'Ys7Gh2NwDY9Dqti92ZwxJh8ymQL4mmZ2 ', { 5 | 'topic:': "devices" 6 | }); 7 | var topicOnline1 = require('../lib/stats/topicOnline.js')(mongo, io, 'Ys7Gh2NwDY9Dqti92ZwxJh8ymQL4mmZ3 ', { 8 | 'topic:': "count" 9 | }); 10 | let defSetting = require('./defaultSetting'); 11 | var chai = require('chai'); 12 | var expect = chai.expect; 13 | 14 | describe('api topicOnline', () => { 15 | 16 | before(() => { 17 | global.apiServer = defSetting.getDefaultApiServer(); 18 | global.apiUrl = defSetting.getDefaultApiUrl(); 19 | }); 20 | 21 | after(() => { 22 | apiServer.close(); 23 | }); 24 | 25 | var data = { 26 | "topic:Test1": { 27 | length: 3 28 | }, 29 | "topic:Test2": { 30 | length: 4 31 | } 32 | }; 33 | 34 | it('Test topicOnline', (done) => { 35 | topicOnline.writeTopicOnline(data); 36 | topicOnline1.writeTopicOnline(data); 37 | setTimeout(() => { 38 | topicOnline.getTopicOnline('topic:Test1', (result) => { 39 | expect(result).to.be.equal(6); 40 | topicOnline.getTopicOnline('xxxx', (result) => { 41 | expect(result).to.be.equal(0); 42 | topicOnline.getTopicOnline('topic:Test2', (result) => { 43 | expect(result).to.be.equal(8); 44 | done(); 45 | }); 46 | }); 47 | }); 48 | }, 1000); 49 | 50 | }); 51 | 52 | it('Test topicDevices data ', (done) => { 53 | topicOnline.getTopicDevices('topic:Test1', (result) => { 54 | expect(result.total).to.be.equal(0); 55 | expect(result.topic).to.be.equal('topic:Test1'); 56 | done(); 57 | }); 58 | }); 59 | 60 | it('Test topicOnline data timeOut', (done) => { 61 | topicOnline.timeValidWithIn = 0; 62 | topicOnline.writeTopicOnline(data); 63 | topicOnline1.timeValidWithIn = 0; 64 | topicOnline1.writeTopicOnline(data); 65 | setTimeout(() => { 66 | topicOnline.getTopicOnline('topic:Test1', (result) => { 67 | expect(result).to.be.equal(0); 68 | done(); 69 | }); 70 | }, 200); 71 | }); 72 | 73 | it('topic onelie restapi', (done) => { 74 | request({ 75 | url: apiUrl + '/api/topicOnline', 76 | method: 'get', 77 | form: { 78 | topic: 'whocares' 79 | } 80 | }, (error, response, body) => { 81 | let ret = JSON.parse(body); 82 | expect(ret.count).to.be.equal(0); 83 | expect(ret.topic).to.be.equal('whocares'); 84 | done(); 85 | }) 86 | }); 87 | 88 | it('topic online restapi with wrong param', (done) => { 89 | request({ 90 | url: apiUrl + '/api/topicOnline', 91 | method: 'get', 92 | }, (error, response, body) => { 93 | let ret = JSON.parse(body); 94 | expect(ret.code).to.be.equal('error'); 95 | done(); 96 | }) 97 | }); 98 | 99 | it('device online restapi', (done) => { 100 | request({ 101 | url: apiUrl + '/api/topicDevices', 102 | method: 'get', 103 | form: { 104 | topic: 'whocares' 105 | } 106 | }, (error, response, body) => { 107 | let ret = JSON.parse(body); 108 | expect(ret.total).to.be.equal(0); 109 | expect(ret.topic).to.be.equal('whocares'); 110 | expect(ret.devices).to.be.array; 111 | done(); 112 | }) 113 | }); 114 | 115 | it('devices online restapi with wrong param', (done) => { 116 | request({ 117 | url: apiUrl + '/api/topicDevices', 118 | method: 'get', 119 | }, (error, response, body) => { 120 | let ret = JSON.parse(body); 121 | expect(ret.code).to.be.equal('error'); 122 | done(); 123 | }) 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /push-server/test/ttlServiceSingleTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var randomstring = require("randomstring"); 4 | var expect = chai.expect; 5 | var logger = require('winston-proxy')('TTLServiceTest'); 6 | var defSetting = require('./defaultSetting'); 7 | 8 | describe('ttlServiceSingleTest.js', function () { 9 | 10 | before(function () { 11 | global.proxyServer = defSetting.getDefaultProxyServer(); 12 | global.apiServer = defSetting.getDefaultApiServer(); 13 | global.apiUrl = defSetting.getDefaultApiUrl(); 14 | global.pushClient = defSetting.getDefaultPushClient(); 15 | }); 16 | 17 | after(function () { 18 | global.proxyServer.close(); 19 | global.apiServer.close(); 20 | }); 21 | 22 | it('test ttl to single', function (done) { 23 | pushClient.on('push', function (data) { 24 | logger.debug('receive first push'); 25 | expect(data.message).to.equal(1); 26 | pushClient.disconnect(); 27 | 28 | request({ 29 | url: apiUrl + '/api/push', 30 | method: "post", 31 | form: { 32 | pushId: pushClient.pushId, 33 | json: JSON.stringify({message: 2}), 34 | timeToLive: 10000 35 | } 36 | }, (error, response, body) => { 37 | expect(JSON.parse(body).code).to.be.equal("success"); 38 | request({ 39 | url: apiUrl + '/api/push', 40 | method: "post", 41 | form: { 42 | pushId: pushClient.pushId, 43 | json: JSON.stringify({message: 3}), 44 | timeToLive: 10000 45 | } 46 | }, (error, response, body) => { 47 | expect(JSON.parse(body).code).to.be.equal("success"); 48 | pushClient.connect(); 49 | var push = [2, 3, 4]; 50 | pushClient.on('push', function (data) { 51 | logger.debug("receive ", data.message); 52 | expect(data.message).to.equal(push.shift()); 53 | if (push.length == 1) { 54 | request({ 55 | url: apiUrl + '/api/push', 56 | method: "post", 57 | form: { 58 | pushId: pushClient.pushId, 59 | json: JSON.stringify({message: 4}), 60 | timeToLive: 10000 61 | } 62 | }, (error, response, body) => { 63 | } 64 | ); 65 | } 66 | if (push.length == 0) { 67 | pushClient.disconnect(); 68 | pushClient.on('push', function (data) { 69 | expect('do not recieve after reconnect').to.equal(data.message); 70 | }); 71 | pushClient.connect(); 72 | pushClient.on('connect', function () { 73 | setTimeout(function () { 74 | pushClient.disconnect(); 75 | done(); 76 | }, 200); 77 | }) 78 | } 79 | }); 80 | }); 81 | }); 82 | }); 83 | 84 | pushClient.on('connect', () => { 85 | pushClient.on('connect'); 86 | request({ 87 | url: apiUrl + '/api/push', 88 | method: "post", 89 | form: { 90 | pushId: pushClient.pushId, 91 | json: JSON.stringify({message: 1}), 92 | timeToLive: 10000 93 | } 94 | }, (error, response, body) => { 95 | expect(JSON.parse(body).code).to.be.equal("success"); 96 | }); 97 | 98 | }); 99 | 100 | }); 101 | 102 | }); 103 | -------------------------------------------------------------------------------- /push-server/test/ttlServiceTopicTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('push test', function () { 7 | 8 | before(function () { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | }); 13 | 14 | after(function () { 15 | global.proxyServer.close(); 16 | global.apiServer.close(); 17 | }); 18 | 19 | it('test ttl to topic', function (done) { 20 | 21 | request({ 22 | url: apiUrl + '/api/push', 23 | method: "post", 24 | form: { 25 | pushAll: "true", 26 | topic: 'message', 27 | json: JSON.stringify({message: 1}), 28 | timeToLive: 10000 29 | } 30 | }, (error, response, body) => { 31 | expect(JSON.parse(body).code).to.be.equal("success"); 32 | var pushClient = defSetting.getDefaultPushClient(); 33 | pushClient.subscribeTopicAndReceiveTTL("message"); 34 | pushClient.on('push', function (data) { 35 | expect(data.message).to.equal(2); 36 | var send = false; 37 | pushClient.on("disconnect", function () { 38 | console.log("pushClient.on(disconnect"); 39 | if (!send) { 40 | send = true; 41 | 42 | request({ 43 | url: apiUrl + '/api/push', 44 | method: "post", 45 | form: { 46 | pushAll: "true", 47 | topic: 'message', 48 | json: JSON.stringify({message: 3}), 49 | timeToLive: 10000 50 | } 51 | }, (error, response, body) => { 52 | expect(JSON.parse(body).code).to.be.equal("success"); 53 | pushClient.connect(); 54 | pushClient.on('push', function (data) { 55 | expect(data.message).to.equal(3); 56 | pushClient.disconnect(); 57 | done(); 58 | }); 59 | }); 60 | } 61 | }); 62 | pushClient.disconnect(); 63 | 64 | }); 65 | setTimeout(function () { // send while online 66 | 67 | request({ 68 | url: apiUrl + '/api/push', 69 | method: "post", 70 | form: { 71 | pushAll: "true", 72 | topic: 'message', 73 | json: JSON.stringify({message: 2}), 74 | timeToLive: 10000 75 | } 76 | }, (error, response, body) => { 77 | }); 78 | }, 100); 79 | }); 80 | }); 81 | 82 | }); 83 | -------------------------------------------------------------------------------- /push-server/test/ttlServiceUidTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var randomstring = require("randomstring"); 4 | var expect = chai.expect; 5 | var logger = require('winston-proxy')('TTLServiceTest'); 6 | var defSetting = require('./defaultSetting'); 7 | 8 | describe('ttlServiceUidTest.js', function() { 9 | 10 | before(function() { 11 | global.proxyServer = defSetting.getDefaultProxyServer(); 12 | global.apiServer = defSetting.getDefaultApiServer(); 13 | global.apiUrl = defSetting.getDefaultApiUrl(); 14 | global.pushClient = defSetting.getDefaultPushClient(); 15 | }); 16 | 17 | after(function() { 18 | global.proxyServer.close(); 19 | global.apiServer.close(); 20 | }); 21 | 22 | it('bind uid', (done) => { 23 | 24 | request({ 25 | url: apiUrl + '/api/uid/bind', 26 | method: "post", 27 | form: { 28 | pushId: pushClient.pushId, 29 | uid: 1 30 | } 31 | }, (error, response, body) => { 32 | expect(JSON.parse(body).code).to.be.equal("success"); 33 | setTimeout(() => { 34 | pushClient.disconnect(); 35 | pushClient.connect(); 36 | pushClient.on('connect', (data) => { 37 | expect(data.uid).to.equal("1"); 38 | done(); 39 | }); 40 | }, 200); 41 | }); 42 | 43 | }); 44 | 45 | it('test ttl to uid', function(done) { 46 | 47 | pushClient.disconnect(); 48 | pushClient.connect(); 49 | 50 | pushClient.on('push', function(data) { 51 | logger.debug('receive first push'); 52 | expect(data.message).to.equal(1); 53 | pushClient.disconnect(); 54 | 55 | request({ 56 | url: apiUrl + '/api/push', 57 | method: "post", 58 | form: { 59 | uid: '1', 60 | json: JSON.stringify({ 61 | message: 2 62 | }), 63 | timeToLive: 10000 64 | } 65 | }, (error, response, body) => { 66 | logger.debug('/api/push messge 2'); 67 | expect(JSON.parse(body).code).to.be.equal("success"); 68 | request({ 69 | url: apiUrl + '/api/push', 70 | method: "post", 71 | form: { 72 | uid: 1, 73 | json: JSON.stringify({ 74 | message: 3 75 | }), 76 | timeToLive: 10000 77 | } 78 | }, (error, response, body) => { 79 | logger.debug('/api/push messge 3'); 80 | expect(JSON.parse(body).code).to.be.equal("success"); 81 | pushClient.connect(); 82 | var push = [2, 3, 4]; 83 | pushClient.on('push', function(data) { 84 | logger.debug("uid receive ", data.message); 85 | expect(data.message).to.equal(push.shift()); 86 | if (push.length == 1) { 87 | request({ 88 | url: apiUrl + '/api/push', 89 | method: "post", 90 | form: { 91 | uid: 1, 92 | json: JSON.stringify({ 93 | message: 4 94 | }), 95 | timeToLive: 10000 96 | } 97 | }, (error, response, body) => { 98 | logger.debug("uid push"); 99 | }); 100 | } 101 | if (push.length == 0) { 102 | pushClient.disconnect(); 103 | pushClient.on('push', function(data) { 104 | expect('do not recieve after reconnect').to.equal(data.message); 105 | }); 106 | pushClient.connect(); 107 | pushClient.on('connect', function() { 108 | setTimeout(function() { 109 | pushClient.disconnect(); 110 | done(); 111 | }, 200); 112 | }) 113 | } 114 | }); 115 | }); 116 | }); 117 | }); 118 | 119 | pushClient.on('connect', () => { 120 | pushClient.on('connect'); 121 | request({ 122 | url: apiUrl + '/api/push', 123 | method: "post", 124 | form: { 125 | uid: 1, 126 | json: JSON.stringify({ 127 | message: 1 128 | }), 129 | timeToLive: 10000 130 | } 131 | }, (error, response, body) => { 132 | console.log('push body ------- ', body); 133 | expect(JSON.parse(body).code).to.be.equal("success"); 134 | }); 135 | 136 | }); 137 | 138 | }); 139 | 140 | }); 141 | -------------------------------------------------------------------------------- /push-server/test/uidPlatformTest.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('pushTest', function () { 7 | 8 | before(function () { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | }); 13 | 14 | after(function () { 15 | global.proxyServer.close(); 16 | global.apiServer.close(); 17 | }); 18 | 19 | function delayCallback(callback) { 20 | setTimeout(()=> { 21 | callback(null, null); 22 | }, 200); 23 | } 24 | 25 | it('bindUid', function (done) { 26 | async.series({ 27 | "bind uid to a": (callback) => { 28 | apiServer.uidStore.bindUid("a", "100", "ios", 1); 29 | delayCallback(callback); 30 | }, 31 | "bind uid to b": (callback) => { 32 | apiServer.uidStore.bindUid("b", "100", "ios", 1); 33 | delayCallback(callback); 34 | }, 35 | "test uid bind result - test1": (callback) => { 36 | apiServer.uidStore.getPushIdByUid("100", function (pushIds) { 37 | expect(pushIds).to.be.deep.equal(["b"]); 38 | apiServer.uidStore.bindUid("c", "100", "ios", 2); 39 | delayCallback(callback); 40 | }); 41 | }, 42 | "test uid bind result - test2": (callback) => { 43 | apiServer.uidStore.getPushIdByUid("100", function (pushIds) { 44 | expect(pushIds).to.be.deep.equal(["b", "c"]); 45 | apiServer.uidStore.bindUid("d", "100", "ios", 2); 46 | delayCallback(callback); 47 | }); 48 | }, 49 | "test uid bind result - test3": (callback) => { 50 | apiServer.uidStore.getPushIdByUid("100", function (pushIds) { 51 | expect(pushIds).to.be.deep.equal(["c", "d"]); 52 | apiServer.uidStore.bindUid("e", "100", "ios", 4); 53 | delayCallback(callback); 54 | }); 55 | }, 56 | "test uid bind result - test4": (callback) => { 57 | apiServer.uidStore.getPushIdByUid("100", function (pushIds) { 58 | expect(pushIds).to.be.deep.equal(["c", "d", "e"]); 59 | delayCallback(callback); 60 | }); 61 | } 62 | }, ()=> { 63 | done(); 64 | }); 65 | }); 66 | 67 | 68 | it('unbind uid', function (done) { 69 | async.series({ 70 | 'remove uid': (callback) => { 71 | apiServer.uidStore.removeUid("1000"); 72 | delayCallback(callback); 73 | }, 74 | 'bind uid to a ': (callback) => { 75 | apiServer.uidStore.bindUid("a", "1000", 0, "ios", 0); 76 | delayCallback(callback); 77 | }, 78 | 'bind uid to b': (callback) => { 79 | apiServer.uidStore.bindUid("b", "1000", 0, "ios", 0); 80 | delayCallback(callback); 81 | }, 82 | 'test uid bind result': (callback) => { 83 | apiServer.uidStore.getPushIdByUid("1000", function (pushIds) { 84 | expect(pushIds).to.be.deep.equal(["a", "b"]); 85 | delayCallback(callback); 86 | }); 87 | }, 88 | "remove uid ": (callback) => { 89 | apiServer.uidStore.removeUid("1000"); 90 | delayCallback(callback); 91 | }, 92 | "test uid bind result ": (callback) => { 93 | apiServer.uidStore.getPushIdByUid("1000", function (pushIds) { 94 | expect(pushIds).to.be.empty; 95 | apiServer.uidStore.getUidByPushId("a", function (uid) { 96 | expect(uid).to.not.exist; 97 | delayCallback(callback); 98 | }); 99 | }); 100 | } 101 | }, ()=> { 102 | done(); 103 | }); 104 | }); 105 | 106 | }); 107 | -------------------------------------------------------------------------------- /push-server/test/uidTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('push test', () => { 7 | 8 | before(() => { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | global.pushClient = defSetting.getDefaultPushClient(); 13 | }); 14 | 15 | after(() => { 16 | global.proxyServer.close(); 17 | global.apiServer.close(); 18 | global.pushClient.disconnect(); 19 | }); 20 | 21 | it('connect', (done) => { 22 | pushClient.on('connect', (data) => { 23 | expect(data.uid).to.be.undefined; 24 | done(); 25 | }); 26 | }); 27 | 28 | it('bind uid', (done) => { 29 | 30 | request({ 31 | url: apiUrl + '/api/uid/bind', 32 | method: "post", 33 | form: { 34 | pushId: pushClient.pushId, 35 | uid: 1 36 | } 37 | }, (error, response, body) => { 38 | expect(JSON.parse(body).code).to.be.equal("success"); 39 | pushClient.disconnect(); 40 | pushClient.connect(); 41 | pushClient.on('connect', (data) => { 42 | expect(data.uid).to.equal("1"); 43 | request({ 44 | url: apiUrl + '/api/uid/bind', 45 | method: "post", 46 | form: { 47 | pushId: pushClient.pushId, 48 | uid: 2 49 | } 50 | }, (error, response, body) => { 51 | expect(JSON.parse(body).code).to.be.equal("success"); 52 | pushClient.disconnect(); 53 | pushClient.connect(); 54 | pushClient.on('connect', (data) => { 55 | expect(data.uid).to.equal("2"); 56 | apiServer.uidStore.getPushIdByUid("1", (pushIds) => { 57 | expect(pushIds).to.not.contain(pushClient.pushId); 58 | apiServer.uidStore.getPushIdByUid("2", (pushIds) => { 59 | expect(pushIds).to.contain(pushClient.pushId); 60 | done(); 61 | }); 62 | }); 63 | }); 64 | }); 65 | }); 66 | }); 67 | 68 | }); 69 | 70 | it('unbind from client', (done) => { 71 | pushClient.unbindUid(); 72 | pushClient.disconnect(); 73 | pushClient.connect(); 74 | pushClient.on('connect', (data) => { 75 | expect(data.uid).to.be.undefined; 76 | pushClient.connect(); 77 | done(); 78 | }); 79 | }); 80 | 81 | it('unbind uid by pushId', (done) => { 82 | request({ 83 | url: apiUrl + '/api/uid/bind', 84 | method: "post", 85 | form: { 86 | pushId: pushClient.pushId, 87 | uid: 1 88 | } 89 | }, (error, response, body) => { 90 | request({ 91 | url: apiUrl + '/api/uid/remove', 92 | method: "post", 93 | form: { 94 | pushId: pushClient.pushId 95 | } 96 | }, (error, response, body) => { 97 | pushClient.disconnect(); 98 | pushClient.connect(); 99 | pushClient.on('connect', (data) => { 100 | expect(data.uid).to.be.undefined; 101 | pushClient.connect(); 102 | done(); 103 | }); 104 | }); 105 | }); 106 | }); 107 | 108 | it('unbind uid by uid', (done) => { 109 | request({ 110 | url: apiUrl + '/api/uid/bind', 111 | method: "post", 112 | form: { 113 | pushId: pushClient.pushId, 114 | uid: 1 115 | } 116 | }, (error, response, body) => { 117 | request({ 118 | url: apiUrl + '/api/uid/remove', 119 | method: "post", 120 | form: { 121 | uid: 1 122 | } 123 | }, (error, response, body) => { 124 | pushClient.disconnect(); 125 | pushClient.connect(); 126 | pushClient.on('connect', (data) => { 127 | expect(data.uid).to.be.undefined; 128 | pushClient.connect(); 129 | done(); 130 | }); 131 | }); 132 | }); 133 | }); 134 | 135 | it('invalid param test', (done) => { 136 | request({ 137 | url: apiUrl + '/api/uid/remove', 138 | method: 'post', 139 | }, (error, response, body) => { 140 | let ret = JSON.parse(body); 141 | expect(ret.code).to.be.equal('error'); 142 | done(); 143 | }) 144 | }); 145 | 146 | }); 147 | -------------------------------------------------------------------------------- /push-server/test/umengProviderTest.js: -------------------------------------------------------------------------------- 1 | const defSetting = require('./defaultSetting'); 2 | const randomstring = require('randomstring'); 3 | 4 | describe('umengProviderTest', function() { 5 | 6 | before(function() { 7 | global.apiServer = defSetting.getDefaultApiServer(); 8 | global.apiUrl = defSetting.getDefaultApiUrl(); 9 | global.umengProvider = apiServer.umengProvider; 10 | }); 11 | 12 | after(function() { 13 | global.apiServer.close(); 14 | }); 15 | 16 | it('sendAll', function(done) { 17 | var notificationAll = { 18 | id: randomstring.generate(12), 19 | android: { 20 | title: "sendAll", 21 | message: "sendAll Msg" 22 | }, 23 | payload: { 24 | test: "wwwwqqqUmeng" 25 | } 26 | }; 27 | var timeToLive = 10000; 28 | umengProvider.sendAll(notificationAll, timeToLive, function() { 29 | done(); 30 | }); 31 | }); 32 | 33 | it('sendMany', function(done) { 34 | var notificationOne = { 35 | id: randomstring.generate(12), 36 | android: { 37 | title: "sendOne", 38 | message: "sendOne Msg" 39 | }, 40 | payload: { 41 | test: "wwwwqqq" 42 | } 43 | }; 44 | umengProvider.sendMany(notificationOne, [{ 45 | token: "AgDbBHsFqu5Me_ULpi8PJ71QY3KmiV3ZTKAeU4GbKZUL" 46 | }, { 47 | token: "AgDbBHsFqu5Me_ULpi8PJ71QY3KmiV3ZTKAeU4GbKZUL" 48 | }], 60 * 60 * 1000, function() { 49 | done(); 50 | }); 51 | }); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /push-server/test/unsubscribeTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var defSetting = require('./defaultSetting'); 5 | 6 | describe('unsubscribe test', function () { 7 | 8 | before(function () { 9 | global.proxyServer = defSetting.getDefaultProxyServer(); 10 | global.apiServer = defSetting.getDefaultApiServer(); 11 | global.apiUrl = defSetting.getDefaultApiUrl(); 12 | global.pushClient = defSetting.getDefaultPushClient(); 13 | }); 14 | 15 | after(function () { 16 | global.proxyServer.close(); 17 | global.apiServer.close(); 18 | global.pushClient.disconnect(); 19 | }); 20 | 21 | 22 | it('connect', function (done) { 23 | pushClient.on('connect', function (data) { 24 | expect(data.pushId).to.be.equal(pushClient.pushId); 25 | done(); 26 | }); 27 | 28 | }); 29 | 30 | 31 | it('push to topic', function (done) { 32 | var json = '{ "message":"ok"}'; 33 | 34 | var i = 0; 35 | pushClient.subscribeTopic("message"); 36 | pushClient.on('push', function (data) { 37 | expect(i++ == 0); 38 | expect(data.message).to.be.equal('ok'); 39 | pushClient.unsubscribeTopic("message"); 40 | 41 | request({ 42 | url: apiUrl + '/api/push', 43 | method: "post", 44 | form: { 45 | topic: 'message', 46 | json: json 47 | } 48 | }, (error, response, body) => { 49 | expect(JSON.parse(body).code).to.be.equal("success"); 50 | setTimeout(function () { 51 | done(); 52 | }, 100); 53 | }); 54 | 55 | }); 56 | 57 | request({ 58 | url: apiUrl + '/api/push', 59 | method: "post", 60 | form: { 61 | topic: 'message', 62 | json: json 63 | } 64 | }, (error, response, body) => { 65 | expect(JSON.parse(body).code).to.be.equal("success"); 66 | }); 67 | 68 | }); 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /push-server/test/versionCompareTest.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var versionCompare = require('../lib/util/versionCompare'); 5 | 6 | describe('notification', function() { 7 | 8 | 9 | it('test1', function(done) { 10 | expect(versionCompare('1.5', '1.5.1') < 0).to.be.true; 11 | expect(versionCompare('1.5.1', '1.5') > 0).to.be.true; 12 | done(); 13 | }); 14 | 15 | it('test2', function(done) { 16 | expect(versionCompare('2.3.1', '2.4') < 0).to.be.true; 17 | expect(versionCompare('2.4', '2.3.1') > 0).to.be.true; 18 | done(); 19 | }); 20 | 21 | it('test3', function(done) { 22 | expect(versionCompare('2.3.5', '2.3.11') < 0).to.be.true; 23 | expect(versionCompare('2.3.11', '2.3.5') > 0).to.be.true; 24 | done(); 25 | }); 26 | 27 | it('test4', function(done) { 28 | expect(versionCompare('1.5', '1.5.0') < 0).to.be.true; 29 | expect(versionCompare('1.5.0', '1.5') > 0).to.be.true; 30 | done(); 31 | }); 32 | 33 | it('test4', function(done) { 34 | expect(versionCompare('1.6', '1.5.0') > 0).to.be.true; 35 | expect(versionCompare('1.5.0', '1.6') < 0).to.be.true; 36 | done(); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /push-server/test/xiaomiProviderTest.js: -------------------------------------------------------------------------------- 1 | var defSetting = require('./defaultSetting'); 2 | 3 | describe('xiaomi test', function () { 4 | 5 | before(function () { 6 | global.apiServer = defSetting.getDefaultApiServer(); 7 | global.apiUrl = defSetting.getDefaultApiUrl(); 8 | global.xiaomiProvider = apiServer.xiaomiProvider; 9 | }); 10 | 11 | after(function () { 12 | global.apiServer.close(); 13 | }); 14 | 15 | it('sendAll', function (done) { 16 | var notificationAll = { 17 | android: {title: "sendAll", message: "sendAll Msg", payload: {test: "wwwwqqq"}} 18 | }; 19 | var timeToLive = 10000; 20 | xiaomiProvider.sendAll(notificationAll, timeToLive, function () { 21 | done(); 22 | }); 23 | }); 24 | 25 | it('sendMany', function (done) { 26 | var notificationOne = { 27 | android: {title: "sendOne", message: "sendOne Msg", payload: {test: "wwwwqqq"}} 28 | }; 29 | xiaomiProvider.sendMany(notificationOne, [{token: "mzxxxzoRCz/Mazl83rjqph2fXSlxqaJ7hy/rnqEeMjo="}, {token: "mQO6QyoRCz/Mazl83rjqph2fXSlxqaJ7hy/rnqEeMjo="}], 60 * 60 * 1000, function () { 30 | done(); 31 | }); 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /readmes/arrive-rate.md: -------------------------------------------------------------------------------- 1 | # 离线通知送达率 2 | 3 | 计算socket.io通道,android的送达率方法 4 | 5 | ## 1.指定单个/多个设备的送达率 6 | 7 | 分子: 收到通知, 客户端回包, +1 8 | 9 | 分母: 调用推送api的时候,所传的设备id数/所传uid对应的设备id数 10 | 11 | 此项指标与业务相关度非常大, 因为很多设备会卸载app, 这些设备必然会收不到推送, 而服务器无法清除这些已经卸载的无效设备. 12 | 随着app寿命增加, 无效设备会越来越多, 送达率会越来越低, 当然这个可以通过对设备id设一个过期时间来解决, 不过目前我们并没有做. 13 | 此项送达率也跟推送目标有关,比如对一个主播的所有粉丝发推送, 送达率, 跟其僵尸粉数量也有关系. 14 | 15 | ## 2.全网离线推送送达率 16 | 17 | 分子: 收到通知, 客户端回包, +1 18 | 19 | 分母: 当前全网安卓在线数 - 华为/小米在线数 + TTL内连接到proxy的设备数 -------------------------------------------------------------------------------- /readmes/bench-mark.md: -------------------------------------------------------------------------------- 1 | ##性能测试 2 | 3 | #####测试机器 4 | CPU Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz (n核,24线程) 5 | 6 | 内存 16G 7 | 8 | 网卡 1000Mb 9 | 10 | #####进程部署 11 | push-server -c 12 启动12个进程 12 | 13 | nginx 代理 14 | ``` 15 | upstream io_proxy 16 | { 17 | #ip_hash; 18 | keepalive 32; 19 | server 127.0.0.1:10001 max_fails=2 fail_timeout=10s; 20 | server 127.0.0.1:10002 max_fails=2 fail_timeout=10s; 21 | server 127.0.0.1:10003 max_fails=2 fail_timeout=10s; 22 | server 127.0.0.1:10004 max_fails=2 fail_timeout=10s; 23 | server 127.0.0.1:10005 max_fails=2 fail_timeout=10s; 24 | server 127.0.0.1:10006 max_fails=2 fail_timeout=10s; 25 | server 127.0.0.1:10007 max_fails=2 fail_timeout=10s; 26 | server 127.0.0.1:10008 max_fails=2 fail_timeout=10s; 27 | server 127.0.0.1:10009 max_fails=2 fail_timeout=10s; 28 | server 127.0.0.1:10010 max_fails=2 fail_timeout=10s; 29 | server 127.0.0.1:10011 max_fails=2 fail_timeout=10s; 30 | server 127.0.0.1:10012 max_fails=2 fail_timeout=10s; 31 | } 32 | 33 | upstream api_proxy 34 | { 35 | #ip_hash; 36 | keepalive 32; 37 | server 127.0.0.1:11001 max_fails=2 fail_timeout=10s; 38 | server 127.0.0.1:11002 max_fails=2 fail_timeout=10s; 39 | server 127.0.0.1:11003 max_fails=2 fail_timeout=10s; 40 | server 127.0.0.1:11004 max_fails=2 fail_timeout=10s; 41 | server 127.0.0.1:11005 max_fails=2 fail_timeout=10s; 42 | server 127.0.0.1:11006 max_fails=2 fail_timeout=10s; 43 | server 127.0.0.1:11007 max_fails=2 fail_timeout=10s; 44 | server 127.0.0.1:11008 max_fails=2 fail_timeout=10s; 45 | server 127.0.0.1:11009 max_fails=2 fail_timeout=10s; 46 | server 127.0.0.1:11010 max_fails=2 fail_timeout=10s; 47 | server 127.0.0.1:11011 max_fails=2 fail_timeout=10s; 48 | server 127.0.0.1:11012 max_fails=2 fail_timeout=10s; 49 | } 50 | ``` 51 | 52 | 3台压测机器每台发起 10000连接 53 | ``` 54 | bin/bench-start-client -w 20 -a 10000 -c 1000 http://wsbench.yy.com 55 | ``` 56 | 57 | #####push topic结果 58 | ``` 59 | bin/bench-api -b 1000 -c 50 http://wsbench.yy.com:8088 60 | ``` 61 | 机器流量 920Mb 62 | 63 | 每秒发包量 11.8w 64 | 65 | load average 12.4 66 | 67 | 68 | -------------------------------------------------------------------------------- /readmes/broadcast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuduo/socket.io-push/3771dc3e9a6c657da1293e5ed30a2d8d3df07de7/readmes/broadcast.gif -------------------------------------------------------------------------------- /readmes/notification-keng.md: -------------------------------------------------------------------------------- 1 | 手机推送系统中的坑和解决方法,这是我在做一个[推送系统](https://github.com/xuduo/socket.io-push)时遇到的问题, 分享下, 希望对大家有帮助 2 | 3 | ## IOS APNS推送 4 | 5 | APNS推送, 需要自己保存ios上报的token, APNS本身不提供全网推送接口, 如果要做一次全网推送, 需要从数据库中查出所有token, 逐个推送. 对于一个用户量较高的APP, 需要存储的token数, 可能在百万或者千万级. 6 | 7 | 1. **国内到APNS服务器网络问题** 8 | 我们项目中, 从国内某机房, 做一次6w的批量推送, 需要10~15分钟. 增加到APNS的连接数, 也无法提升速度. 9 | **解决方法:** 把APNS调用服务部署到了香港机房, 同样一次6w的批量推送, 10秒左右就完成了, 速度提升近100倍. 可见国内某些机房到APNS服务器, 网络有很大问题. 10 | 11 | 2. **token相关** 12 | 1. 用户如果不允许通知权限, 是无法获取token的. 用户卸载APP, 或者禁用通知, 一般来说APP也无法得知. 13 | 2. 根据IOS版本的不同, 卸载重装APP, token可能变, 也可能不变. 用户升级/重装操作系统, 也可能会变. 所以每次启动APP, 都需要重新上报token. 14 | 3. 调用APNS推送, 对于已经卸载APP或者禁用通知的用户, APNS是不会有特殊返回值的. 15 | 16 | 3. **APNS调用报错: invalid token (8)** 17 | token非法, 最常见的产生原因 : ios开发, 测试, 企业版内测, 提交APPSTORE都有不同的bundleId, 而每个bundleId都对应不同的推送证书, 如果token存储的时候没有区分bundleId, 调用时就会产生错误8. 错误8太多, APNS有可能会断掉推送的http2长连接, 导致推送**失败率增加**. 18 | 19 | **解决方法:** 记录每个token对应的bundleId. 可以做到无论任bundleId的版本, 连正式/测试, 都可以收到APNS推送. 20 | 21 | 4. **发送用户看不到的通知** 22 | 23 | 如果调用APNS的时候, 不传alert等, 只传payload, 这种推送, 用户是看不到的, 但APP内会收到回调. 24 | 25 | 可以用于测试全网推送速度, 我们全网推送速度优化, 就是用这种方法来测试的. 26 | 27 | 5. **APP在前台时候, 无法弹出系统通知, 但APP内可以收到回调** 28 | 29 | 这是IOS的默认行为,APP能收到回调的前提同样是, 用户开了通知权限. 30 | 31 | 6. **APNS不会返回结果说用户有没有收到, 也不会告诉你用户禁用了通知** 32 | 33 | 错误码我们只遇到过 error(8), 见上. error(10) 系统维护中. 34 | 35 | 7. **苹果推送证书只有一年有效期, 到期前需要重新生成** 36 | 37 |    新版HTTP2接口, 用的证书, 永不过期 38 |   39 | ## 安卓后台保活 40 | 41 | 1. **对于国内大部分手机厂商** 42 | 43 | 无法实现后台保活, 各种歪门邪道已经过时了. 很多操作系统默认的配置(如华为), 切到后台, 直接杀掉, 或者进程断网. 非用户点击触发拉起app, 均返回没有权限. 44 | 45 | 为啥微信可以? 因为在系统厂商默认白名单, 可以尝试把你APP包名改成微信的, 装在小米手机上, 小米系统直接帮你把APP图标都换成微信了. 等你APP也做成微信级别, 也有这种待遇了. 46 | 47 | **解决方法:** 小米华为手机, 接入厂商提供的官方推送系统. 其它系统, 如果用户投诉, 尝试忽悠用户开下后台运行权限. 48 | 49 | 2. **对于安卓原生或小厂** 50 | 51 | 可以保证进程不被系统回收, 也不会因为用户从进程列表里划一下杀掉, 算是利用了安卓的一个漏洞, 6.0依然可以,(如google nexus, 三星系列手机) 52 | 53 | 实现方法参见 https://github.com/xuduo/socket.io-push-android/blob/master/android-push-sdk/src/main/java/com/yy/httpproxy/service/ForegroundService.java 54 | 55 | starForeground再stopForeground, 进程还是forground级别, 但通知栏那个foreground级别常驻的东西不会弹出来. 56 | 57 | 3. **友盟推送** 58 | 59 |   友盟有互相拉起的机制, 大概可以提升50%以上的后台推送设备数. 60 |   然而国产主流厂商(如OPPO,VIVO), 默认配置下依然是不能互相拉起. 61 | 62 | ## 小米/华为推送 63 | 64 | 1. **小米提供多包名支持, 华为不支持** 65 | 66 | 华为手机如需要多包名支持, 只能申请多个APPID. 67 | 68 | 2. **不提供环境隔离, 都提供全网推送接口, 需要特别小心, 不要在往测试环境发的到了正式环境** 69 | 70 | 那就有点尴尬了. 71 | 72 | **解决方法:** 申请多个APPID, 测试正式环境用不同Android包名. 73 | 74 | 3. **timeToLive参数不要设置太长, 建议7天以内** 75 | 76 | 小米文档说只支持7天, 但是传个一年, 似乎也没太大问题. 77 | 78 | 华为传太长, 接口直接就报错了. 79 | 80 | 4. **华为推送, 无论客户端服务端, 都非常反人类** 81 | 82 | 谁用谁知道. 83 | 84 | 某些华为系统版本, 还有bug, 可能收不到推送, 或者点击出现问题. 联系过华为的技术人员, 说只能升级操作系统解决. 85 | 86 | 5. **小米有非常详细的送达, 点击统计, 华为虽然有, 但是基本没法看** 87 | 88 | 6. **调用小米/华为接口成功率大概在98%~99%** 89 | 90 | 根据错误类型, 进行重试. 91 | 92 | 7. **小米可以通过服务器API接口控制, APP在前台时, 是否弹系统通知栏. 华为无法实现** 93 | -------------------------------------------------------------------------------- /readmes/notification.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuduo/socket.io-push/3771dc3e9a6c657da1293e5ed30a2d8d3df07de7/readmes/notification.gif -------------------------------------------------------------------------------- /readmes/topic.md: -------------------------------------------------------------------------------- 1 | ## topic概念 应用于如直播APP的直播间 2 | 3 | 1. **性能** 4 | 5 | 可以支持20W以上用户在同一个topic 6 | 7 | 2. **对topic发送广播** 8 | 9 | 见API文档 /api/topicOnline 10 | 11 | 12 | 3. **启用在线查询接口** 13 | 14 | ``` 15 | //config-proxy.js 16 | 17 | config.topicOnlineFilter = { 18 | chatRoom: 'devices', 19 | drawTopic: 'count' 20 | }; 21 | //在线统计功能, 以chatRoom开头的topic会进行统计在线, 并提供查询接口 22 | // devices -- 统计设备列表 count -- 只统计总数 23 | // 对于同时在线特别大的频道不建议统计设备列表 24 | ``` 25 | 26 | 4. **topic在线查询接口** 27 | 28 | 见API文档 /api/topicOnline 29 | -------------------------------------------------------------------------------- /socket.io-push-redis/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | local.properties 3 | *.iml 4 | node_modules 5 | *.log 6 | dump.rdb 7 | 8 | -------------------------------------------------------------------------------- /socket.io-push-redis/.npmignore: -------------------------------------------------------------------------------- 1 | log 2 | test/log 3 | node_modules 4 | -------------------------------------------------------------------------------- /socket.io-push-redis/config-log.js: -------------------------------------------------------------------------------- 1 | let config = {}; 2 | //输出的最低日志级别 debug < info < warn < error, 默认info 3 | config.level = "debug"; 4 | 5 | //可选 简单日志文件配置,配合formatter和timestamp可以接入ELK 6 | //config.filename = 'log/history.log'; 7 | 8 | //可选 轮转日志文件配置 9 | config.rotate_dir = 'log'; 10 | 11 | 12 | //是否输出到console, 默认不输出 13 | config.console = true; 14 | 15 | module.exports = config; 16 | -------------------------------------------------------------------------------- /socket.io-push-redis/emitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var parser = require('socket.io-parser'); 6 | var hasBin = require('has-binary'); 7 | var debug = require('debug')('socket.io-emitter'); 8 | 9 | /** 10 | * Module exports. 11 | */ 12 | 13 | module.exports = Emitter; 14 | 15 | /** 16 | * Flags. 17 | * 18 | * @api public 19 | */ 20 | 21 | var flags = [ 22 | 'json', 23 | 'volatile', 24 | 'broadcast' 25 | ]; 26 | 27 | /** 28 | * uid for emitter 29 | * 30 | * @api private 31 | */ 32 | 33 | var uid = 'emitter'; 34 | 35 | /** 36 | * Socket.IO redis based emitter. 37 | * 38 | * @param {Object} redis client (optional) 39 | * @param {Object} options 40 | * @api public 41 | */ 42 | 43 | function Emitter(redis, opts) { 44 | if (!(this instanceof Emitter)) return new Emitter(redis, opts); 45 | opts = opts || {}; 46 | 47 | this.redis = redis; 48 | this.prefix = (opts.key || 'io'); 49 | 50 | this._rooms = []; 51 | this._flags = {}; 52 | } 53 | 54 | /** 55 | * Apply flags from `Socket`. 56 | */ 57 | 58 | flags.forEach(function(flag) { 59 | Emitter.prototype.__defineGetter__(flag, function() { 60 | debug('flag %s on', flag); 61 | this._flags[flag] = true; 62 | return this; 63 | }); 64 | }); 65 | 66 | /** 67 | * Limit emission to a certain `room`. 68 | * 69 | * @param {String} room 70 | */ 71 | 72 | Emitter.prototype.in = 73 | Emitter.prototype.to = function(room) { 74 | this._rooms.push(room); 75 | return this; 76 | }; 77 | 78 | /** 79 | * Limit emission to certain `namespace`. 80 | * 81 | * @param {String} namespace 82 | */ 83 | 84 | Emitter.prototype.of = function(nsp) { 85 | debug('nsp set to %s', nsp); 86 | this._flags.nsp = nsp; 87 | return this; 88 | }; 89 | 90 | /** 91 | * Send the packet. 92 | * 93 | * @api public 94 | */ 95 | 96 | Emitter.prototype.emit = function() { 97 | // packet 98 | var args = Array.prototype.slice.call(arguments); 99 | 100 | var msg = JSON.stringify(args); 101 | // publish 102 | if (this._rooms && this._rooms.length) { 103 | this._rooms.forEach((room) => { 104 | const chnRoom = this.prefix + "#" + room; 105 | this.redis.publish(chnRoom, msg); 106 | }); 107 | } else { 108 | this.redis.publish(this.prefix, msg); 109 | } 110 | 111 | // reset state 112 | this._rooms = []; 113 | this._flags = {}; 114 | 115 | return this; 116 | }; 117 | -------------------------------------------------------------------------------- /socket.io-push-redis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-push-redis", 3 | "version": "1.0.10", 4 | "description": "redis operation for socket.io-push, include adapter, emitter, read/write ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test " 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "www.github.com/xuduo/socket.io-push" 12 | }, 13 | "keywords": [ 14 | "redis", 15 | "socket.io-push" 16 | ], 17 | "author": "xuduo, rekii", 18 | "license": "ISC", 19 | "dependencies": { 20 | "ioredis": "^1.15.0", 21 | "redis-commands": "1.1.0", 22 | "winston-proxy": "^1.0.5", 23 | "uid2": "0.0.3", 24 | "msgpack-js": "0.3.0", 25 | "socket.io-adapter": "0.4.0", 26 | "async": "0.9.0", 27 | "socket.io-parser": "2.2.6", 28 | "has-binary": "0.1.7" 29 | }, 30 | "devDependencies": { 31 | "socket.io": "1.4.8", 32 | "socket.io-client": "1.4.8", 33 | "mocha": "*", 34 | "chai": "^3.5.0", 35 | "expect.js": "0.3.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /socket.io-push-redis/test.js: -------------------------------------------------------------------------------- 1 | // const IoRedis = require('ioredis'); 2 | // 3 | // const redis = new IoRedis(); 4 | // 5 | // function test_handle(){ 6 | // redis.call.apply(redis, ['set', 'mmtest', '111']); 7 | // } 8 | // 9 | // // test_handle(); 10 | // 11 | // console.log('hisdfsd'); 12 | 13 | const redis = require('./cluster.js')({write: [ 14 | {host:"127.0.0.1", port: 6379}, 15 | {host:"127.0.0.1", port: 6380}, 16 | {host:"127.0.0.1", port: 6380}]}); 17 | 18 | redis.hhincrby("conninfo-test", "eiwuqroesda1903", 1); 19 | redis.hhincrby("conninfo-test", "eiwuqroesda1904", 1); 20 | redis.hhincrby("conninfo-test", "eiwuqroesda1905", 1); 21 | redis.hhincrby("conninfo-test", "eiwuqroesda1906", 1); 22 | redis.hhincrby("conninfo-test", "eiwuqroesda1907", 1); 23 | redis.hhincrby("conninfo-test", "eiwuqroesda1908", 1); 24 | redis.hhincrby("conninfo-test", "eiwuqroesda1909", 1); 25 | redis.hhget("conninfo-test", "eiwuqroesda1909", (err, result) => { 26 | console.log('conninfo-test: eiwuq..1903', result); 27 | }); 28 | 29 | const ss = redis.hhscanStream("conninfo-test"); 30 | ss.on('data', function (resultKeys) { 31 | for (let i = 0; i < resultKeys.length; i++) { 32 | console.log(resultKeys[i]); 33 | // if (i % 2 == 0) { 34 | // callback(resultKeys[i]); 35 | // } 36 | } 37 | }); 38 | ss.on('end', function () { 39 | console.log("end"); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /socket.io-push-redis/test/adapterTest.js: -------------------------------------------------------------------------------- 1 | var http = require('http').Server; 2 | var io = require('socket.io'); 3 | var ioc = require('socket.io-client'); 4 | var expect = require('expect.js'); 5 | var redis = require('ioredis'); 6 | var adapter = require('../adapter.js'); 7 | 8 | describe('socket.io-redis', function() { 9 | 10 | it('broadcasts to rooms', function(done) { 11 | create(function(server1, client1) { 12 | create(function(server2, client2) { 13 | create(function(server3, client3) { 14 | create(function(server4, client4) { 15 | server1.on('connection', function(c1) { 16 | c1.join('woot'); 17 | }); 18 | 19 | server2.on('connection', function(c2) { 20 | // does not join, performs broadcast 21 | c2.on('do broadcast', function() { 22 | c2.broadcast.to('woot').emit('broadcast'); 23 | }); 24 | }); 25 | 26 | server3.on('connection', function(c3) { 27 | // does not join, signals broadcast 28 | client2.emit('do broadcast'); 29 | }); 30 | 31 | server4.on('connection', function(c4) { 32 | c4.join('woot'); 33 | }); 34 | 35 | client1.on('broadcast', function() { 36 | client1.disconnect(); 37 | client2.disconnect(); 38 | client3.disconnect(); 39 | client4.disconnect() 40 | setTimeout(done, 100); 41 | }); 42 | 43 | client2.on('broadcast', function() { 44 | throw new Error('Not in room'); 45 | }); 46 | 47 | client3.on('broadcast', function() { 48 | throw new Error('Not in room'); 49 | }); 50 | }); 51 | }); 52 | }); 53 | }); 54 | }); 55 | 56 | it('doesn\'t broadcast to left rooms', function(done) { 57 | create(function(server1, client1) { 58 | create(function(server2, client2) { 59 | create(function(server3, client3) { 60 | server1.on('connection', function(c1) { 61 | c1.join('woot'); 62 | c1.leave('woot'); 63 | }); 64 | 65 | server2.on('connection', function(c2) { 66 | c2.on('do broadcast', function() { 67 | c2.broadcast.to('woot').emit('broadcast'); 68 | 69 | setTimeout(function() { 70 | client1.disconnect(); 71 | client2.disconnect(); 72 | client3.disconnect(); 73 | done(); 74 | }, 100); 75 | }); 76 | }); 77 | 78 | server3.on('connection', function(c3) { 79 | client2.emit('do broadcast'); 80 | }); 81 | 82 | client1.on('broadcast', function() { 83 | throw new Error('Not in room'); 84 | }); 85 | }); 86 | }); 87 | }); 88 | }); 89 | 90 | it('deletes rooms upon disconnection', function(done) { 91 | create(function(server, client) { 92 | server.on('connection', function(c) { 93 | c.join('woot'); 94 | c.on('disconnect', function() { 95 | expect(c.adapter.sids[c.id]).to.be.empty(); 96 | expect(c.adapter.rooms).to.be.empty(); 97 | client.disconnect(); 98 | done(); 99 | }); 100 | c.disconnect(); 101 | }); 102 | }); 103 | }); 104 | 105 | // create a pair of socket.io server+client 106 | function create(nsp, fn) { 107 | var srv = http(); 108 | var sio = io(srv); 109 | sio.adapter(adapter({ 110 | pubClient: new RedisClient(), 111 | subClient: new RedisClient(true) 112 | })); 113 | srv.listen(function(err) { 114 | if (err) throw err; // abort tests 115 | if ('function' == typeof nsp) { 116 | fn = nsp; 117 | nsp = ''; 118 | } 119 | nsp = nsp || '/'; 120 | var addr = srv.address(); 121 | var url = 'http://localhost:' + addr.port + nsp; 122 | fn(sio.of(nsp), ioc(url)); 123 | }); 124 | } 125 | 126 | function RedisClient(isSub) { 127 | if (!(this instanceof RedisClient)) return new RedisClient(isSub); 128 | this.messageCallbacks = []; 129 | this.client = new redis(); 130 | if (isSub) { 131 | this.client.on("messageBuffer", (channel, message) => { 132 | this.messageCallbacks.forEach((callback) => { 133 | callback(channel, message); 134 | }); 135 | }); 136 | } 137 | } 138 | RedisClient.prototype.on = function(message, callback) { 139 | if (message == "message") { 140 | this.messageCallbacks.push(callback); 141 | } 142 | }; 143 | ["publish", "subscribe", "unsubscribe"].forEach((command) => { 144 | RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function(key, arg, callback) { 145 | return this.client.callBuffer.apply(this.client, [command].concat(arguments)); 146 | }; 147 | }) 148 | 149 | }); 150 | -------------------------------------------------------------------------------- /socket.io-push-redis/test/clusterTest.js: -------------------------------------------------------------------------------- 1 | //@todo 2 | -------------------------------------------------------------------------------- /socket.io-push-redis/test/emitterTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var redis = require('ioredis'); 3 | var io = require('socket.io'); 4 | var ioc = require('socket.io-client'); 5 | var redisAdapter = require('../adapter'); 6 | var http = require('http').Server; 7 | var ioe = require('../emitter'); 8 | 9 | function client(srv, nsp, opts){ 10 | if ('object' == typeof nsp) { 11 | opts = nsp; 12 | nsp = null; 13 | } 14 | var addr = srv.address(); 15 | if (!addr) addr = srv.listen().address(); 16 | var url = 'http://localhost:' + addr.port + (nsp || ''); 17 | return ioc(url, opts); 18 | } 19 | 20 | describe('emitter', function() { 21 | //ignore namespace test 22 | 23 | describe('emit to room', function(){ 24 | it('should be able to emit to a room', function(done){ 25 | var pub = new RedisClient(); 26 | var sub = new RedisClient(true); 27 | var srv = http(); 28 | var sio = io(srv, {adapter: redisAdapter({pubClient: pub, subClient: sub})}); 29 | 30 | var secondConnecting = false; 31 | srv.listen(function() { 32 | sio.on('connection', function(socket) { 33 | if (secondConnecting) { 34 | socket.join('exclusive room'); 35 | } else { 36 | secondConnecting = true; 37 | } 38 | 39 | socket.on('broadcast event', function(payload) { 40 | socket.emit('broadcast event', payload); 41 | }); 42 | }); 43 | }); 44 | 45 | var a = client(srv, { forceNew: true }); 46 | a.on('broadcast event', function(payload) { 47 | expect().fail(); 48 | }); 49 | 50 | var b; 51 | a.on('connect', function() { 52 | b = client(srv, { forceNew: true }); 53 | 54 | b.on('broadcast event', function(payload) { 55 | expect(payload).to.be('broadcast payload'); 56 | setTimeout(done, 1000); 57 | }); 58 | 59 | b.on('connect', function() { 60 | var emitter = ioe(new redis()); 61 | emitter.to('exclusive room').broadcast.emit('broadcast event', 'broadcast payload'); 62 | }); 63 | }); 64 | }); 65 | 66 | //ignore emitter by socket id, actually it is prevent by adapter 67 | }); 68 | 69 | function RedisClient(isSub){ 70 | if(! (this instanceof RedisClient)) return new RedisClient(isSub); 71 | this.messageCallbacks = []; 72 | this.client = new redis(); 73 | if(isSub){ 74 | this.client.on("messageBuffer", (channel, message) => { 75 | this.messageCallbacks.forEach((callback) => { 76 | callback(channel, message); 77 | }); 78 | }); 79 | } 80 | } 81 | RedisClient.prototype.on = function(message, callback){ 82 | if(message == "message"){ 83 | this.messageCallbacks.push(callback); 84 | } 85 | }; 86 | ["publish", "subscribe", "unsubscribe"].forEach((command) => { 87 | RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function(key, arg, callback){ 88 | return this.client.callBuffer.apply(this.client, [command].concat(arguments)); 89 | }; 90 | }) 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /socket.io-push-redis/test/utilTest.js: -------------------------------------------------------------------------------- 1 | var util = require('../util.js'); 2 | 3 | var chai = require('chai'); 4 | var expect = chai.expect; 5 | var IoRedis = require('ioredis'); 6 | 7 | describe('util', function() { 8 | 9 | 10 | it('getByHash size 0', function(done) { 11 | var array = [1]; 12 | expect(util.getByHash(array, 'abc')).to.equal(1); 13 | expect(util.getByHash(array, 'abcd')).to.equal(1); 14 | done(); 15 | }); 16 | 17 | it('getByHash size 10', function(done) { 18 | var array = [1, 2, 3, 4, 5, 6, 7, 8, 9]; 19 | expect(util.getByHash(array, 'abc')).to.equal(1); 20 | expect(util.getByHash(array, 'abc')).to.equal(1); 21 | expect(util.getByHash(array, 'abcdefaaaaaa')).to.equal(2); 22 | expect(util.getByHash(array, 'abcdefaaaaaa')).to.equal(2); 23 | expect(util.getByHash(array, 'abc22222')).to.equal(2); 24 | expect(util.getByHash(array, 'abc22222')).to.equal(2); 25 | expect(util.getByHash(array, 'defg')).to.equal(5); 26 | expect(util.getByHash(array, 'defg')).to.equal(5); 27 | done(); 28 | }); 29 | 30 | // it('scanHelper', function (done){ 31 | // var streamArr = []; 32 | // let redis = new IoRedis(6379); 33 | // [6379,6380,6381].forEach(function(port){ 34 | // let redis = new IoRedis(port); 35 | // redis.hset('scanHelperTest', ''+port, ""); 36 | // streamArr.push(redis.hscanStream('scanHelperTest', {})); 37 | // }); 38 | // var stream = util.scanHelper(streamArr); 39 | // var fields = []; 40 | // stream.on('data', function(result){ 41 | // fields.push(result[0]); 42 | // if(fields.length == 3){ 43 | // done(); 44 | // } 45 | // }); 46 | // stream.on('end', function(){ 47 | // }) 48 | // }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /socket.io-push-redis/util.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const Emitter = require('events').EventEmitter; 3 | 4 | module.exports = { 5 | getByHash: function (array, key) { 6 | if (!array || array.length == 0) { 7 | return; 8 | } 9 | if (array.length == 1) { 10 | return array[0]; 11 | } 12 | return array[module.exports.hash(key) % array.length]; 13 | }, 14 | hash: function (key) { 15 | let hash = 0; 16 | if (!key || key.length == 0) return 0; 17 | for (let i = 0; i < key.length; i++) { 18 | const char = key.charCodeAt(i); 19 | hash = ((hash << 5) - hash) + char; 20 | hash = hash & hash; // Convert to 32bit integer 21 | } 22 | return Math.abs(hash); 23 | }, 24 | scanHelper: ScanHelper 25 | 26 | }; 27 | 28 | function ScanHelper(streamArr) { 29 | if (!(this instanceof ScanHelper)) return new ScanHelper(streamArr); 30 | this.streams = streamArr; 31 | let streamCount = 0; 32 | this.streams.forEach((stream) => { 33 | stream.on('data', (result) => { 34 | this.emit('data', result); 35 | }); 36 | stream.on('end', (result) => { 37 | streamCount++; 38 | if (streamCount == this.streams.length) { 39 | this.emit('end', result); 40 | } 41 | }); 42 | }); 43 | } 44 | util.inherits(ScanHelper, Emitter); 45 | 46 | -------------------------------------------------------------------------------- /winston-proxy/.npmignore: -------------------------------------------------------------------------------- 1 | log 2 | node_modules 3 | -------------------------------------------------------------------------------- /winston-proxy/config-log.js: -------------------------------------------------------------------------------- 1 | let config = {}; 2 | //输出的最低日志级别 debug < info < warn < error, 默认info 3 | config.level = "debug"; 4 | 5 | //简单日志文件配置,配合formatter和timestamp可以接入ELK 6 | config.filename = 'log/history.log'; 7 | 8 | //轮转日志文件配置 9 | config.rotate_dir = 'log'; 10 | 11 | config.formatter = function (args) { 12 | let jsonObj = {}; 13 | jsonObj.id = args.meta.id; 14 | jsonObj.timestamp = Date.now() / 1000 | 0; 15 | jsonObj.service = "me_push"; //不同项目由service区分,一般修改这里就可以, 只能用数字小写字母和下划线 16 | jsonObj.pid = process.pid; 17 | jsonObj.ip = "##CTL_IP##"; //电信IP,对于没有电信IP的修改成其他 18 | jsonObj.logtype = "event"; 19 | jsonObj.level = args.level; 20 | jsonObj.module = args.meta.module; 21 | jsonObj.msg = args.message; 22 | return JSON.stringify(jsonObj); 23 | }; 24 | 25 | //是否输出到console, 默认不输出 26 | config.console = true; 27 | 28 | module.exports = config; -------------------------------------------------------------------------------- /winston-proxy/index.js: -------------------------------------------------------------------------------- 1 | const winston = require('winston'); 2 | const rotatefile = require('winston-daily-rotate-file'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const mkdirp = require('mkdirp'); 6 | const uuid = require('node-uuid'); 7 | 8 | function readableFormatter(args) { 9 | return new Date().toLocaleString() + " " + workId + " " + 10 | args.level.substring(0, 1).toUpperCase() + "/" + args.meta.module + " " + (args.message ? args.message : ''); 11 | } 12 | 13 | let deleteOpStarted = false; 14 | 15 | function createFileRotateTransport(dir, level, formatter = readableFormatter) { 16 | mkdirp(dir, err => { 17 | if (err && err.code != 'EEXIST') { 18 | console.log('mkdir dir error, %s', dir); 19 | } 20 | }); 21 | let opt = { 22 | name: 'rotate-file-' + level, 23 | json: false, 24 | level: level, 25 | showLevel: false, 26 | datePattern: 'yyyy-MM-dd.log', 27 | filename: dir + '/' + level + '_', 28 | formatter: formatter 29 | }; 30 | const msPerDel = 24 * 60 * 60 * 1000; 31 | if (workId == 'master' && false == deleteOpStarted) { 32 | deleteOpStarted = true; 33 | deleteOutdatedLog(dir); 34 | setInterval(() => { 35 | deleteOutdatedLog(dir); 36 | }, msPerDel); 37 | } 38 | return new rotatefile(opt); 39 | } 40 | 41 | function createFileTransport(file, level, formatter = readableFormatter) { 42 | mkdirp(path.dirname(file), err => { 43 | if (err && err.code != 'EEXIST') { 44 | console.log('mkdir dir error, %s', path.dirname(file)); 45 | } 46 | }); 47 | let opt = { 48 | name: 'file-' + level, 49 | json: false, 50 | level: level, 51 | showLevel: false, 52 | filename: file, 53 | formatter: formatter 54 | }; 55 | 56 | fs.watchFile(file, { 57 | persistent: false, 58 | interval: 30000 59 | }, (curr, prev) => { 60 | if (curr.ino !== prev.ino) { 61 | console.log('ino %d -> %d', prev.ino, curr.ino); 62 | logger.remove(opt.name); 63 | logger.add(winston.transports.File, opt); 64 | } 65 | }); 66 | return new winston.transports.File(opt); 67 | } 68 | 69 | function createConsoleTransport(level, formatter = readableFormatter) { 70 | let opt = { 71 | name: 'console', 72 | json: false, 73 | level: level, 74 | showLevel: false, 75 | formatter: formatter, 76 | colorize: 'all', 77 | align: true 78 | }; 79 | return new winston.transports.Console(opt); 80 | } 81 | 82 | function createLogger(opts) { 83 | let transports = []; 84 | if (!opts || !opts.level) { 85 | return; 86 | } 87 | if (opts.rotate_dir) { 88 | transports.push(createFileRotateTransport(opts.rotate_dir, opts.level, opts.formatter)); 89 | transports.push(createFileRotateTransport(opts.rotate_dir, "error", opts.formatter)); 90 | } 91 | if (opts.filename) { 92 | transports.push(createFileTransport(opts.filename, opts.level, opts.formatter)) 93 | } 94 | if (opts.console) { 95 | transports.push(createConsoleTransport('debug')); 96 | } 97 | return new winston.Logger({ 98 | transports: transports 99 | }); 100 | } 101 | 102 | let logger; 103 | let workId; 104 | 105 | function LogProxy(moduleName) { 106 | this.module = moduleName; 107 | this.logger = logger; 108 | } 109 | 110 | ['debug', 'info', 'warn', 'error'].forEach(function(command) { 111 | LogProxy.prototype[command] = function(key, arg, callback) { 112 | if (this.logger) { 113 | const mainArguments = Array.prototype.slice.call(arguments); 114 | mainArguments.push({ 115 | id: uuid.v4(), 116 | module: this.module 117 | }); 118 | this.logger[command].apply(this, mainArguments); 119 | } 120 | } 121 | }); 122 | 123 | const deleteOutdatedLog = function(dir, days = 7) { 124 | const msPerDay = 24 * 60 * 60 * 1000; 125 | fs.readdir(dir, (err, files) => { 126 | if (err) { 127 | console.log('readdir error ', err); 128 | return; 129 | } 130 | let now = Date.now(); 131 | files.forEach((filename) => { 132 | try { 133 | let stat = fs.statSync(dir + '/' + filename); 134 | let time_diff = now - stat.mtime; 135 | if (time_diff > days * msPerDay) { 136 | console.log("delete log file ", filename); 137 | fs.unlinkSync(dir + '/' + filename); 138 | } 139 | } catch (ex) { 140 | console.log("delete file error ", ex); 141 | } 142 | }); 143 | }); 144 | }; 145 | 146 | function init() { 147 | let opts = {}; 148 | try { 149 | opts = require(process.cwd() + '/config-log'); 150 | } catch (ex) { 151 | console.log(ex); 152 | } 153 | opts.level = opts.level || 'debug'; 154 | try { 155 | const cluster = require('cluster'); 156 | if (cluster.isMater) { 157 | workId = 'master'; 158 | } else { 159 | workId = cluster.worker.id; 160 | workId = 'worker:' + (workId < 10 ? '0' + workId : workId); 161 | } 162 | } catch (ex) { 163 | workId = 'master'; 164 | } 165 | logger = createLogger(opts); 166 | } 167 | 168 | const Logger = function(moduleName) { 169 | if (typeof moduleName == 'string') { 170 | if (!logger) { 171 | init(); 172 | } 173 | return new LogProxy(moduleName); 174 | } 175 | }; 176 | 177 | module.exports = Logger; 178 | -------------------------------------------------------------------------------- /winston-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "winston-proxy", 3 | "version": "1.0.6", 4 | "description": "a self defined log moudle based on winston, support multi process and module names", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "mocha" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "www.github.com/xuduo/socket.io-push/winston-proxy" 15 | }, 16 | "keywords": [ 17 | "logging", 18 | "moudle" 19 | ], 20 | "author": "xuduo,libin", 21 | "license": "ISC", 22 | "dependencies": { 23 | "winston": "2.2.0", 24 | "winston-daily-rotate-file": "1.1.5", 25 | "node-uuid": "1.4.7", 26 | "mkdirp": "0.5.1" 27 | }, 28 | "devDependencies": { 29 | "expect.js": "0.3.1", 30 | "chai-spies": "^0.7.1", 31 | "mocha": "*", 32 | "chai": "^3.5.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /winston-proxy/test/logTest.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | 5 | describe('rotate log test', () => { 6 | 7 | before(done => { 8 | fs.access('log', err => { 9 | if (err) { 10 | fs.mkdirSync('./log'); 11 | } 12 | global.testfile = './log/file_to_be_del.log'; 13 | fs.closeSync(fs.openSync(testfile, 'w')); 14 | fs.utimesSync(testfile, 1451613600, 1451613600); //utime = mtime = '2016-01-01 10:00:00' 15 | done(); 16 | }); 17 | }); 18 | 19 | after(done => { 20 | fs.readdir('./log', (err, files) => { 21 | if (!err) { 22 | files.forEach((filename) => { 23 | fs.unlinkSync('./log/' + filename); 24 | }); 25 | fs.rmdirSync('./log'); 26 | } 27 | done(); 28 | }); 29 | }); 30 | 31 | it('auto delete', done => { 32 | require('../index.js')('autoDel'); 33 | setTimeout(() => { 34 | fs.access(testfile, err => { 35 | expect(err).to.be.ok; 36 | done(); 37 | }); 38 | }, 1000) 39 | }); 40 | 41 | it('log test', done => { 42 | let logger = require('../index.js')('MochaTest'); 43 | let errorMsg = '--- error msg, this should be logged and print'; 44 | let warnMsg = '--- warn msg, this should be logged and print'; 45 | let infoMsg = '--- info msg, this should be logged and print.'; 46 | let debugMsg = '--- debug msg, this should be logged and print'; 47 | logger.info(infoMsg); 48 | logger.debug(debugMsg); 49 | logger.warn(warnMsg); 50 | logger.error(errorMsg); 51 | fs.readdir('./log', (err, files) => { 52 | expect(err).to.not.be.ok; 53 | let date = new Date(); 54 | let dateString = date.getFullYear() + '-' + (date.getMonth() < 9 ? '0' : '' ) + (date.getMonth() + 1) + '-' + (date.getDate() < 10 ? '0' : '') + date.getDate(); 55 | errLogFile = './log/error_' + dateString + '.log'; 56 | debugFile = './log/debug_' + dateString + '.log'; 57 | filename = './log/history.log'; 58 | fs.readFile(errLogFile, (err, data) => { 59 | expect(err).to.not.be.ok; 60 | expect(data.toString()).to.string(errorMsg); 61 | fs.readFile(debugFile, (err, data) => { 62 | expect(err).to.not.be.ok; 63 | expect(data.toString()).to.string(debugMsg); 64 | expect(data.toString()).to.string(infoMsg); 65 | expect(data.toString()).to.string(warnMsg); 66 | fs.readFile(filename, (err, data) => { 67 | expect(data.toString()).to.string(errorMsg); 68 | expect(data.toString()).to.string(debugMsg); 69 | expect(data.toString()).to.string(infoMsg); 70 | expect(data.toString()).to.string(warnMsg); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | }); 76 | }); 77 | }); --------------------------------------------------------------------------------