├── .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 [](https://travis-ci.org/xuduo/socket.io-push) [](https://coveralls.io/github/xuduo/socket.io-push?branch=master&a=1)
2 | =======================
3 | 整合了小米,华为,友盟,苹果推送的统一解决方案
4 |
5 | 更有应用内超低延迟顺序(生产环境平均200MS以下)透传功能,支持websocket
6 |
7 | 
8 |
9 | 广播可支持同频道10w以上在线,每台前端(proxy)可支持5W以上长连接(取决于你的推送量)
10 |
11 | 
12 |
13 | 目前处于只修bug,不会更新新功能状态
14 |
15 | [视频介绍](http://www.bilibili.com/video/av8531451/)
16 |
17 | [](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 | *
10 | *
11 | * lexicographical: true compares each part of the version strings lexicographically instead of
12 | * naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than
13 | * "1.2".
14 | *
15 | *
16 | * zeroExtend: true changes the result if one version string has less parts than the other. In
17 | * this case the shorter string will be padded with "zero" parts instead of being considered smaller.
18 | *
19 | *
20 | * @returns {number|NaN}
21 | *
22 | * 0 if the versions are equal
23 | * a negative integer iff v1 < v2
24 | * a positive integer iff v1 > v2
25 | * NaN if either version string is in the wrong format
26 | *
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 | request
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 | IP
55 | Total
56 | IOS
57 | Android
58 | packetDrop
59 | packetDropThreshold
60 |
61 |
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 |
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 | });
--------------------------------------------------------------------------------