├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc └── api.md ├── examples ├── BatchPushExample.js ├── Conf.js.example ├── DeviceAsyncExamples.js ├── DeviceExample.js ├── PushAsyncExample.js ├── PushExample.js ├── ReportAsyncExample.js ├── ReportExample.js └── ScheduleExample.js ├── index.js ├── lib └── JPush │ ├── JPush.js │ ├── JPushAsync.js │ ├── JPushError.js │ ├── PushPayload.js │ ├── PushPayloadAsync.js │ ├── Request2.js │ └── util.js ├── package.json └── test ├── BaseTest.js.example ├── DeviceTest.js ├── Payload.tests.js └── Push.tests.js /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | name: npm-publish 4 | on: 5 | push: 6 | branches: 7 | - master # Change this to your default branch 8 | jobs: 9 | npm-publish: 10 | name: npm-publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@master 15 | - name: Set up Node.js 16 | uses: actions/setup-node@master 17 | with: 18 | node-version: 10.0.0 19 | registry-url: https://registry.npmjs.org/ 20 | - run: npm publish 21 | env: # More info about the environment variables in the README 22 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} # You need to set this in your repo settings 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node,macos 2 | 3 | ### macOS ### 4 | *.DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | ### Node ### 31 | # Logs 32 | logs 33 | *.log 34 | npm-debug.log* 35 | yarn-debug.log* 36 | yarn-error.log* 37 | 38 | # Runtime data 39 | pids 40 | *.pid 41 | *.seed 42 | *.pid.lock 43 | 44 | # Directory for instrumented libs generated by jscoverage/JSCover 45 | lib-cov 46 | 47 | # Coverage directory used by tools like istanbul 48 | coverage 49 | 50 | # nyc test coverage 51 | .nyc_output 52 | 53 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 54 | .grunt 55 | 56 | # Bower dependency directory (https://bower.io/) 57 | bower_components 58 | 59 | # node-waf configuration 60 | .lock-wscript 61 | 62 | # Compiled binary addons (http://nodejs.org/api/addons.html) 63 | build/Release 64 | 65 | # Dependency directories 66 | node_modules/ 67 | jspm_packages/ 68 | 69 | # Typescript v1 declaration files 70 | typings/ 71 | 72 | # Optional npm cache directory 73 | .npm 74 | 75 | # Optional eslint cache 76 | .eslintcache 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # dotenv environment variables file 88 | .env 89 | 90 | 91 | # End of https://www.gitignore.io/api/node,macos 92 | 93 | \.vscode/ 94 | 95 | \.idea/ 96 | package-lock.json 97 | /examples/Conf.js 98 | /test/BaseTest.js 99 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "4" 5 | - "6" 6 | - "node" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | JPush's officially supported Node.js client library for accessing JPush APIs. 294 | Copyright (C) 2013 极光推送 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JPush API client library for Node.js 2 | 3 | 本 SDK 提供 JPush 服务端接口的 Node 封装,与 JPush Rest API 组件通信。使用时引用该模块即可,可参考附带 Demo 学习使用方法。 4 | 5 | > Node >= 7.6(async/await 语法支持),若 node 版本小于 7.6 请使用 [legacy 分支的代码](https://github.com/jpush/jpush-api-nodejs-client/tree/legacy) 6 | 7 | [REST API 文档](http://docs.jiguang.cn/jpush/server/push/server_overview/) 8 | 9 | [NodeJS API 文档](https://github.com/jpush/jpush-api-nodejs-client/blob/master/doc/api.md) 10 | 11 | ## Install 12 | ``` 13 | npm install jpush-async 14 | #or 15 | { 16 | "dependencies": { 17 | "jpush-async": "*" 18 | } 19 | } 20 | ``` 21 | 22 | ## Example 23 | ### Quick start 24 | 此 Demo 展示如何使用 Node lib 向所有用户推送通知。 25 | ``` js 26 | var JPush = require("../lib/JPush/JPushAsync.js") 27 | var client = JPush.buildClient('your appKey', 'your masterSecret') 28 | 29 | //easy push 30 | client.push().setPlatform(JPush.ALL) 31 | .setAudience(JPush.ALL) 32 | .setNotification('Hi, JPush', JPush.ios('ios alert', 'happy', 5)) 33 | .send() 34 | .then(function(result) { 35 | console.log(result) 36 | }).catch(function(err) { 37 | console.log(err) 38 | }) 39 | ``` 40 | 41 | ### Expert mode(高级版) 42 | 43 | ```js 44 | client.push().setPlatform('ios', 'android') 45 | .setAudience(JPush.tag('555', '666'), JPush.alias('666,777')) 46 | .setNotification('Hi, JPush', JPush.ios('ios alert'), JPush.android('android alert', null, 1)) 47 | .setMessage('msg content') 48 | .setOptions(null, 60) 49 | .send() 50 | .then(function(result) { 51 | console.log(result) 52 | }).catch(function(err) { 53 | console.log(err) 54 | }); 55 | ``` 56 | 57 | 关于 Payload 对象的方法,参考[详细 API 文档](https://github.com/jpush/jpush-api-nodejs-client/blob/master/doc/api.md)。 58 | 59 | ### 关闭 Log 60 | 61 | ```js 62 | // 在构建 JPushClient 对象的时候, 指定 isDebug 参数。 63 | var client = JPush.buildClient({ 64 | appKey:'your appKey', 65 | masterSecret:'your masterSecret', 66 | isDebug:false 67 | }); 68 | // or 69 | var client = JPush.buildClient('your appKey', 'your masterSecret', null, false); 70 | ``` 71 | 72 | > 目前使用了 debug 模块来控制日志输出,若要查看 JPush 的相关日志信息,请先配置 DEBUG 环境变量 'jpush'。 73 | -------------------------------------------------------------------------------- /doc/api.md: -------------------------------------------------------------------------------- 1 | # JPush Node.js client api doc 2 | 3 | ## 整体描述 4 | 本 SDK 提供 JPush 服务端接口的 Node 封装,与 [JPush Rest API][1] 组件通信。使用时引用该模块即可,可参考附带 Demo 学习使用方法。 5 | 6 | ## 公共类型定义 7 | 8 | 函数: 9 | 10 | |函数|说明| 11 | |-----|-----| 12 | |tag|创建 audience 的 tag 属性,使用方法请参考 Audience 示例。| 13 | |tag_and|创建 audience 的 tag_and 属性,使用方法请参考 Audience 示例。| 14 | |tag_not|创建 audience 的 tag_not 属性,使用方法请参考 Audience 示例。| 15 | |alias|创建 audience 的 alias 属性,使用方法请参考 Audience 示例。| 16 | |registration_id|创建 audience 的 reigistration 属性,使用方法请参考 Audience 示例。| 17 | |segment|创建 audience 的 segment 属性,使用方法请参考 Audience 示例。| 18 | |abtest|创建 audience 的 abtest 属性,使用方法请参考 Audience 示例。| 19 | |ios|创建 iOS Notification,接收 5 个参数:alert, sound, badge, contentAvailable, extras。| 20 | |android|创建 Android Notification。| 21 | |winphone|创建 WinPhone Notification。| 22 | 23 | **ios(alert,sound,badge,contentAvailable,extras)** 24 | 25 | 创建 iOS Notification 26 | 27 | |参数|类型|必须|默认值|说明| 28 | |-----|-----|-----|-----|-----| 29 | |alert|string|是|无|通知内容。| 30 | |sound|string|否|无|如果为 null,则此消息无声音提示;有此字段,如果找到了指定的声音就播放该声音,否则播放默认声音, 如果此字段为空字符串(''),iOS 7 为默认声音,iOS 8 及以上系统为无声音。(消息) 说明:JPush 官方 API Library (SDK) 会默认填充声音字段。提供另外的方法关闭声音。| 31 | |badge|int|否|无|如果为 null,表示不改变角标数字;否则把角标数字改为指定的数字;为 0 表示清除。| 32 | |contentAvailable|boolean|否|无|推送的时候携带 "content-available":true 说明是 Background Remote Notification,如果不携带此字段则是普通的 Remote Notification。详情参考:[Background Remote Notification](http://docs.jiguang.cn/jpush/client/iOS/ios_new_fetures/#ios-7-background-remote-notification)| 33 | |extras|object|否|无|自定义 key / value 信息,以供业务使用。| 34 | |category|string|否|无|iOS 8 开始支持,即 APNs payload 中的 'category' 字段。| 35 | |mutableContent|boolean|否|无|推送的时候携带"mutable-content":true 说明是支持 iOS 10 的 UNNotificationServiceExtension,如果不携带此字段则是普通的 Remote Notification。详情参考:[UNNotificationServiceExtension](https://developer.apple.com/reference/usernotifications/unnotificationserviceextension)| 36 | 37 | **android(alert, title, builder_id, extras, priority, category, style, value, alertType)** 38 | 39 | |参数|类型|必须|默认值|说明| 40 | |-----|-----|-----|-----|-----| 41 | |alert|string|是|无|通知内容。| 42 | |title|string|否|无|通知标题。| 43 | |builder_id|int|否|无|Android SDK 可设置通知栏样式,这里根据样式 ID 来指定该使用哪套样式。| 44 | |extras|object|否|无|自定义 key / value 信息,以供业务使用。| 45 | |priority|int|否|无|通知栏展示优先级,默认为0,范围为 -2~2 ,其他值将会被忽略而采用默认。| 46 | |category|string|否|无|通知栏条目过滤或排序,完全依赖 rom 厂商对 category 的处理策略。| 47 | |style|int|否|无|通知栏样式类型,默认为0,还有1,2,3可选,用来指定选择哪种通知栏样式,其他值无效。有三种可选分别为 bigText=1,Inbox=2,bigPicture=3。| 48 | |value|object|否|无|当 style = 1, 为大文本通知栏样式,类型为 string,内容会被通知栏以大文本的形式展示出来。支持 API 16 以上的 rom;
当 style = 2,为文本条目通知栏样式,类型为 json 对象,json 的每个 key 对应的 value 会被当作文本条目逐条展示。支持 API 16 以上的 rom;
当 style = 3,为大图片通知栏样式,类型为 string,可以是网络图片 url,或本地图片的 path,目前支持.jpg和.png后缀的图片。图片内容会被通知栏以大图片的形式展示出来。如果是 http/https 的url,会自动下载;如果要指定开发者准备的本地图片就填 sdcard 的相对路径。支持 API 16以上的 rom。| 49 | |alertTYpe|int|否|-1|可选范围为 -1 ~ 7 ,对应 Notification.DEFAULT_ALL = -1 或者 Notification.DEFAULT_SOUND = 1, Notification.DEFAULT_VIBRATE = 2, Notification.DEFAULT_LIGHTS = 4 的任意 “or” 组合。默认按照 -1 处理。| 50 | 51 | ***注:对于Android notification 其他参数,比如:uri_activity,uri_action等,可使用如下方式添加:*** 52 | ``` 53 | // 创建android notification对象 54 | var androidObj = JPush.android('Hi,JPush', 'JPush Title', 1, {'key':'value'}); 55 | // 新增参数:uri_activity, uri_action 56 | androidObj.android['uri_activity'] = 'xxx'; 57 | androidObj.android['uri_action'] = 'xxx'; 58 | ``` 59 | 60 | 61 | 62 | 63 | **winphone(alert, title, openPage, extras)** 64 | 65 | |参数|类型|必须|默认值|说明| 66 | |-----|-----|-----|-----|-----| 67 | |alert|string|是|无|通知内容。| 68 | |title|string|否|无|通知标题。| 69 | |openPage|string|否|无|点击打开的页面。会填充到推送信息的 param 字段上,表示由哪个 App 页面打开该通知。可不填,则由默认的首页打开。| 70 | |extras|object|否|无|自定义 key / value 信息,以供业务使用。| 71 | 72 | 常量 73 | 74 | |字段名|说明| 75 | |-----|-----| 76 | |ALL|设置 audience 与 platform 为推送全部对象时使用。| 77 | |DISABLE_SOUND|设置 iOS Notification 不需要声音时使用。| 78 | |DISABLE_BADGE|设置 iOS Notification 不需要角标时使用。| 79 | 80 | 类 81 | 82 | |类名|说明| 83 | |-----|-----| 84 | |APIConnectionError|网络原因使请求失败时候抛出的异常。| 85 | |APIRequestError|推送失败时抛出的异常,携带服务器返回的失败提示。| 86 | |InvalidArgumentError|参数错误抛出的异常。| 87 | |JPushClient|推送客户端,是所有 API 调用的对外接口。| 88 | |PushPayload|推送对象。| 89 | 90 | ---------- 91 | 92 | ### JPush 93 | 94 | JPush Client API,调用该类的实例执行对 JPush API 的请求。 95 | 96 | 构建方法 97 | 98 | **JPush.buildClient(appkey, masterSecret, retryTimes)** 99 | 100 | |参数|类型|必须|默认值|说明| 101 | |-----|-----|-----|-----|-----| 102 | |appKey|string|是|无|开发者 AppKey,可从 JPush Portal 获取| 103 | |masterSecret|string|是|无|开发者 masterSecret,可从 JPush Portal 获取| 104 | |retryTimes|int|否|5|请求失败重试次数| 105 | |isGroup|bool|否|false|是否是群组发送,一个实例只能为群组或不是群组,无法切换| 106 | 107 | 该类包含的接口有: 108 | 109 | #### push() 110 | 111 | 创建推送对象 PushPayload,每次推送创建一个推送对象。 112 | 113 | #### sendPush(payload) 114 | 115 | 推送 payload 对象到 JPush 服务器上,该方法由 PushPayload.send() 调用,建议不要主动调用此函数。 116 | 117 | |参数|类型|必须|默认值|说明| 118 | |-----|-----|-----|-----|-----| 119 | |payload|PushPayload|是|无|需要推送的 PushPayload 对象。| 120 | 121 | #### getReportReceiveds(msg_ids) 122 | 123 | 获取对应 msg_id 的返回报告,多个 msg_id 用逗号连接起来,中间不要有空格。 124 | 125 | |参数|类型|必须|默认值|说明| 126 | |-----|-----|-----|-----|-----| 127 | |msg_ids|string|是|无|需要获取的 msg_id,多个 msg_id用逗号连接起来,中间不要有空格。| 128 | 129 | #### setSchedule(payload) 130 | 设置指定的定时任务,该方法由 PushPayload.setSchedule() 调用,不需要主动调用此函数。 131 | 132 | |参数|类型|必须|默认值|说明| 133 | |-----|-----|-----|-----|-----| 134 | |payload|PushPayload|是|无|需要设置的 PushPayload 对象。| 135 | 136 | #### updateSchedule(scheduleID, payload) 137 | 更新指定的定时任务,该方法由 PushPayload.updateSchedule() 调用,不需要主动调用此函数。 138 | 139 | |参数|类型|必须|默认值|说明| 140 | |-----|-----|-----|-----|-----| 141 | |scheduleID|string|是|无|需要更新的定时任务 ID。| 142 | |payload|PushPayload|是|无|需要设置的 PushPayload 对象。| 143 | 144 | #### getScheduleList(page) 145 | 获取有效的定时任务列表。 146 | 147 | |参数|类型|必须|默认值|说明| 148 | |-----|-----|-----|-----|-----| 149 | |page|int|是|无|请求页的页数,每页最多返回 50 个,如果请求页页数大于总页数,则 schedule 为空。| 150 | 151 | #### getSchedule(scheduleID) 152 | 获取指定的定时任务信息。 153 | 154 | |参数|类型|必须|默认值|说明| 155 | |-----|-----|-----|-----|-----| 156 | |scheduleID|string|是|无|指定的定时任务 ID。| 157 | 158 | #### delSchedule(scheduleID) 159 | 删除指定的定时任务。 160 | 161 | |参数|类型|必须|默认值|说明| 162 | |-----|-----|-----|-----|-----| 163 | |scheduleID|string|是|无|指定的定时任务 ID。| 164 | 165 | ---------- 166 | 167 | ### PushPayload 168 | 169 | 单次推送的对象,由 JPushClient 创建。 170 | 171 | 该类包含的方法有: 172 | 173 | |函数|说明| 174 | |-----|-----| 175 | |setPlatform|设置 platform,本方法接收 `JPush.ALL`, `android`, `ios`, `android`这几个参数.具体使用可参考 Platform 示例。 | 176 | |setAudience|设置 audience,本方法接收 `JPush.ALL`,或者是 `tag()`, `tag_and()`, `alias()`, `registration_id()` 创建的对象,具体可参考 Audience 示例。| 177 | |setNotification|设置 notification,本方法接收 `ios()`, `android()`, `winphone()`等方法创建的对象,如果第一个参数为字符串,则指定全局的 alert,具体可参考 Notification 示例。| 178 | |setMessage|设置 message,本方法接受 4 个参数`msg_content(string,必填)`, `title(string)`, `content_type(string)`, `extras(Object)`。| 179 | |setOptions|设置 options,本方法接收 5 个参数,`sendno(int)`, `time_to_live(int)`, `override_msg_id(int)`, `apns_production(boolean)`, `big_push_duration(int)`。| 180 | |toJSON|将当前 payload 对象转换为 json 字符串。| 181 | |send|推送当前 payload 对象。| 182 | |isIosExceedLength|检测当前 payload 是否超出 iOS notification 长度限定,返回 true / false(iOS Notification 不超过 220 并且 iOS notification + message 不超过 1200)。| 183 | |isGlobalExceedLength|检测当前 payload 是否超出长度限定,返回 true / false(iOS Notification 不超过 220 并且所有平台的 notification + message不超过1200)。| 184 | |setSingleSchedule|配置简单的定时任务,参数为形如 'YYYY-MM-DD HH:MM:SS' 的字符串。| 185 | |setPeriodicalSchedule|配置较复杂的定期任务,参数包括 `startDate(string)`, `endDate(string)`, `time(string)`, `timeUnit(string)`, `frequency(int)`, `point(string array)`,具体参数用法可参照[官方文档](http://docs.jiguang.cn/server/rest_api_push_schedule/#schedule_1)。| 186 | |setSchedule|向服务器提交设置的定时任务。参数:`name(string)`, `enabled(boolean)`。| 187 | |updateSchedule|向服务器请求更新指定的定期任务。参数:`id(string)`, `name(string)`, `enabled(boolean)`。| 188 | 189 | 开发者可以参考[推送示例][2]快速了解推送细节和[定时任务示例](/examples/ScheduleExample.js)了解定时任务细节。 190 | 191 | 192 | ### Platform 示例 193 | ```javascript 194 | client.push().setPlatform(JPush.ALL) // 指定推送给所有平台。 195 | client.push().setPlatform('ios', 'android') // 推送特定平台,参数排序不分先后。 196 | ``` 197 | 198 | ### Audience 示例 199 | 以下示例最终都构建`{"audience":{"tag":["555","666"]}}`,开发者可以根据具体应用场景选择合适的设值方法。 200 | 同理,`tag_and()`, `alias()`, `registration_id()` 都可以使用以下方法设置: 201 | ```javascript 202 | client.push().setAudience(JPush.tag('tag1', 'tag2')) 203 | client.push().setAudience(JPush.tag('tag1,tag2')) 204 | client.push().setAudience(JPush.tag(['tag1', 'tag2'])) 205 | ``` 206 | 当需要同时设置不同属性的audience的时候,参数的排列不分先后 207 | ```javascript 208 | client.push().setAudience(JPush.tag('tag1', 'tag2'), JPush.alias('alias1', 'alias2')) 209 | ``` 210 | 211 | ### Notification 示例 212 | ```javascript 213 | client.push().setNotification('Hi, JPush') // 设置全局的 alert。 214 | 215 | // 设置特定平台 notification。 216 | client.push().setNotification( 217 | JPush.android('Hi,JPush', 'JPush Title', 1, {'key':'value'}), 218 | JPush.ios('Hi, JPush', 'sound', 1)) 219 | 220 | // 同时设置全局 alert 与特定平台 notification。 221 | client.push().setNotification('Hi, JPush', 222 | JPush.android('Hi,JPush', 'JPush Title', 1, {'key':'value'}), 223 | JPush.ios('Hi, JPush', 'sound', 1)) 224 | ``` 225 | 226 | [1]: http://docs.jpush.cn/display/dev/Push-API-v3 227 | [2]: https://github.com/jpush/jpush-api-nodejs-client/blob/master/examples/PushExample.js 228 | -------------------------------------------------------------------------------- /examples/BatchPushExample.js: -------------------------------------------------------------------------------- 1 | var JPush = require("../index.js").JPush; 2 | var Conf = require("./Conf.js"); 3 | 4 | console.log(Conf.appKey); 5 | console.log(Conf.masterSecret); 6 | var client = JPush.buildClient(Conf.appKey, Conf.masterSecret); 7 | console.log(client.appkey); 8 | console.log(client.masterSecret); 9 | 10 | var singlePayloads = [ 11 | {"platform":"all", "target":"regid1", "notification":{"alert":"alert title"}}, 12 | {"platform":"all", "target":"regid2", "notification":{"alert":"alert title"}}, 13 | ]; 14 | 15 | client.batchPushByRegid(singlePayloads, function(err, res) { 16 | console.log(err); 17 | console.log(res); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/Conf.js.example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpush/jpush-api-nodejs-client/24afe5866398d1cd48ea736ebd1c7351209f7e1a/examples/Conf.js.example -------------------------------------------------------------------------------- /examples/DeviceAsyncExamples.js: -------------------------------------------------------------------------------- 1 | var JPush = require("../index.js").JPushAsync; 2 | var Conf = require("./Conf.js"); 3 | 4 | var client = JPush.buildClient(Conf.appKey, Conf.masterSecret); 5 | 6 | async function fun() { 7 | try { 8 | var re = await client.getTagList() 9 | console.log(re) 10 | } catch (err) { 11 | console.log(err) 12 | } 13 | } 14 | fun() 15 | 16 | client.getTagList() 17 | .then(function(result) { 18 | console.log(result) 19 | }).catch(function(err) { 20 | console.log(err) 21 | }) 22 | 23 | client.getDeviceTagAlias('0900e8d85ef') 24 | .then(function(result) { 25 | console.log(result) 26 | }).catch(function(err) { 27 | console.log(err) 28 | }) 29 | 30 | async function getDeviceStatus() { 31 | const resp = await client.getDeviceStatus(['171976fa8a8085fcdba']); 32 | if (resp.err) { 33 | console.log(resp.err.message) 34 | } else { 35 | console.log(resp.res); 36 | } 37 | } 38 | getDeviceStatus(); 39 | -------------------------------------------------------------------------------- /examples/DeviceExample.js: -------------------------------------------------------------------------------- 1 | var JPush = require("../index.js").JPush; 2 | var Conf = require("./Conf.js"); 3 | 4 | var client = JPush.buildClient(Conf.appKey, Conf.masterSecret); 5 | 6 | client.getDeviceTagAlias('0900e8d85ef', function (err, res) { 7 | if (err) { 8 | if (err instanceof JPush.APIConnectionError) { 9 | console.log(err.message) 10 | } else if (err instanceof JPush.APIRequestError) { 11 | console.log(err.message) 12 | } 13 | } else { 14 | console.log('getDeviceTagAlias :') 15 | console.log(res.alias) 16 | console.log(res.tags) 17 | } 18 | }) 19 | 20 | var tagsToAdd = ['tag1', 'tag2'] 21 | 22 | client.updateDeviceTagAlias('171976fa8a8085fcdba', 'alias1', false, tagsToAdd, [], 23 | function (err, res) { 24 | if (err) { 25 | if (err instanceof JPush.APIConnectionError) { 26 | console.log(err.message) 27 | } else if (err instanceof JPush.APIRequestError) { 28 | console.log(err.message) 29 | } 30 | } else { 31 | console.log('updateDeviceTagAlias :' + res) 32 | console.log('success') 33 | } 34 | }) 35 | 36 | client.getTagList(function (err, res) { 37 | if (err) { 38 | if (err instanceof JPush.APIConnectionError) { 39 | console.log(err.message) 40 | } else if (err instanceof JPush.APIRequestError) { 41 | console.log(err.message) 42 | } 43 | } else { 44 | console.log('getTagList :') 45 | console.log('got result:' + res.tags) 46 | } 47 | }) 48 | 49 | var toAddUsers = ['18071adc030dd6faa56', '171976fa8a8085fcdba'] 50 | var toRemoveUsers = ['171976fa8a8085fcdba'] 51 | 52 | client.addRemoveDevicesFromTag('tagtag', toAddUsers, toRemoveUsers, 53 | function (err, res) { 54 | if (err) { 55 | if (err instanceof JPush.APIConnectionError) { 56 | console.log(err.message) 57 | } else if (err instanceof JPush.APIRequestError) { 58 | console.log(err.message) 59 | } 60 | } else { 61 | console.log('success') 62 | } 63 | }) 64 | 65 | client.isDeviceInTag('tagtag', '171976fa8a8085fcdba', function (err, res) { 66 | if (err) { 67 | if (err instanceof JPush.APIConnectionError) { 68 | console.log(err.message) 69 | } else if (err instanceof JPush.APIRequestError) { 70 | console.log(err.message) 71 | } 72 | } else { 73 | console.log('isDeviceInTag :') 74 | console.log('got result:' + res['result']) 75 | } 76 | }) 77 | 78 | client.deleteTag('tag1', null, function (err, res) { 79 | if (err) { 80 | if (err instanceof JPush.APIConnectionError) { 81 | console.log(err.message) 82 | } else if (err instanceof JPush.APIRequestError) { 83 | console.log(err.message) 84 | } 85 | } else { 86 | console.log('deleteTag :') 87 | console.log('success') 88 | } 89 | }) 90 | 91 | client.getAliasDeviceList('alias1', null, function (err, res) { 92 | if (err) { 93 | if (err instanceof JPush.APIConnectionError) { 94 | console.log(err.message) 95 | } else if (err instanceof JPush.APIRequestError) { 96 | console.log(err.message) 97 | } 98 | } else { 99 | console.log('getAliasDeviceList :') 100 | console.log(res.registration_ids) 101 | } 102 | }) 103 | 104 | client.deleteAlias('alias2', null, function (err, res) { 105 | if (err) { 106 | if (err instanceof JPush.APIConnectionError) { 107 | console.log(err.message) 108 | } else if (err instanceof JPush.APIRequestError) { 109 | console.log(err.message) 110 | } 111 | } else { 112 | console.log('deleteAlias :') 113 | console.log('success') 114 | } 115 | }) 116 | 117 | /** 查询设备在线状态 */ 118 | client.getDeviceStatus(["160a3797c80b688c24b"], function(err, res) { 119 | if (err) { 120 | if (err instanceof JPush.APIConnectionError) { 121 | console.log(err.message) 122 | } else if (err instanceof JPush.APIRequestError) { 123 | console.log(err.message) 124 | } 125 | } else { 126 | console.log(res); 127 | } 128 | }) 129 | -------------------------------------------------------------------------------- /examples/PushAsyncExample.js: -------------------------------------------------------------------------------- 1 | var JPush = require("../index.js").JPushAsync; 2 | var Conf = require("./Conf.js"); 3 | 4 | var client = JPush.buildClient(Conf.appKey, Conf.masterSecret); 5 | 6 | // 使用 proxy 7 | // var client = JPush.buildClient(Conf.appKey, Conf.masterSecret, null, null, null,'http://192.168.8.236:3128') 8 | 9 | // easy push. 10 | client.push().setPlatform(JPush.ALL) 11 | .setAudience(JPush.ALL) 12 | .setNotification('Hi, JPush', JPush.ios('ios alert', 'happy', 5)) 13 | .send() 14 | .then(function(result) { 15 | console.log(result) 16 | }).catch(function(err) { 17 | console.log(err) 18 | }) 19 | 20 | // full push. 21 | client.push().setPlatform('ios', 'android') 22 | .setAudience(JPush.tag('555', '666'), JPush.alias('666,777')) 23 | .setNotification('Hi, JPush', JPush.ios('ios alert'), JPush.android('android alert', null, 1)) 24 | .setMessage('msg content') 25 | .setOptions(null, 60) 26 | .send() 27 | .then(function(result) { 28 | console.log(result) 29 | }).catch(function(err) { 30 | console.log(err) 31 | }) 32 | -------------------------------------------------------------------------------- /examples/PushExample.js: -------------------------------------------------------------------------------- 1 | var JPush = require("../index.js").JPush; 2 | var Conf = require("./Conf.js"); 3 | 4 | var client = JPush.buildClient(Conf.appKey, Conf.masterSecret); 5 | 6 | // 使用 proxy 7 | // var client = JPush.buildClient(Conf.appKey, Conf.masterSecret, null, null, null,'http://192.168.8.236:3128') 8 | 9 | // easy push. 10 | client.push().setPlatform(JPush.ALL) 11 | .setAudience(JPush.ALL) 12 | .setNotification('Hi, JPush', JPush.ios('ios alert', 'happy', 5)) 13 | .send(function (err, res) { 14 | if (err) { 15 | if (err instanceof JPush.APIConnectionError) { 16 | console.log(err.message) 17 | } else if (err instanceof JPush.APIRequestError) { 18 | console.log(err.message) 19 | } 20 | } else { 21 | console.log('Sendno: ' + res.sendno) 22 | console.log('Msg_id: ' + res.msg_id) 23 | } 24 | }) 25 | 26 | // full push. 27 | client.push().setPlatform('ios', 'android') 28 | .setAudience(JPush.tag('555', '666'), JPush.alias('666,777')) 29 | .setNotification('Hi, JPush', JPush.ios('ios alert'), JPush.android('android alert', null, 1)) 30 | .setMessage('msg content') 31 | .setOptions(null, 60) 32 | .send(function (err, res) { 33 | if (err) { 34 | if (err instanceof JPush.APIConnectionError) { 35 | console.log(err.message) 36 | // Response Timeout means your request to the server may have already received, 37 | // please check whether or not to push 38 | console.log(err.isResponseTimeout) 39 | } else if (err instanceof JPush.APIRequestError) { 40 | console.log(err.message) 41 | } 42 | } else { 43 | console.log('Sendno: ' + res.sendno) 44 | console.log('Msg_id: ' + res.msg_id) 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /examples/ReportAsyncExample.js: -------------------------------------------------------------------------------- 1 | var JPush = require("../index.js").JPushAsync; 2 | var Conf = require("./Conf.js"); 3 | 4 | var client = JPush.buildClient(Conf.appKey, Conf.masterSecret); 5 | 6 | async function getReportStatusMessage() { 7 | const data = await client.getReportStatusMessage(23223422, ['24243242']); 8 | if (data.err) { 9 | console.log(data.err); 10 | } else { 11 | console.log(data.res); 12 | } 13 | } 14 | getReportStatusMessage(); 15 | -------------------------------------------------------------------------------- /examples/ReportExample.js: -------------------------------------------------------------------------------- 1 | var JPush = require("../index.js").JPush; 2 | var Conf = require("./Conf.js"); 3 | 4 | var client = JPush.buildClient(Conf.appKey, Conf.masterSecret); 5 | 6 | function getReportReceiveds() { 7 | client.getReportReceiveds('746522674,344076897', function (err, res) { 8 | if (err) { 9 | if (err instanceof JPush.APIConnectionError) { 10 | console.log(err.message) 11 | // Response Timeout means your request to the server may have already received, 12 | // please check whether or not to push 13 | console.log(err.isResponseTimeout) 14 | } else if (err instanceof JPush.APIRequestError) { 15 | console.log(err.message) 16 | } 17 | } else { 18 | for (var i = 0; i < res.length; i++) { 19 | console.log(res[i].android_received) 20 | console.log(res[i].ios_apns_sent) 21 | console.log(res[i].msg_id) 22 | console.log('------------') 23 | } 24 | } 25 | }); 26 | } 27 | 28 | client.getReportReceivedDetail('746522674,344076897', function(err, res) { 29 | console.log(err); 30 | console.log(res); 31 | }) 32 | 33 | client.getReportStatusMessage(746522674, ['regid1', 'regid2'], null, function(err, res) { 34 | console.log(err); 35 | console.log(res); 36 | }) 37 | 38 | client.getReportMessagesDetail('746522674,344076897', function(err, res) { 39 | console.log(err); 40 | console.log(res); 41 | }) 42 | -------------------------------------------------------------------------------- /examples/ScheduleExample.js: -------------------------------------------------------------------------------- 1 | var JPush = require("../index.js").JPush; 2 | var Conf = require("./Conf.js"); 3 | 4 | var client = JPush.buildClient(Conf.appKey, Conf.masterSecret); 5 | 6 | // 设置定时任务。 7 | client.push().setPlatform(JPush.ALL) 8 | .setAudience(JPush.ALL) 9 | .setNotification('Hi, JPush', JPush.ios('Hello')) 10 | .setSingleSchedule('2016-08-08 18:00:00') 11 | .setSchedule('Schedule_Name', true, function (err, res) { 12 | if (err) { 13 | if (err instanceof JPush.APIConnectionError) { 14 | console.log(err.message) 15 | } else if (err instanceof JPush.APIRequestError) { 16 | console.log(err.message) 17 | } 18 | } 19 | }) 20 | 21 | // 设置定期任务。 22 | client.push().setPlatform(JPush.ALL) 23 | .setAudience(JPush.ALL) 24 | .setNotification('Hi, JPush', JPush.ios('Hello')) 25 | .setPeriodicalSchedule('2016-08-07 12:00:00', '2016-08-10 12:00:00', '12:00:00', 'week', 2, ['wed', 'fri']) 26 | .setSchedule('Schedule_Name_2', true, function (err, res) { 27 | if (err) { 28 | if (err instanceof JPush.APIConnectionError) { 29 | console.log(err.message) 30 | } else if (err instanceof JPush.APIRequestError) { 31 | console.log(err.message) 32 | } 33 | } 34 | }) 35 | 36 | // 更新定时任务。 37 | client.push().setSingleSchedule('2016-08-10 20:00:00') 38 | .updateSchedule('fb8fd1a4-5c91-11e6-a6b6-0021f653c902', null, null, 39 | function (err, res) { 40 | if (err) { 41 | if (err instanceof JPush.APIConnectionError) { 42 | console.log(err.message) 43 | } else if (err instanceof JPush.APIRequestError) { 44 | console.log(err.message) 45 | } 46 | } 47 | }) 48 | 49 | // 更新定期任务。 50 | client.push() 51 | .setPeriodicalSchedule('2016-08-10 12:00:00', '2016-08-11 12:00:00', '12:00:00', 'week', 3, ['wed', 'sun']) 52 | .updateSchedule('50713b1a-5d08-11e6-9fac-0021f653c902', null, null, function (err, res) { 53 | if (err instanceof JPush.APIConnectionError) { 54 | console.log(err.message) 55 | } else if (err instanceof JPush.APIRequestError) { 56 | console.log(err.message) 57 | } 58 | }) 59 | 60 | // 获取有效的 schedule 列表, 1 代表请求页页数,每页最多返回 50 个任务。 61 | client.getScheduleList(1, function (err, res) { 62 | if (err) { 63 | if (err instanceof JPush.APIConnectionError) { 64 | console.log(err.message) 65 | } else if (err instanceof JPush.APIRequestError) { 66 | console.log(err.message) 67 | } 68 | } 69 | }) 70 | 71 | // 获取指定的 schedule。 72 | client.getSchedule('fb8fd1a4-5c91-11e6-a6b6-0021f653c902', function (err, res) { 73 | if (err) { 74 | if (err instanceof JPush.APIConnectionError) { 75 | console.log(err.message) 76 | } else if (err instanceof JPush.APIRequestError) { 77 | console.log(err.message) 78 | } 79 | } 80 | }) 81 | 82 | // 删除指定的 schedule。 83 | client.delSchedule('97aa062c-5c92-11e6-a6ab-0021f652c102', function (err, res) { 84 | if (err) { 85 | if (err instanceof JPush.APIConnectionError) { 86 | console.log(err.message) 87 | } else if (err instanceof JPush.APIRequestError) { 88 | console.log(err.message) 89 | } 90 | } 91 | }) 92 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: JPush 3 | */ 4 | module.exports = { 5 | JPush: require('./lib/JPush/JPush.js'), 6 | JPushAsync: require('./lib/JPush/JPushAsync.js') 7 | } 8 | -------------------------------------------------------------------------------- /lib/JPush/JPush.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('jpush') 2 | var JError = require('./JPushError') 3 | var Request2 = require('./Request2') 4 | var JModel = require('./PushPayload') 5 | 6 | var PUSH_API_URL = 'https://api.jpush.cn/v3/push' 7 | var GROUP_API_URL = 'https://api.jpush.cn/v3/grouppush' 8 | var REPORT_API_URL = 'https://report.jpush.cn/v3' 9 | var SCHEDULE_API_URL = 'https://api.jpush.cn/v3/schedules' // 定时任务 10 | var REPORT_RECEIVED = '/received' 11 | var REPORT_RECEIVED_DETAIL = '/received/detail' 12 | var REPORT_STATUS_MESSAGE = '/status/message' 13 | var REPORT_USER = '/users' 14 | var REPORT_MESSAGE = '/messages' 15 | var REPORT_MESSAGE_DETAIL = '/messages/detail' 16 | var HOST_NAME_SSL = 'https://device.jpush.cn' 17 | var DEVICE_PATH = '/v3/devices' 18 | var TAG_PATH = '/v3/tags' 19 | var ALIAS_PATH = '/v3/aliases' 20 | var VALIDATE = '/validate' 21 | var DEFAULT_MAX_RETRY_TIMES = 3 22 | var READ_TIMEOUT = 30 * 1000 23 | 24 | // Pattern 25 | var PUSH_PATTERNS = /^[a-zA-Z0-9]{24}/ 26 | var MSG_IDS_PATTERNS = /[^\d,]/ 27 | 28 | exports.buildClient = function (appKey, masterSecret, retryTimes, isDebug, readTimeOut, proxy, isGroup) { 29 | if (arguments.length === 1 && typeof arguments[0] === 'object') { 30 | var options = arguments[0] 31 | return new JPushClient(options.appKey, 32 | options.masterSecret, 33 | options.retryTimes, 34 | options.isDebug, 35 | options.readTimeOut, 36 | options.proxy, 37 | options.isGroup 38 | ) 39 | } else { 40 | return new JPushClient(appKey, masterSecret, retryTimes, isDebug, readTimeOut, proxy, isGroup) 41 | } 42 | } 43 | 44 | function JPushClient (appKey, masterSecret, retryTimes, isDebug, readTimeOut = null, proxy = null, isGroup) { 45 | if (!appKey || !masterSecret) { 46 | throw JError.InvalidArgumentError('appKey and masterSecret are both required.') 47 | } 48 | 49 | this.isGroup = isGroup; 50 | if (typeof appKey !== 'string' || typeof masterSecret !== 'string' || 51 | !PUSH_PATTERNS.test(appKey) || !PUSH_PATTERNS.test(masterSecret)) { 52 | throw new JError.InvalidArgumentError('Key and Secret format is incorrect. ' + 53 | 'They should be 24 size, and be composed with alphabet and numbers. ' + 54 | 'Please confirm that they are coming from JPush Web Portal.') 55 | } 56 | this.appkey = isGroup ? 'group-' + appKey : appKey 57 | this.masterSecret = masterSecret 58 | this.isGroup = isGroup 59 | if (retryTimes) { 60 | if (typeof retryTimes !== 'number') { 61 | throw JError.InvalidArgumentError('Invalid retryTimes.') 62 | } 63 | this.retryTimes = retryTimes 64 | } else { 65 | this.retryTimes = DEFAULT_MAX_RETRY_TIMES 66 | } 67 | if (isDebug != null) { 68 | this.isDebug = isDebug 69 | } else { 70 | this.isDebug = true 71 | } 72 | if (readTimeOut != null) { 73 | this.readTimeOut = readTimeOut 74 | } else { 75 | this.readTimeOut = READ_TIMEOUT 76 | } 77 | if (proxy != null) { 78 | this.proxy = proxy 79 | } 80 | } 81 | 82 | /** 83 | * create a push payload 84 | * @returns {exports.PushPayload} 85 | */ 86 | function push () { 87 | return new JModel.PushPayload(this) 88 | } 89 | 90 | function sendPush (payload, callback) { 91 | return _request(this, this.isGroup === true ? GROUP_API_URL : PUSH_API_URL, payload, 'POST', callback) 92 | } 93 | 94 | function getReportReceiveds(msgIds, callback) { 95 | if (MSG_IDS_PATTERNS.test(msgIds)) { 96 | throw new JError.InvalidArgumentError( 97 | 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') 98 | } 99 | var url = REPORT_API_URL + REPORT_RECEIVED + '?msg_ids=' + msgIds 100 | return _request(this, url, null, 'GET', callback) 101 | } 102 | 103 | function getReportReceivedDetail(msgIds, callback) { 104 | if (MSG_IDS_PATTERNS.test(msgIds)) { 105 | throw new JError.InvalidArgumentError( 106 | 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') 107 | } 108 | var url = REPORT_API_URL + REPORT_RECEIVED_DETAIL + '?msg_ids=' + msgIds 109 | return _request(this, url, null, 'GET', callback) 110 | } 111 | 112 | function getReportStatusMessage(msgId, registrationIds, date, callback) { 113 | if (msgId == null) { 114 | throw new JError.InvalidArgumentError('msgId is null!'); 115 | } 116 | // if (typeof(msgId) != 'number') { 117 | // throw new JError.InvalidArgumentError('msgId is not number type!'); 118 | // } 119 | if (registrationIds == null) { 120 | throw new JError.InvalidArgumentError('registrationIds is null!'); 121 | } 122 | var json = { 123 | "msg_id": msgId, 124 | "registration_ids": registrationIds 125 | }; 126 | if (date) { 127 | json.date = date; 128 | } 129 | var url = REPORT_API_URL + REPORT_STATUS_MESSAGE; 130 | return _request(this, url, JSON.stringify(json), 'POST', callback); 131 | } 132 | 133 | function getReportMessages(msgIds, callback) { 134 | if (MSG_IDS_PATTERNS.test(msgIds)) { 135 | throw new JError.InvalidArgumentError( 136 | 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') 137 | } 138 | var url = REPORT_API_URL + REPORT_MESSAGE + '?msg_ids=' + msgIds 139 | return _request(this, url, null, 'GET', callback) 140 | } 141 | 142 | function getReportMessagesDetail(msgIds, callback) { 143 | if (MSG_IDS_PATTERNS.test(msgIds)) { 144 | throw new JError.InvalidArgumentError( 145 | 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') 146 | } 147 | var url = REPORT_API_URL + REPORT_MESSAGE_DETAIL + '?msg_ids=' + msgIds 148 | return _request(this, url, null, 'GET', callback) 149 | } 150 | 151 | function getReportUsers (timeUnit, start, duration, callback) { 152 | var url = REPORT_API_URL + REPORT_USER + '?time_unit=' + timeUnit + '&start=' + start + '&duration=' + duration 153 | return _request(this, url, null, 'GET', callback) 154 | } 155 | 156 | /** 157 | * device api 158 | * 159 | * @param registrationId 160 | */ 161 | function getDeviceTagAlias (registrationId, callback) { 162 | var url = HOST_NAME_SSL + DEVICE_PATH + '/' + registrationId 163 | return _request(this, url, null, 'GET', callback) 164 | } 165 | 166 | // 结合短信业务使用,需要先调用该方法将用户的手机号码与设备的 registration id 匹配。 167 | function setMobile (registrationId, mobileNumber, callback) { 168 | var json = {} 169 | json['mobile'] = mobileNumber 170 | 171 | var url = HOST_NAME_SSL + DEVICE_PATH + '/' + registrationId 172 | return _request(this, url, JSON.stringify(json), 'POST', callback) 173 | } 174 | 175 | function updateDeviceTagAlias (registrationId, alias, clearTag, tagsToAdd, tagsToRemove, callback) { 176 | var url = HOST_NAME_SSL + DEVICE_PATH + '/' + registrationId 177 | 178 | if (tagsToAdd instanceof Array && tagsToRemove instanceof Array) { 179 | var json = {} 180 | var tags = {} 181 | if (alias != null) { 182 | json['alias'] = alias 183 | } 184 | if (clearTag) { 185 | json['tags'] = '' 186 | } else { 187 | if (tagsToAdd != null && tagsToAdd.length > 0) { 188 | tags['add'] = tagsToAdd 189 | } 190 | if (tagsToRemove != null && tagsToRemove.length > 0) { 191 | tags['remove'] = tagsToRemove 192 | } 193 | json['tags'] = tags 194 | debug(json) 195 | } 196 | } else { 197 | throw new JError.InvalidArgumentError('tagsToAdd or tagsToRemove type should be array') 198 | } 199 | return _request(this, url, JSON.stringify(json), 'POST', callback) 200 | } 201 | 202 | function getTagList (callback) { 203 | var url = HOST_NAME_SSL + TAG_PATH 204 | return _request(this, url, null, 'GET', callback) 205 | } 206 | 207 | function isDeviceInTag (theTag, registrationID, callback) { 208 | var url = HOST_NAME_SSL + TAG_PATH + '/' + theTag + '/registration_ids/' + registrationID 209 | return _request(this, url, null, 'GET', callback) 210 | } 211 | 212 | function addRemoveDevicesFromTag (theTag, toAddUsers, toRemoveUsers, callback) { 213 | var url = HOST_NAME_SSL + TAG_PATH + '/' + theTag 214 | var registrationIds = {} 215 | if (toAddUsers != null && toAddUsers.length > 0) { 216 | registrationIds['add'] = toAddUsers 217 | } 218 | if (toRemoveUsers != null && toRemoveUsers.length > 0) { 219 | registrationIds['remove'] = toRemoveUsers 220 | } 221 | var json = {} 222 | json['registration_ids'] = registrationIds 223 | debug(json['registration_ids']) 224 | return _request(this, url, JSON.stringify(json), 'POST', callback) 225 | } 226 | 227 | function deleteTag (theTag, platform, callback) { 228 | var url = HOST_NAME_SSL + TAG_PATH + '/' + theTag 229 | if (platform != null) { 230 | url += ('/?platform=' + platform) 231 | } 232 | return _request(this, url, null, 'delete', callback) 233 | } 234 | 235 | function getAliasDeviceList (alias, platform, callback) { 236 | var url = HOST_NAME_SSL + ALIAS_PATH + '/' + alias 237 | if (platform != null) { 238 | url += '/?platform=' + platform 239 | } 240 | return _request(this, url, null, 'GET', callback) 241 | } 242 | 243 | function deleteAlias (alias, platform, callback) { 244 | var url = HOST_NAME_SSL + ALIAS_PATH + '/' + alias 245 | if (platform != null) { 246 | url += '/?platform=' + platform 247 | } 248 | return _request(this, url, null, 'delete', callback) 249 | } 250 | 251 | function validate (payload, callback) { 252 | return _request(this, PUSH_API_URL + VALIDATE, payload, 'POST', callback) 253 | } 254 | 255 | // 定时任务 start 256 | 257 | function setSchedule (payload, callback) { 258 | return _request(this, SCHEDULE_API_URL, payload, 'POST', callback) 259 | } 260 | 261 | function updateSchedule (scheduleId, payload, callback) { 262 | var url = SCHEDULE_API_URL + '/' + scheduleId 263 | return _request(this, url, payload, 'PUT', callback) 264 | } 265 | 266 | // 获取有效的定时任务列表。 267 | function getScheduleList (page, callback) { 268 | if (typeof page !== 'number') { 269 | throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the Number.') 270 | } 271 | var url = SCHEDULE_API_URL + '?page=' + page 272 | return _request(this, url, null, 'GET', callback) 273 | } 274 | 275 | // 获取指定的定时任务。 276 | function getSchedule (scheduleId, callback) { 277 | if (typeof scheduleId !== 'string') { 278 | throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the String.') 279 | } 280 | var url = SCHEDULE_API_URL + '/' + scheduleId 281 | return _request(this, url, null, 'GET', callback) 282 | } 283 | 284 | // 删除指定的定时任务。 285 | function delSchedule (scheduleId, callback) { 286 | if (typeof scheduleId !== 'string') { 287 | throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the String.') 288 | } 289 | var url = SCHEDULE_API_URL + '/' + scheduleId 290 | return _request(this, url, null, 'DELETE', callback) 291 | } 292 | 293 | // 获取定时任务对应的所有 msg_id 294 | function getScheduleMsgIds (scheduleId, callback) { 295 | if (typeof scheduleId !== 'string') { 296 | throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the String.') 297 | } 298 | var url = SCHEDULE_API_URL + '/' + scheduleId + '/msg_ids' 299 | return _request(this, url, null, 'GET', callback) 300 | } 301 | 302 | /** 303 | * 获取推送唯一标识符 304 | * http://docs.jiguang.cn/jpush/server/push/rest_api_v3_push/#cid 305 | * @param {*} count 可选参数。数值类型,不传则默认为 1。范围为 [1, 1000] 306 | * @param {*} type 可选参数。CID 类型。取值:push(默认),schedule 307 | * @param {*} callback 308 | */ 309 | function getCid(count, type, callback) { 310 | if (!count) { 311 | count = 1; 312 | } 313 | if (!type) { 314 | type = 'push'; 315 | } 316 | var url = PUSH_API_URL + '/cid?count=' + count + '&type=' + type; 317 | return _request(this, url, null, 'GET', callback); 318 | } 319 | 320 | /** 321 | * 针对RegID方式批量单推(VIP专属接口) 322 | * http://docs.jiguang.cn/jpush/server/push/rest_api_v3_push/#vip 323 | * @param {*} singlePayloads 单推payload数组 324 | * @param {*} callback 325 | */ 326 | function batchPushByRegid(singlePayloads, callback) { 327 | var url = PUSH_API_URL + '/batch/regid/single'; 328 | return batchPush.call(this, url, singlePayloads, callback); 329 | } 330 | 331 | /** 332 | * 针对Alias方式批量单推(VIP专属接口)s 333 | * http://docs.jiguang.cn/jpush/server/push/rest_api_v3_push/#vip 334 | * @param {*} singlePayloads 单推payload数组 335 | * @param {*} callback 336 | */ 337 | function batchPushByAlias(singlePayloads, callback) { 338 | var url = PUSH_API_URL + '/batch/alias/single'; 339 | return batchPush.call(this, url, singlePayloads, callback); 340 | } 341 | 342 | function batchPush(url, singlePayloads, callback) { 343 | var client = this; 344 | return getCid.call(client, singlePayloads.length, 'push', function(err, res) { 345 | if (err) { 346 | return callback(err); 347 | } 348 | var body = {"pushlist":{}}; 349 | for (var i = 0; i < singlePayloads.length; i++) { 350 | body.pushlist[res.cidlist[i]] = singlePayloads[i]; 351 | } 352 | return _request(client, url, JSON.stringify(body), 'POST', callback); 353 | }); 354 | } 355 | 356 | // 定时任务 end 357 | 358 | /** 359 | * 获取用户在线状态(vip专属接口) 360 | * https://docs.jiguang.cn//jpush/server/push/rest_api_v3_device/#vip 361 | * @param {*} regIds 需要在线状态的用户 registration_id 362 | * @param {*} callback 363 | */ 364 | function getDeviceStatus(regIds, callback) { 365 | var json = { 366 | "registration_ids": regIds 367 | }; 368 | 369 | var url = HOST_NAME_SSL + DEVICE_PATH + '/status/'; 370 | return _request(this, url, JSON.stringify(json), 'POST', callback); 371 | } 372 | 373 | 374 | // Proxy start 375 | 376 | // Proxy end 377 | 378 | function _request (client, url, body, method, callback, times = 1) { 379 | if (client.isDebug) { 380 | debug('Push URL :' + url) 381 | if (body) { 382 | debug('Body :' + body) 383 | } 384 | // debug("Auth :" + JSON.stringify(auth)) 385 | debug('Method :' + method) 386 | debug('Times/MaxTryTimes : ' + times + '/' + client.maxTryTimes) 387 | } 388 | 389 | var _callback = function (err, res, body) { 390 | if (err) { 391 | if (err.code === 'ETIMEDOUT' && err.syscall !== 'connect') { 392 | // response timeout 393 | return callback(new JError.APIConnectionError( 394 | 'Response timeout. Your request to the server may have already received, please check whether or not to push', 395 | true)) 396 | } else if (err.code === 'ENOTFOUND') { 397 | // unknown host 398 | return callback(new JError.APIConnectionError('Known host : ' + url)) 399 | } 400 | // other connection error 401 | if (times < client.maxTryTimes) { 402 | return _request(client, url, body, method, callback, times + 1) 403 | } else { 404 | return callback(new JError.APIConnectionError('Connect timeout. Please retry later.')) 405 | } 406 | } 407 | if (res.statusCode === 200) { 408 | if (body.length !== 0) { 409 | if (client.isDebug) { 410 | debug('Success, response : ' + body) 411 | } 412 | 413 | try { 414 | callback(null, JSON.parse(body)) 415 | } catch (e) { 416 | callback(e) 417 | } 418 | } else { 419 | if (client.isDebug) { 420 | debug('Success, response : ' + body) 421 | } 422 | callback(null, 200) 423 | } 424 | } else { 425 | if (client.isDebug) { 426 | debug('Fail, HttpStatusCode: ' + res.statusCode + ' result: ' + body.toString()) 427 | } 428 | callback(new JError.APIRequestError(res.statusCode, body)) 429 | } 430 | } 431 | Request2(client, url, { method, body: JSON.parse(body), timeout: client.readTimeOut }, _callback); 432 | } 433 | 434 | JPushClient.prototype.sendPush = sendPush 435 | JPushClient.prototype.getReportReceiveds = getReportReceiveds 436 | JPushClient.prototype.getReportReceivedDetail = getReportReceivedDetail 437 | JPushClient.prototype.getReportStatusMessage = getReportStatusMessage 438 | JPushClient.prototype.getReportMessagesDetail = getReportMessagesDetail 439 | JPushClient.prototype.push = push 440 | JPushClient.prototype.setMobile = setMobile 441 | JPushClient.prototype.getDeviceTagAlias = getDeviceTagAlias 442 | JPushClient.prototype.updateDeviceTagAlias = updateDeviceTagAlias 443 | JPushClient.prototype.getTagList = getTagList 444 | JPushClient.prototype.isDeviceInTag = isDeviceInTag 445 | JPushClient.prototype.addRemoveDevicesFromTag = addRemoveDevicesFromTag 446 | JPushClient.prototype.deleteTag = deleteTag 447 | JPushClient.prototype.getAliasDeviceList = getAliasDeviceList 448 | JPushClient.prototype.deleteAlias = deleteAlias 449 | JPushClient.prototype.getDeviceStatus = getDeviceStatus 450 | JPushClient.prototype.validate = validate 451 | JPushClient.prototype.getReportMessages = getReportMessages 452 | JPushClient.prototype.getReportUsers = getReportUsers 453 | JPushClient.prototype.getScheduleList = getScheduleList 454 | JPushClient.prototype.getSchedule = getSchedule 455 | JPushClient.prototype.delSchedule = delSchedule 456 | JPushClient.prototype.setSchedule = setSchedule 457 | JPushClient.prototype.updateSchedule = updateSchedule 458 | JPushClient.prototype.getCid = getCid 459 | JPushClient.prototype.batchPushByRegid = batchPushByRegid 460 | JPushClient.prototype.batchPushByAlias = batchPushByAlias 461 | 462 | // exports constants and methods 463 | exports.ALL = JModel.ALL 464 | exports.tag = JModel.tag 465 | exports.tag_and = JModel.tag_and 466 | exports.tag_not = JModel.tag_not 467 | exports.alias = JModel.alias 468 | exports.registration_id = JModel.registration_id 469 | exports.segment = JModel.segment 470 | exports.abtest = JModel.abtest 471 | exports.ios = JModel.ios 472 | exports.android = JModel.android 473 | exports.winphone = JModel.winphone 474 | 475 | // error 476 | exports.APIConnectionError = JError.APIConnectionError 477 | exports.APIRequestError = JError.APIRequestError 478 | exports.InvalidArgumentError = JError.InvalidArgumentError 479 | -------------------------------------------------------------------------------- /lib/JPush/JPushAsync.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('jpush') 2 | var JError = require('./JPushError') 3 | var Request2 = require('./Request2') 4 | var JModel = require('./PushPayloadAsync') 5 | 6 | var PUSH_API_URL = 'https://api.jpush.cn/v3/push' 7 | var GROUP_API_URL = 'https://api.jpush.cn/v3/grouppush' 8 | var REPORT_API_URL = 'https://report.jpush.cn/v3' 9 | var SCHEDULE_API_URL = 'https://api.jpush.cn/v3/schedules' // 定时任务 10 | var REPORT_RECEIVED = '/received' 11 | var REPORT_RECEIVED_DETAIL = '/received/detail' 12 | var REPORT_STATUS_MESSAGE = '/status/message' 13 | var REPORT_USER = '/users' 14 | var REPORT_MESSAGE = '/messages' 15 | var REPORT_MESSAGE_DETAIL = '/messages/detail' 16 | var HOST_NAME_SSL = 'https://device.jpush.cn' 17 | var DEVICE_PATH = '/v3/devices' 18 | var TAG_PATH = '/v3/tags/' 19 | var ALIAS_PATH = '/v3/aliases' 20 | var VALIDATE = '/validate' 21 | var DEFAULT_MAX_RETRY_TIMES = 3 22 | var READ_TIMEOUT = 30 * 1000 23 | 24 | // Pattern 25 | var PUSH_PATTERNS = /^[a-zA-Z0-9]{24}/ 26 | var MSG_IDS_PATTERNS = /[^\d,]/ 27 | 28 | exports.buildClient = function (appKey, masterSecret, retryTimes, isDebug, readTimeOut, proxy, isGroup = false) { 29 | if (arguments.length === 1 && typeof arguments[0] === 'object') { 30 | var options = arguments[0] 31 | return new JPushClient(options.appKey, 32 | options.masterSecret, 33 | options.retryTimes, 34 | options.isDebug, 35 | options.readTimeOut, 36 | options.proxy, 37 | options.isGroup 38 | ) 39 | } else { 40 | return new JPushClient(appKey, masterSecret, retryTimes, isDebug, readTimeOut, proxy, isGroup) 41 | } 42 | } 43 | 44 | function JPushClient (appKey, masterSecret, retryTimes, isDebug, readTimeOut = null, proxy = null, isGroup) { 45 | if (!appKey || !masterSecret) { 46 | throw JError.InvalidArgumentError('Key and Secret are both required.') 47 | } 48 | 49 | this.isGroup = isGroup; 50 | if (typeof appKey !== 'string' || typeof masterSecret !== 'string' || 51 | !PUSH_PATTERNS.test(appKey) || !PUSH_PATTERNS.test(masterSecret)) { 52 | throw new JError.InvalidArgumentError('appKey and masterSecret format is incorrect. ' + 53 | 'They should be 24 size, and be composed with alphabet and numbers. ' + 54 | 'Please confirm that they are coming from JPush Web Portal.') 55 | } 56 | this.appkey = isGroup ? 'group-' + appKey : appKey 57 | this.masterSecret = masterSecret 58 | if (retryTimes) { 59 | if (typeof retryTimes !== 'number') { 60 | throw JError.InvalidArgumentError('Invalid retryTimes.') 61 | } 62 | this.retryTimes = retryTimes 63 | } else { 64 | this.retryTimes = DEFAULT_MAX_RETRY_TIMES 65 | } 66 | if (isDebug != null) { 67 | this.isDebug = isDebug 68 | } else { 69 | this.isDebug = true 70 | } 71 | if (readTimeOut != null) { 72 | this.readTimeOut = readTimeOut 73 | } else { 74 | this.readTimeOut = READ_TIMEOUT 75 | } 76 | if (proxy != null) { 77 | this.proxy = proxy 78 | } 79 | } 80 | 81 | /** 82 | * create a push payload 83 | * @returns {exports.PushPayload} 84 | */ 85 | function push () { 86 | return new JModel.PushPayload(this) 87 | } 88 | 89 | async function sendPush (payload) { 90 | return _request(this, this.isGroup === true ? GROUP_API_URL : PUSH_API_URL, payload, 'POST') 91 | } 92 | 93 | async function getReportReceiveds(msgIds) { 94 | if (MSG_IDS_PATTERNS.test(msgIds)) { 95 | throw new JError.InvalidArgumentError( 96 | 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') 97 | } 98 | var url = REPORT_API_URL + REPORT_RECEIVED + '?msg_ids=' + msgIds 99 | return _request(this, url, null, 'GET') 100 | } 101 | 102 | async function getReportReceivedDetail(msgIds) { 103 | if (MSG_IDS_PATTERNS.test(msgIds)) { 104 | throw new JError.InvalidArgumentError( 105 | 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') 106 | } 107 | var url = REPORT_API_URL + REPORT_RECEIVED_DETAIL + '?msg_ids=' + msgIds 108 | return _request(this, url, null, 'GET') 109 | } 110 | 111 | function getReportStatusMessage(msgId, registrationIds, date) { 112 | if (msgId == null) { 113 | throw new JError.InvalidArgumentError('msgId is null!'); 114 | } 115 | 116 | if (registrationIds == null) { 117 | throw new JError.InvalidArgumentError('registrationIds is null!'); 118 | } 119 | var json = { 120 | "msg_id": msgId > Number.MAX_SAFE_INTEGER ? String(msgId) : msgId, 121 | "registration_ids": registrationIds 122 | }; 123 | if (date != null) { 124 | json.date = date; 125 | } 126 | var url = REPORT_API_URL + REPORT_STATUS_MESSAGE; 127 | return _request(this, url, json, 'POST') 128 | .then(res => ({ res })) 129 | .catch(error => ({ err: error })); 130 | } 131 | 132 | async function getReportMessages(msgIds) { 133 | if (MSG_IDS_PATTERNS.test(msgIds)) { 134 | throw new JError.InvalidArgumentError( 135 | 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') 136 | } 137 | var url = REPORT_API_URL + REPORT_MESSAGE + '?msg_ids=' + msgIds 138 | return _request(this, url, null, 'GET') 139 | } 140 | 141 | async function getReportMessagesDetail(msgIds) { 142 | if (MSG_IDS_PATTERNS.test(msgIds)) { 143 | throw new JError.InvalidArgumentError( 144 | 'Invalid msg_ids, msg_ids should be composed with alphabet and comma.') 145 | } 146 | var url = REPORT_API_URL + REPORT_MESSAGE_DETAIL + '?msg_ids=' + msgIds 147 | return _request(this, url, null, 'GET') 148 | } 149 | 150 | async function getReportUsers (timeUnit, start, duration) { 151 | var url = REPORT_API_URL + REPORT_USER + '?time_unit=' + timeUnit + '&start=' + start + '&duration=' + duration 152 | return _request(this, url, null, 'GET') 153 | } 154 | 155 | /** 156 | * device api 157 | * 158 | * @param registrationId 159 | */ 160 | async function getDeviceTagAlias (registrationId) { 161 | var url = HOST_NAME_SSL + DEVICE_PATH + '/' + registrationId 162 | return _request(this, url, null, 'GET') 163 | } 164 | 165 | // 结合短信业务使用,需要先调用该方法将用户的手机号码与设备的 registration id 匹配。 166 | async function setMobile (registrationId, mobileNumber) { 167 | var json = {} 168 | json['mobile'] = mobileNumber 169 | 170 | var url = HOST_NAME_SSL + DEVICE_PATH + '/' + registrationId 171 | return _request(this, url, json, 'POST') 172 | } 173 | 174 | async function updateDeviceTagAlias (registrationId, alias, clearTag, tagsToAdd, tagsToRemove) { 175 | var url = HOST_NAME_SSL + DEVICE_PATH + '/' + registrationId 176 | 177 | if (tagsToAdd instanceof Array && tagsToRemove instanceof Array) { 178 | var json = {} 179 | var tags = {} 180 | if (alias != null) { 181 | json['alias'] = alias 182 | } 183 | if (clearTag) { 184 | json['tags'] = '' 185 | } else { 186 | if (tagsToAdd != null && tagsToAdd.length > 0) { 187 | tags['add'] = tagsToAdd 188 | } 189 | if (tagsToRemove != null && tagsToRemove.length > 0) { 190 | tags['remove'] = tagsToRemove 191 | } 192 | json['tags'] = tags 193 | debug(json) 194 | } 195 | } else { 196 | throw new JError.InvalidArgumentError('tagsToAdd or tagsToRemove type should be array') 197 | } 198 | return _request(this, url, json, 'POST') 199 | } 200 | 201 | async function getTagList () { 202 | var url = HOST_NAME_SSL + TAG_PATH 203 | return _request(this, url, null, 'GET') 204 | } 205 | 206 | async function isDeviceInTag (theTag, registrationID) { 207 | var url = HOST_NAME_SSL + TAG_PATH + '/' + theTag + '/registration_ids/' + registrationID 208 | return _request(this, url, null, 'GET') 209 | } 210 | 211 | async function addRemoveDevicesFromTag (theTag, toAddUsers, toRemoveUsers) { 212 | var url = HOST_NAME_SSL + TAG_PATH + theTag 213 | var registrationIds = {} 214 | if (toAddUsers != null && toAddUsers.length > 0) { 215 | registrationIds['add'] = toAddUsers 216 | } 217 | if (toRemoveUsers != null && toRemoveUsers.length > 0) { 218 | registrationIds['remove'] = toRemoveUsers 219 | } 220 | var json = {} 221 | json['registration_ids'] = registrationIds 222 | debug(json['registration_ids']) 223 | return _request(this, url, json, 'POST') 224 | } 225 | 226 | async function deleteTag (theTag, platform) { 227 | var url = HOST_NAME_SSL + TAG_PATH + '/' + theTag 228 | if (platform != null) { 229 | url += ('/?platform=' + platform) 230 | } 231 | return _request(this, url, null, 'delete') 232 | } 233 | 234 | async function getAliasDeviceList (alias, platform) { 235 | var url = HOST_NAME_SSL + ALIAS_PATH + '/' + alias 236 | if (platform != null) { 237 | url += '/?platform=' + platform 238 | } 239 | return _request(this, url, null, 'GET') 240 | } 241 | 242 | async function deleteAlias (alias, platform) { 243 | var url = HOST_NAME_SSL + ALIAS_PATH + '/' + alias 244 | if (platform != null) { 245 | url += '/?platform=' + platform 246 | } 247 | return _request(this, url, null, 'delete') 248 | } 249 | 250 | async function validate (payload) { 251 | return _request(this, PUSH_API_URL + VALIDATE, payload, 'POST') 252 | } 253 | 254 | // 定时任务 start 255 | 256 | async function setSchedule (payload) { 257 | return _request(this, SCHEDULE_API_URL, payload, 'POST') 258 | } 259 | 260 | async function updateSchedule (scheduleId, payload) { 261 | var url = SCHEDULE_API_URL + '/' + scheduleId 262 | return _request(this, url, payload, 'PUT') 263 | } 264 | 265 | // 获取有效的定时任务列表。 266 | async function getScheduleList (page) { 267 | if (typeof page !== 'number') { 268 | throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the Number.') 269 | } 270 | var url = SCHEDULE_API_URL + '?page=' + page 271 | return _request(this, url, null, 'GET') 272 | } 273 | 274 | // 获取指定的定时任务。 275 | async function getSchedule (scheduleId) { 276 | if (typeof scheduleId !== 'string') { 277 | throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the String.') 278 | } 279 | var url = SCHEDULE_API_URL + '/' + scheduleId 280 | return _request(this, url, null, 'GET') 281 | } 282 | 283 | // 删除指定的定时任务。 284 | async function delSchedule (scheduleId) { 285 | if (typeof scheduleId !== 'string') { 286 | throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the String.') 287 | } 288 | var url = SCHEDULE_API_URL + '/' + scheduleId 289 | return _request(this, url, null, 'DELETE') 290 | } 291 | 292 | // 获取定时任务对应的所有 msg_id 293 | async function getScheduleMsgIds (scheduleId, callback) { 294 | if (typeof scheduleId !== 'string') { 295 | throw new JError.InvalidArgumentError('Invalid argument, it can only be set to the String.') 296 | } 297 | var url = SCHEDULE_API_URL + '/' + scheduleId + '/msg_ids' 298 | return _request(this, url, null, 'GET', callback) 299 | } 300 | 301 | // 定时任务 end 302 | 303 | // Proxy start 304 | 305 | // Proxy end 306 | 307 | /** 308 | * 获取用户在线状态(vip专属接口) 309 | * https://docs.jiguang.cn//jpush/server/push/rest_api_v3_device/#vip 310 | * @param {*} regIds 需要在线状态的用户 registration_id 311 | */ 312 | function getDeviceStatus(regIds) { 313 | var json = { 314 | "registration_ids": regIds 315 | }; 316 | 317 | var url = HOST_NAME_SSL + DEVICE_PATH + '/status/'; 318 | return _request(this, url, json, 'POST') 319 | .then(res => ({ res })) 320 | .catch(error => ({ err: error })); 321 | } 322 | 323 | async function _request (client, url, body, method, times = 1) { 324 | if (client.isDebug) { 325 | debug('Push URL :' + url) 326 | if (body) { 327 | debug('Body :' + body) 328 | } 329 | // debug("Auth :" + JSON.stringify(auth)) 330 | debug('Method :' + method) 331 | debug('Times/MaxTryTimes : ' + times + '/' + client.maxTryTimes) 332 | } 333 | 334 | var options = { 335 | method: method.toUpperCase(), 336 | json: true, 337 | uri: url, 338 | body: body, 339 | auth: { 340 | user: client.appkey, 341 | pass: client.masterSecret 342 | }, 343 | timeout: client.readTimeOut, 344 | proxy: client.proxy 345 | }; 346 | 347 | try { 348 | return await Request2(client, url, { method, body, timeout: client.readTimeOut }); 349 | } catch (err) { 350 | if (err instanceof Buffer) return err.toString(); 351 | if (err.error.code === 'ETIMEDOUT' && err.error.syscall !== 'connect') { 352 | // response timeout 353 | throw new JError.APIConnectionError( 354 | 'Response timeout. Your request to the server may have already received, please check whether or not to push', 355 | true) 356 | } else if (err.error.code === 'ENOTFOUND') { 357 | // unknown host 358 | throw new JError.APIConnectionError('Unknown host : ' + url) 359 | } else if (times < client.maxTryTimes) { 360 | return _request(client, url, body, method, times + 1) 361 | } else { 362 | if (client.isDebug) { 363 | debug('Fail, HttpStatusCode: ' + err.statusCode + ' result: ' + JSON.stringify(err.error)) 364 | } 365 | throw new JError.APIRequestError(err.statusCode, JSON.stringify(err.error)) 366 | } 367 | } 368 | } 369 | 370 | JPushClient.prototype.sendPush = sendPush 371 | JPushClient.prototype.getReportReceiveds = getReportReceiveds 372 | JPushClient.prototype.getReportReceivedDetail = getReportReceivedDetail 373 | JPushClient.prototype.getReportStatusMessage = getReportStatusMessage 374 | JPushClient.prototype.getReportMessagesDetail = getReportMessagesDetail 375 | JPushClient.prototype.push = push 376 | JPushClient.prototype.setMobile = setMobile 377 | JPushClient.prototype.getDeviceTagAlias = getDeviceTagAlias 378 | JPushClient.prototype.updateDeviceTagAlias = updateDeviceTagAlias 379 | JPushClient.prototype.getTagList = getTagList 380 | JPushClient.prototype.isDeviceInTag = isDeviceInTag 381 | JPushClient.prototype.getDeviceStatus = getDeviceStatus 382 | JPushClient.prototype.addRemoveDevicesFromTag = addRemoveDevicesFromTag 383 | JPushClient.prototype.deleteTag = deleteTag 384 | JPushClient.prototype.getAliasDeviceList = getAliasDeviceList 385 | JPushClient.prototype.deleteAlias = deleteAlias 386 | JPushClient.prototype.validate = validate 387 | JPushClient.prototype.getReportMessages = getReportMessages 388 | JPushClient.prototype.getReportUsers = getReportUsers 389 | JPushClient.prototype.getScheduleList = getScheduleList 390 | JPushClient.prototype.getSchedule = getSchedule 391 | JPushClient.prototype.delSchedule = delSchedule 392 | JPushClient.prototype.setSchedule = setSchedule 393 | JPushClient.prototype.updateSchedule = updateSchedule 394 | 395 | // exports constants and methods 396 | exports.ALL = JModel.ALL 397 | exports.tag = JModel.tag 398 | exports.tag_and = JModel.tag_and 399 | exports.tag_not = JModel.tag_not 400 | exports.alias = JModel.alias 401 | exports.registration_id = JModel.registration_id 402 | exports.segment = JModel.segment 403 | exports.abtest = JModel.abtest 404 | exports.ios = JModel.ios 405 | exports.android = JModel.android 406 | exports.winphone = JModel.winphone 407 | 408 | // error 409 | exports.APIConnectionError = JError.APIConnectionError 410 | exports.APIRequestError = JError.APIRequestError 411 | exports.InvalidArgumentError = JError.InvalidArgumentError 412 | -------------------------------------------------------------------------------- /lib/JPush/JPushError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JPush Error 3 | */ 4 | 5 | exports.APIConnectionError = APIConnectionError 6 | exports.APIRequestError = APIRequestError 7 | exports.InvalidArgumentError = InvalidArgumentError 8 | 9 | function APIConnectionError (message, isResponseTimeout) { 10 | this.name = 'APIConnectionError' 11 | this.message = message 12 | this.isResponseTimeout = isResponseTimeout || false 13 | this.stack = (new Error()).stack 14 | } 15 | 16 | APIConnectionError.prototype = Object.create(Error.prototype) 17 | APIConnectionError.prototype.constructor = APIConnectionError 18 | 19 | function APIRequestError (httpCode, response) { 20 | var message = 'Push Fail, HttpStatusCode: ' + httpCode + ' result: ' + response.toString() 21 | this.name = 'APIRequestError' 22 | this.message = message 23 | this.httpCode = httpCode 24 | this.response = response 25 | this.stack = (new Error()).stack 26 | } 27 | 28 | APIRequestError.prototype = Object.create(Error.prototype) 29 | APIRequestError.prototype.constructor = APIRequestError 30 | 31 | function InvalidArgumentError (message) { 32 | this.name = 'InvalidArgumentError' 33 | this.message = message 34 | this.stack = (new Error()).stack 35 | } 36 | 37 | InvalidArgumentError.prototype = Object.create(Error.prototype) 38 | InvalidArgumentError.prototype.constructor = InvalidArgumentError -------------------------------------------------------------------------------- /lib/JPush/PushPayload.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | 3 | var JError = require('./JPushError') 4 | var JUtil = require('./util') 5 | 6 | function PushPayload (client) { 7 | this.client = client 8 | this.payload = {} 9 | this.trigger = {} 10 | } 11 | 12 | function setPlatform () { 13 | if (arguments.length < 1) { 14 | throw new JError.InvalidArgumentError("platform's args cannot all be null") 15 | } 16 | 17 | var platform, i 18 | 19 | if (arguments.length === 1 && arguments[0] === ALL) { 20 | platform = ALL 21 | } else if (arguments.length === 1 && typeof arguments[0] === 'object') { 22 | platform = [] 23 | for (i = 0; i < arguments[0].length; i++) { 24 | if (VALID_DEVICE_TYPES.indexOf(arguments[0][i]) !== -1) { 25 | if (platform.indexOf(arguments[0][i]) === -1) { 26 | platform.push(arguments[0][i]) 27 | } 28 | } else { 29 | throw new JError.InvalidArgumentError("Invalid device type '" + arguments[0][i] + 30 | "', platform can only be set to 'android', 'ios' or 'winPhone'") 31 | } 32 | } 33 | } else { 34 | platform = [] 35 | for (i = 0; i < arguments.length; i++) { 36 | if (VALID_DEVICE_TYPES.indexOf(arguments[i]) !== -1) { 37 | if (platform.indexOf(arguments[i]) === -1) { 38 | platform.push(arguments[i]) 39 | } 40 | } else { 41 | throw new JError.InvalidArgumentError("Invalid device type '" + arguments[i] + 42 | "', platform can only be set to 'android', 'ios' or 'winPhone'") 43 | } 44 | } 45 | } 46 | this.payload = JUtil.extend(this.payload, { 47 | 'platform': platform 48 | }) 49 | return this 50 | } 51 | 52 | function buildAudience (args, title) { 53 | if (args.length < 1) { 54 | throw new JError.InvalidArgumentError('Should be set at least one ' + title) 55 | } 56 | var payload = [] 57 | var i 58 | if (args.length === 1 && typeof args[0] === 'string') { 59 | var tags_t = args[0].split(',') 60 | for (i = 0; i < tags_t.length; i++) { 61 | if (tags_t[i].trim().length > 0) { 62 | payload.push(tags_t[i].trim()) 63 | } 64 | } 65 | if (payload.length < 1) { 66 | throw new JError.InvalidArgumentError('Should be set at least one ' + title) 67 | } 68 | } else if (args.length === 1 && Array.isArray(args[0])) { 69 | for (i = 0; i < args[0].length; i++) { 70 | if (typeof args[0][i] !== 'string') { 71 | throw new JError.InvalidArgumentError('Invalid ' + title + ' at index ' + i + ', ' + 72 | title + ' can only be set to the String') 73 | } 74 | payload.push(args[0][i]) 75 | } 76 | } else { 77 | for (i = 0; i < args.length; i++) { 78 | if (typeof args[i] !== 'string') { 79 | throw new JError.InvalidArgumentError('Invalid ' + title + ' at argument ' + i + ', ' + 80 | title + ' can only be set to the String') 81 | } 82 | payload.push(args[i]) 83 | } 84 | } 85 | return payload 86 | } 87 | 88 | function alias () { 89 | return { 90 | 'alias': buildAudience(arguments, 'alias') 91 | } 92 | } 93 | 94 | function tag () { 95 | return { 96 | 'tag': buildAudience(arguments, 'tag') 97 | } 98 | } 99 | 100 | function tag_and () { 101 | return { 102 | 'tag_and': buildAudience(arguments, 'tag_and') 103 | } 104 | } 105 | 106 | function tag_not() { 107 | return { 108 | 'tag_not': buildAudience(arguments, 'tag_not') 109 | } 110 | } 111 | 112 | function registration_id () { 113 | return { 114 | 'registration_id': buildAudience(arguments, 'registration_id') 115 | } 116 | } 117 | 118 | function segment() { 119 | return { 120 | 'segment': buildAudience(arguments, 'segment') 121 | } 122 | } 123 | 124 | function abtest() { 125 | return { 126 | 'abtest': buildAudience(arguments, 'abtest') 127 | } 128 | } 129 | 130 | function setAudience () { 131 | if (arguments.length < 1) { 132 | throw new JError.InvalidArgumentError('audience must be set') 133 | } 134 | var audience 135 | if (arguments.length === 1 && arguments[0] === ALL) { 136 | audience = ALL 137 | } else { 138 | audience = {} 139 | for (var i = 0; i < arguments.length; i++) { 140 | audience = JUtil.extend(audience, arguments[i]) 141 | } 142 | } 143 | this.payload = JUtil.extend(this.payload, { 144 | 'audience': audience 145 | }) 146 | return this 147 | } 148 | 149 | function android (alert, title, builder_id, extras, priority, category, style, value, alertType, channel_id, uri_activity, uri_action, badge_class, badge_add_num) { 150 | if (alert != null) { 151 | if (typeof alert !== 'string') { 152 | throw new JError.InvalidArgumentError('android.alert is require and only can be set to the string') 153 | } 154 | } 155 | var android = { 156 | 'alert': alert 157 | } 158 | 159 | if (title != null) { 160 | if (typeof title !== 'string') { 161 | throw new JError.InvalidArgumentError('Invalid android.title, it only can be set to the string') 162 | } 163 | android['title'] = title 164 | } 165 | 166 | if (builder_id != null) { 167 | if (typeof builder_id !== 'number') { 168 | throw new JError.InvalidArgumentError('Invalid android.builder_id, it only can be set to the number') 169 | } 170 | android['builder_id'] = builder_id 171 | } 172 | 173 | if (channel_id != null) { 174 | if (typeof channel_id !== 'string') { 175 | throw new JError.InvalidArgumentError('Invalid android.channel_id, it only can be set to the string') 176 | } 177 | android['channel_id'] = channel_id 178 | } 179 | 180 | if (extras != null) { 181 | if (typeof extras !== 'object') { 182 | throw new JError.InvalidArgumentError('Invalid android.extras') 183 | } 184 | android['extras'] = extras 185 | } 186 | 187 | if (priority != null) { 188 | if (typeof priority !== 'number') { 189 | throw new JError.InvalidArgumentError('Invalid android.priority, it only can be set to the number.') 190 | } 191 | android['priority'] = priority 192 | } 193 | 194 | if (category != null) { 195 | if (typeof category !== 'string') { 196 | throw new JError.InvalidArgumentError('Invalid android.category, it only can be set to the number.') 197 | } 198 | android['category'] = category 199 | } 200 | 201 | if (style != null) { 202 | if (typeof style !== 'number') { 203 | throw new JError.InvalidArgumentError('Invalid android.style, it only can be set to the number.') 204 | } 205 | if (style === 1) { 206 | android['big_text'] = value 207 | } else if (style === 2) { 208 | android['inbox'] = value 209 | } else if (style === 3) { 210 | android['big_pic_path'] = value 211 | } 212 | android['style'] = style 213 | } 214 | 215 | if (alertType != null) { 216 | if (typeof alertType !== 'number') { 217 | throw new JError.InvalidArgumentError('Invalid android.alertType, it only can be set to the number.') 218 | } 219 | android['alert_type'] = alertType 220 | } 221 | 222 | if (uri_activity != null) { 223 | if (typeof uri_activity !== 'string') { 224 | throw new JError.InvalidArgumentError('Invalid android.uri_activity, it only can be set to the string.') 225 | } 226 | android['uri_activity'] = uri_activity 227 | } 228 | 229 | if (uri_action != null) { 230 | if (typeof uri_action !== 'string') { 231 | throw new JError.InvalidArgumentError('Invalid android.uri_action, it only can be set to the string.') 232 | } 233 | android['uri_action'] = uri_action 234 | } 235 | 236 | if (badge_class != null) { 237 | if (typeof badge_class !== 'string') { 238 | throw new JError.InvalidArgumentError('Invalid android.badge_class, it only can be set to the string.') 239 | } 240 | android['badge_class'] = badge_class 241 | } 242 | 243 | if (badge_add_num != null) { 244 | if (typeof badge_add_num !== 'number') { 245 | throw new JError.InvalidArgumentError('Invalid android.badge_class, it only can be set to the number.') 246 | } 247 | android['badge_add_num'] = badge_add_num 248 | } 249 | 250 | 251 | return { 252 | 'android': android 253 | } 254 | } 255 | 256 | function ios (alert, sound, badge, contentAvailable, extras, category, mutableContent) { 257 | if (alert != null) { 258 | if (typeof alert !== 'string' && typeof alert !== 'object') { 259 | throw new JError.InvalidArgumentError('ios.alert is require and can only be set to the String or object') 260 | } 261 | } 262 | var ios = { 263 | 'alert': alert 264 | } 265 | 266 | if (sound != null) { 267 | if (typeof sound !== 'string') { 268 | throw new JError.InvalidArgumentError('Invalid ios.sound, it can only be set to the String') 269 | } 270 | ios['sound'] = sound 271 | } 272 | 273 | if (badge != null) { 274 | ios['badge'] = badge 275 | } 276 | 277 | if (contentAvailable != null) { 278 | if (typeof contentAvailable !== 'boolean') { 279 | throw new JError.InvalidArgumentError('Invalid ios.contentAvailable, it can only be set to the Boolean') 280 | } 281 | ios['content-available'] = contentAvailable 282 | } 283 | 284 | if (extras != null) { 285 | if (typeof extras !== 'object') { 286 | throw new JError.InvalidArgumentError('Invalid ios.extras') 287 | } 288 | ios['extras'] = extras 289 | } 290 | 291 | if (category != null) { 292 | ios['category'] = category 293 | } 294 | 295 | if (mutableContent != null) { 296 | if (typeof mutableContent !== 'boolean') { 297 | throw new JError.InvalidArgumentError('Invalid ios.mutable-content, it can only be set to the boolean.') 298 | } 299 | ios['mutable-content'] = mutableContent 300 | } 301 | return { 302 | 'ios': ios 303 | } 304 | } 305 | 306 | function winphone (alert, title, openPage, extras) { 307 | if (alert != null) { 308 | if (typeof alert !== 'string') { 309 | throw new JError.InvalidArgumentError('winphone.alert is require and can only be set to the String') 310 | } 311 | } 312 | 313 | var winphone = { 314 | 'alert': alert 315 | } 316 | 317 | if (title != null) { 318 | if (typeof title !== 'string') { 319 | throw new JError.InvalidArgumentError('Invalid winphone.title, it can only be set to the String') 320 | } 321 | winphone['title'] = title 322 | } 323 | 324 | if (openPage != null) { 325 | if (typeof openPage !== 'string') { 326 | throw new JError.InvalidArgumentError('Invalid winphone.openPage, it can only be set to the String') 327 | } 328 | winphone['_open_page'] = openPage 329 | } 330 | 331 | if (extras != null) { 332 | if (typeof extras !== 'object') { 333 | throw new JError.InvalidArgumentError('Invalid winphone.extras') 334 | } 335 | winphone['extras'] = extras 336 | } 337 | 338 | return { 339 | 'winphone': winphone 340 | } 341 | } 342 | 343 | function setNotification () { 344 | if (arguments.length < 1) { 345 | throw new JError.InvalidArgumentError('Invalid notification') 346 | } 347 | var notification = {} 348 | var offset = 0 349 | if (typeof arguments[0] === 'string') { 350 | notification['alert'] = arguments[0] 351 | offset = 1 352 | } 353 | for (; offset < arguments.length; offset++) { 354 | if (typeof arguments[offset] !== 'object') { 355 | throw new JError.InvalidArgumentError('Invalid notification argument at index ' + offset) 356 | } 357 | notification = JUtil.extend(notification, arguments[offset]) 358 | } 359 | this.payload = JUtil.extend(this.payload, { 360 | 'notification': notification 361 | }) 362 | return this 363 | } 364 | 365 | function setMessage (msg_content, title, content_type, extras) { 366 | if (msg_content == null || typeof msg_content !== 'string') { 367 | throw new JError.InvalidArgumentError('message.msg_content is require and can only be set to the String') 368 | } 369 | var message = { 370 | 'msg_content': msg_content 371 | } 372 | 373 | if (title != null) { 374 | if (typeof title !== 'string') { 375 | throw new JError.InvalidArgumentError('Invalid message.title, it can only be set to the String') 376 | } 377 | message['title'] = title 378 | } 379 | 380 | if (content_type != null) { 381 | if (typeof content_type !== 'string') { 382 | throw new JError.InvalidArgumentError('Invalid message.content_type, it can only be set to the String') 383 | } 384 | message['content_type'] = content_type 385 | } 386 | 387 | if (extras != null) { 388 | if (typeof extras !== 'object') { 389 | throw new JError.InvalidArgumentError('Invalid message.extras') 390 | } 391 | message['extras'] = extras 392 | } 393 | 394 | this.payload = JUtil.extend(this.payload, { 395 | 'message': message 396 | }) 397 | return this 398 | } 399 | 400 | function setSmsMessage (content, delayTime) { 401 | var smsMessage = {} 402 | if (content != null) { 403 | if (typeof content !== 'string') { 404 | throw new JError.InvalidArgumentError('Invalid content, it can only be set to a string') 405 | } 406 | smsMessage['content'] = content 407 | } 408 | 409 | if (delayTime != null) { 410 | if (typeof delayTime !== 'number') { 411 | throw new JError.InvalidArgumentError('Invalid delayTime, it can only be set to a int') 412 | } 413 | smsMessage['delay_time'] = delayTime 414 | } 415 | 416 | this.payload = JUtil.extend(this.payload, { 417 | 'sms_message': smsMessage 418 | }) 419 | return this 420 | } 421 | 422 | function generateSendno () { 423 | return (MIN_SENDNO + Math.round(Math.random() * (MAX_SENDNO - MIN_SENDNO))) 424 | } 425 | 426 | function setOptions (sendno, time_to_live, override_msg_id, apns_production, big_push_duration, apns_collapse_id, third_party_channel) { 427 | if (sendno == null && time_to_live == null && override_msg_id == null && apns_production == null && 428 | big_push_duration == null && third_party_channel == null) { 429 | throw new JError.InvalidArgumentError("option's args cannot all be null.") 430 | } 431 | var options = {} 432 | 433 | if (sendno != null) { 434 | if (typeof sendno !== 'number') { 435 | throw new JError.InvalidArgumentError('Invalid options.sendno, it can only be set to the Number') 436 | } 437 | options['sendno'] = sendno 438 | } else { 439 | options['sendno'] = generateSendno() 440 | } 441 | 442 | if (time_to_live != null) { 443 | if (typeof time_to_live !== 'number') { 444 | throw new JError.InvalidArgumentError('Invalid options.time_to_live, it can only be set to the Number') 445 | } 446 | options['time_to_live'] = time_to_live 447 | } 448 | 449 | if (override_msg_id != null) { 450 | if (typeof override_msg_id !== 'number') { 451 | throw new JError.InvalidArgumentError('Invalid options.override_msg_id, it can only be set to the Number') 452 | } 453 | options['override_msg_id'] = override_msg_id 454 | } 455 | 456 | // true: 推送生产环境;false: 推送开发环境。 457 | if (apns_production != null) { 458 | if (typeof apns_production !== 'boolean') { 459 | throw new JError.InvalidArgumentError('Invalid options.apns_production, it can only be set to the Boolean') 460 | } 461 | options['apns_production'] = apns_production 462 | } else { 463 | options['apns_production'] = true // 如果不指定,默认推送生产环境。 464 | } 465 | 466 | if (big_push_duration != null) { // 又叫缓慢推送,设置后将在给定的 n 分钟内,匀速的向用户发送推送,最大值为 1400。 467 | if (typeof big_push_duration !== 'number') { 468 | throw new JError.InvalidArgumentError('Invalid options.big_push_duration, it can only be set to the Number') 469 | } 470 | 471 | if (big_push_duration > 1400 || big_push_duration <= 0) { 472 | throw new JError.InvalidArgumentError('Invalid options.big_push_duration, it should bigger than 0 and less than 1400') 473 | } 474 | options['big_push_duration'] = big_push_duration 475 | } 476 | 477 | if (apns_collapse_id != null) { 478 | options['apns_collapse_id'] = apns_collapse_id 479 | } 480 | 481 | if (third_party_channel != null) { 482 | if (typeof third_party_channel !== 'object') { 483 | throw new JError.InvalidArgumentError('Invalid options.third_part_channel, it can only be set to JSON Object') 484 | } 485 | options['third_party_channel'] = third_party_channel 486 | } 487 | 488 | this.payload = JUtil.extend(this.payload, { 489 | 'options': options 490 | }) 491 | console.info(options) 492 | return this 493 | } 494 | 495 | function toJSON () { 496 | this.payload.options = JUtil.extend({ 497 | 'sendno': generateSendno(), 498 | 'apns_production': false 499 | }, this.payload.options) 500 | return JSON.stringify(this.payload) 501 | } 502 | 503 | function send (callback) { 504 | validate(this.payload) 505 | var body = this.toJSON() 506 | return this.client.sendPush(body, callback) 507 | } 508 | 509 | function sendValidate (callback) { 510 | validate(this.payload) 511 | var body = this.toJSON() 512 | return this.client.validate(body, callback) 513 | } 514 | 515 | // 定时任务 start 516 | // date: 必须为 YYYY-MM-DD HH:MM:SS 格式,如:"2014-02-15 12:00:00" 517 | function setSingleSchedule (date) { 518 | if (typeof date !== 'string') { 519 | throw new JError.InvalidArgumentError('date must be set to the string.') 520 | } 521 | var single = { 522 | 'time': date 523 | } 524 | this.trigger = JUtil.extend(this.trigger, { 525 | 'single': single 526 | }) 527 | return this 528 | } 529 | 530 | // 具体参数格式参照:http://docs.jiguang.cn/server/rest_api_push_schedule/#_4 531 | function setPeriodicalSchedule (start, end, time, timeUnit, frequency, point) { 532 | var periodical = { 533 | 'start': start, 534 | 'end': end, 535 | 'time': time, 536 | 'time_unit': timeUnit, 537 | 'frequency': frequency, 538 | 'point': point 539 | } 540 | this.trigger = JUtil.extend(this.trigger, { 541 | 'periodical': periodical 542 | }) 543 | return this 544 | } 545 | 546 | function setSchedule (name, enabled, callback) { 547 | if (typeof name !== 'string') { 548 | throw new JError.InvalidArgumentError('name must be set to string.') 549 | } 550 | if (typeof enabled !== 'boolean') { 551 | throw new JError.InvalidArgumentError('enabled must be set to boolean.') 552 | } 553 | validate(this.payload) 554 | this.payload.options = JUtil.extend({ 555 | 'sendno': generateSendno(), 556 | 'apns_production': false 557 | }, 558 | this.payload.options) 559 | var body = { 560 | 'name': name, 561 | 'enabled': enabled, 562 | 'trigger': this.trigger, 563 | 'push': this.payload 564 | } 565 | return this.client.setSchedule(JSON.stringify(body), callback) 566 | } 567 | 568 | // scheduleId: 必填。 569 | // name, enabled 如果为 null,则代表不更新。 570 | function updateSchedule (scheduleId, name, enabled, callback) { 571 | if (typeof scheduleId !== 'string') { 572 | throw new JError.InvalidArgumentError('Schedule ID must be set to string.') 573 | } 574 | var body = {} 575 | if (name != null) { 576 | if (typeof name !== 'string') { 577 | throw new JError.InvalidArgumentError('name must be set to string.') 578 | } 579 | body = JUtil.extend(body, { 580 | 'name': name 581 | }) 582 | } 583 | if (enabled != null) { 584 | if (typeof enabled !== 'boolean') { 585 | throw new JError.InvalidArgumentError('enabled must be set to boolean.') 586 | } 587 | body = JUtil.extend(body, { 588 | 'enabled': enabled 589 | }) 590 | } 591 | if (!JUtil.isEmptyObject(this.trigger)) { 592 | body = JUtil.extend(body, { 593 | 'trigger': this.trigger 594 | }) 595 | } 596 | if (!JUtil.isEmptyObject(this.payload)) { 597 | validate(this.payload) 598 | this.payload.options = JUtil.extend({ 599 | 'sendno': generateSendno(), 600 | 'apns_production': false 601 | }, 602 | this.payload.options) 603 | body = JUtil.extend(body, { 604 | 'push': this.payload 605 | }) 606 | } 607 | return this.client.updateSchedule(scheduleId, JSON.stringify(body), callback) 608 | } 609 | // 定时任务 end. 610 | 611 | /** 612 | * Verify the payload legitimacy, it will call by this.send() 613 | * @param payload 614 | */ 615 | function validate (payload) { 616 | var notification = payload.notification 617 | var message = payload.message 618 | if (!notification && !message) { 619 | throw new JError.InvalidArgumentError('Either or both notification and message must be set.') 620 | } 621 | } 622 | 623 | function calculateLength (str) { 624 | var ch = [] 625 | var st = [] 626 | var re = [] 627 | for (var i = 0; i < str.length; i++) { 628 | ch = str.charCodeAt(i) 629 | st = [] 630 | do { 631 | st.push(ch & 0xFF) 632 | ch = ch >> 8 633 | } while (ch) 634 | re = re.concat(st.reverse()) 635 | } 636 | // return an array of bytes. 637 | return re.length 638 | } 639 | 640 | function isIosExceedLength () { 641 | var ios 642 | var notification = this.payload.notification 643 | var message = this.payload.message 644 | var alert = notification.alert ? notification.alert : '' 645 | ios = calculateLength(JSON.stringify(JUtil.extend({ 646 | 'alert': alert 647 | }, notification.ios))) 648 | if (message != null) { 649 | var msgLen = calculateLength(JSON.stringify(message)) 650 | return msgLen >= 1000 651 | } 652 | return ios >= 2000 653 | } 654 | 655 | function isGlobalExceedLength () { 656 | var android = 0 657 | var winphone = 0 658 | var ios = false 659 | var notification = this.payload.notification 660 | var message = this.payload.message 661 | var platform = this.payload.platform 662 | 663 | var hasIOS = true 664 | if (platform !== ALL) { 665 | hasIOS = false 666 | for (var i = 0; i < platform.length; i++) { 667 | if (platform[i] === 'ios') { 668 | hasIOS = true 669 | break 670 | } 671 | } 672 | } 673 | 674 | if (hasIOS) { 675 | ios = this.isIosExceedLength() 676 | } 677 | 678 | if (notification != null) { 679 | var alert = notification.alert ? notification.alert : '' 680 | winphone = calculateLength(JSON.stringify(JUtil.extend({ 681 | 'alert': alert 682 | }, notification.winphone))) 683 | android = calculateLength(JSON.stringify(JUtil.extend({ 684 | 'alert': alert 685 | }, notification.android))) 686 | } 687 | if (message != null) { 688 | var msg_length = calculateLength(JSON.stringify(message)) 689 | winphone += msg_length 690 | android += msg_length 691 | } 692 | return ios || winphone > 1000 || android > 1000 693 | } 694 | 695 | // ------ PushPayload prototype 696 | PushPayload.prototype.setPlatform = setPlatform 697 | PushPayload.prototype.setAudience = setAudience 698 | PushPayload.prototype.setNotification = setNotification 699 | PushPayload.prototype.setMessage = setMessage 700 | PushPayload.prototype.setOptions = setOptions 701 | PushPayload.prototype.toJSON = toJSON 702 | PushPayload.prototype.send = send 703 | PushPayload.prototype.sendValidate = sendValidate 704 | PushPayload.prototype.setSingleSchedule = setSingleSchedule 705 | PushPayload.prototype.setPeriodicalSchedule = setPeriodicalSchedule 706 | PushPayload.prototype.setSchedule = setSchedule 707 | PushPayload.prototype.updateSchedule = updateSchedule 708 | PushPayload.prototype.isIosExceedLength = isIosExceedLength 709 | PushPayload.prototype.isGlobalExceedLength = isGlobalExceedLength 710 | PushPayload.prototype.setSmsMessage = setSmsMessage 711 | 712 | // ------ private constant define ------ 713 | var VALID_DEVICE_TYPES = ['ios', 'android', 'winphone'] 714 | var MIN_SENDNO = 100000 715 | var MAX_SENDNO = 4294967294 716 | var ALL = 'all' 717 | 718 | // ------ exports constants and methods ------- 719 | exports.ALL = ALL 720 | exports.tag = tag 721 | exports.tag_and = tag_and 722 | exports.tag_not = tag_not 723 | exports.alias = alias 724 | exports.registration_id = registration_id 725 | exports.segment = segment 726 | exports.abtest = abtest 727 | exports.ios = ios 728 | exports.android = android 729 | exports.winphone = winphone 730 | exports.setSchedule = setSchedule 731 | // class 732 | exports.PushPayload = PushPayload 733 | -------------------------------------------------------------------------------- /lib/JPush/PushPayloadAsync.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | 3 | var JError = require('./JPushError') 4 | var JUtil = require('./util') 5 | 6 | function PushPayload (client) { 7 | this.client = client 8 | this.payload = {} 9 | this.trigger = {} 10 | } 11 | 12 | function setPlatform () { 13 | if (arguments.length < 1) { 14 | throw new JError.InvalidArgumentError("platform's args cannot all be null") 15 | } 16 | 17 | var platform, i 18 | 19 | if (arguments.length === 1 && arguments[0] === ALL) { 20 | platform = ALL 21 | } else if (arguments.length === 1 && typeof arguments[0] === 'object') { 22 | platform = [] 23 | for (i = 0; i < arguments[0].length; i++) { 24 | if (VALID_DEVICE_TYPES.indexOf(arguments[0][i]) !== -1) { 25 | if (platform.indexOf(arguments[0][i]) === -1) { 26 | platform.push(arguments[0][i]) 27 | } 28 | } else { 29 | throw new JError.InvalidArgumentError("Invalid device type '" + arguments[0][i] + 30 | "', platform can only be set to 'android', 'ios' or 'winPhone'") 31 | } 32 | } 33 | } else { 34 | platform = [] 35 | for (i = 0; i < arguments.length; i++) { 36 | if (VALID_DEVICE_TYPES.indexOf(arguments[i]) !== -1) { 37 | if (platform.indexOf(arguments[i]) === -1) { 38 | platform.push(arguments[i]) 39 | } 40 | } else { 41 | throw new JError.InvalidArgumentError("Invalid device type '" + arguments[i] + 42 | "', platform can only be set to 'android', 'ios' or 'winPhone'") 43 | } 44 | } 45 | } 46 | this.payload = JUtil.extend(this.payload, { 47 | 'platform': platform 48 | }) 49 | return this 50 | } 51 | 52 | function buildAudience (args, title) { 53 | if (args.length < 1) { 54 | throw new JError.InvalidArgumentError('Should be set at least one ' + title) 55 | } 56 | var payload = [] 57 | var i 58 | if (args.length === 1 && typeof args[0] === 'string') { 59 | var tags_t = args[0].split(',') 60 | for (i = 0; i < tags_t.length; i++) { 61 | if (tags_t[i].trim().length > 0) { 62 | payload.push(tags_t[i].trim()) 63 | } 64 | } 65 | if (payload.length < 1) { 66 | throw new JError.InvalidArgumentError('Should be set at least one ' + title) 67 | } 68 | } else if (args.length === 1 && Array.isArray(args[0])) { 69 | for (i = 0; i < args[0].length; i++) { 70 | if (typeof args[0][i] !== 'string') { 71 | throw new JError.InvalidArgumentError('Invalid ' + title + ' at index ' + i + ', ' + 72 | title + ' can only be set to the String') 73 | } 74 | payload.push(args[0][i]) 75 | } 76 | } else { 77 | for (i = 0; i < args.length; i++) { 78 | if (typeof args[i] !== 'string') { 79 | throw new JError.InvalidArgumentError('Invalid ' + title + ' at argument ' + i + ', ' + 80 | title + ' can only be set to the String') 81 | } 82 | payload.push(args[i]) 83 | } 84 | } 85 | return payload 86 | } 87 | 88 | function alias () { 89 | return { 90 | 'alias': buildAudience(arguments, 'alias') 91 | } 92 | } 93 | 94 | function tag () { 95 | return { 96 | 'tag': buildAudience(arguments, 'tag') 97 | } 98 | } 99 | 100 | function tag_and () { 101 | return { 102 | 'tag_and': buildAudience(arguments, 'tag_and') 103 | } 104 | } 105 | 106 | function tag_not() { 107 | return { 108 | 'tag_not': buildAudience(arguments, 'tag_not') 109 | } 110 | } 111 | 112 | function registration_id () { 113 | return { 114 | 'registration_id': buildAudience(arguments, 'registration_id') 115 | } 116 | } 117 | 118 | function segment() { 119 | return { 120 | 'segment': buildAudience(arguments, 'segment') 121 | } 122 | } 123 | 124 | function abtest() { 125 | return { 126 | 'abtest': buildAudience(arguments, 'abtest') 127 | } 128 | } 129 | 130 | function setAudience () { 131 | if (arguments.length < 1) { 132 | throw new JError.InvalidArgumentError('audience must be set') 133 | } 134 | var audience 135 | if (arguments.length === 1 && arguments[0] === ALL) { 136 | audience = ALL 137 | } else { 138 | audience = {} 139 | for (var i = 0; i < arguments.length; i++) { 140 | audience = JUtil.extend(audience, arguments[i]) 141 | } 142 | } 143 | this.payload = JUtil.extend(this.payload, { 144 | 'audience': audience 145 | }) 146 | return this 147 | } 148 | 149 | function android (alert, title, builder_id, extras, priority, category, style, value, alertType, channel_id) { 150 | if (alert != null) { 151 | if (typeof alert !== 'string') { 152 | throw new JError.InvalidArgumentError('android.alert is require and only can be set to the string') 153 | } 154 | } 155 | var android = { 156 | 'alert': alert 157 | } 158 | 159 | if (title != null) { 160 | if (typeof title !== 'string') { 161 | throw new JError.InvalidArgumentError('Invalid android.title, it only can be set to the string') 162 | } 163 | android['title'] = title 164 | } 165 | 166 | if (builder_id != null) { 167 | if (typeof builder_id !== 'number') { 168 | throw new JError.InvalidArgumentError('Invalid android.builder_id, it only can be set to the number') 169 | } 170 | android['builder_id'] = builder_id 171 | } 172 | 173 | if (channel_id != null) { 174 | if (typeof channel_id !== 'string') { 175 | throw new JError.InvalidArgumentError('Invalid android.channel_id, it only can be set to the string') 176 | } 177 | android['channel_id'] = channel_id 178 | } 179 | 180 | if (extras != null) { 181 | if (typeof extras !== 'object') { 182 | throw new JError.InvalidArgumentError('Invalid android.extras') 183 | } 184 | android['extras'] = extras 185 | } 186 | 187 | if (priority != null) { 188 | if (typeof priority !== 'number') { 189 | throw new JError.InvalidArgumentError('Invalid android.priority, it only can be set to the number.') 190 | } 191 | android['priority'] = priority 192 | } 193 | 194 | if (category != null) { 195 | if (typeof category !== 'string') { 196 | throw new JError.InvalidArgumentError('Invalid android.category, it only can be set to the number.') 197 | } 198 | android['category'] = category 199 | } 200 | 201 | if (style != null) { 202 | if (typeof style !== 'number') { 203 | throw new JError.InvalidArgumentError('Invalid android.style, it only can be set to the number.') 204 | } 205 | if (style === 1) { 206 | android['big_text'] = value 207 | } else if (style === 2) { 208 | android['inbox'] = value 209 | } else if (style === 3) { 210 | android['big_pic_path'] = value 211 | } 212 | } 213 | 214 | if (alertType != null) { 215 | if (typeof alertType !== 'number') { 216 | throw new JError.InvalidArgumentError('Invalid android.alertType, it only can be set to the number.') 217 | } 218 | android['alert_type'] = alertType 219 | } 220 | 221 | return { 222 | 'android': android 223 | } 224 | } 225 | 226 | function ios (alert, sound, badge, contentAvailable, extras, category, mutableContent) { 227 | if (alert != null) { 228 | if (typeof alert !== 'string' && typeof alert !== 'object') { 229 | throw new JError.InvalidArgumentError('ios.alert is require and can only be set to the String or object') 230 | } 231 | } 232 | var ios = { 233 | 'alert': alert 234 | } 235 | 236 | if (sound != null) { 237 | if (typeof sound !== 'string') { 238 | throw new JError.InvalidArgumentError('Invalid ios.sound, it can only be set to the String') 239 | } 240 | ios['sound'] = sound 241 | } 242 | 243 | if (badge != null) { 244 | ios['badge'] = badge 245 | } 246 | 247 | if (contentAvailable != null) { 248 | if (typeof contentAvailable !== 'boolean') { 249 | throw new JError.InvalidArgumentError('Invalid ios.contentAvailable, it can only be set to the Boolean') 250 | } 251 | ios['content-available'] = contentAvailable 252 | } 253 | 254 | if (extras != null) { 255 | if (typeof extras !== 'object') { 256 | throw new JError.InvalidArgumentError('Invalid ios.extras') 257 | } 258 | ios['extras'] = extras 259 | } 260 | 261 | if (category != null) { 262 | ios['category'] = category 263 | } 264 | 265 | if (mutableContent != null) { 266 | if (typeof mutableContent !== 'boolean') { 267 | throw new JError.InvalidArgumentError('Invalid ios.mutable-content, it can only be set to the boolean.') 268 | } 269 | ios['mutable-content'] = mutableContent 270 | } 271 | return { 272 | 'ios': ios 273 | } 274 | } 275 | 276 | function winphone (alert, title, openPage, extras) { 277 | if (alert != null) { 278 | if (typeof alert !== 'string') { 279 | throw new JError.InvalidArgumentError('winphone.alert is require and can only be set to the String') 280 | } 281 | } 282 | 283 | var winphone = { 284 | 'alert': alert 285 | } 286 | 287 | if (title != null) { 288 | if (typeof title !== 'string') { 289 | throw new JError.InvalidArgumentError('Invalid winphone.title, it can only be set to the String') 290 | } 291 | winphone['title'] = title 292 | } 293 | 294 | if (openPage != null) { 295 | if (typeof openPage !== 'string') { 296 | throw new JError.InvalidArgumentError('Invalid winphone.openPage, it can only be set to the String') 297 | } 298 | winphone['_open_page'] = openPage 299 | } 300 | 301 | if (extras != null) { 302 | if (typeof extras !== 'object') { 303 | throw new JError.InvalidArgumentError('Invalid winphone.extras') 304 | } 305 | winphone['extras'] = extras 306 | } 307 | 308 | return { 309 | 'winphone': winphone 310 | } 311 | } 312 | 313 | function setNotification () { 314 | if (arguments.length < 1) { 315 | throw new JError.InvalidArgumentError('Invalid notification') 316 | } 317 | var notification = {} 318 | var offset = 0 319 | if (typeof arguments[0] === 'string') { 320 | notification['alert'] = arguments[0] 321 | offset = 1 322 | } 323 | for (; offset < arguments.length; offset++) { 324 | if (typeof arguments[offset] !== 'object') { 325 | throw new JError.InvalidArgumentError('Invalid notification argument at index ' + offset) 326 | } 327 | notification = JUtil.extend(notification, arguments[offset]) 328 | } 329 | this.payload = JUtil.extend(this.payload, { 330 | 'notification': notification 331 | }) 332 | return this 333 | } 334 | 335 | function setMessage (msg_content, title, content_type, extras) { 336 | if (msg_content == null || typeof msg_content !== 'string') { 337 | throw new JError.InvalidArgumentError('message.msg_content is require and can only be set to the String') 338 | } 339 | var message = { 340 | 'msg_content': msg_content 341 | } 342 | 343 | if (title != null) { 344 | if (typeof title !== 'string') { 345 | throw new JError.InvalidArgumentError('Invalid message.title, it can only be set to the String') 346 | } 347 | message['title'] = title 348 | } 349 | 350 | if (content_type != null) { 351 | if (typeof content_type !== 'string') { 352 | throw new JError.InvalidArgumentError('Invalid message.content_type, it can only be set to the String') 353 | } 354 | message['content_type'] = content_type 355 | } 356 | 357 | if (extras != null) { 358 | if (typeof extras !== 'object') { 359 | throw new JError.InvalidArgumentError('Invalid message.extras') 360 | } 361 | message['extras'] = extras 362 | } 363 | 364 | this.payload = JUtil.extend(this.payload, { 365 | 'message': message 366 | }) 367 | return this 368 | } 369 | 370 | function setSmsMessage (content, delayTime) { 371 | var smsMessage = {} 372 | if (content != null) { 373 | if (typeof content !== 'string') { 374 | throw new JError.InvalidArgumentError('Invalid content, it can only be set to a string') 375 | } 376 | smsMessage['content'] = content 377 | } 378 | 379 | if (delayTime != null) { 380 | if (typeof delayTime !== 'number') { 381 | throw new JError.InvalidArgumentError('Invalid delayTime, it can only be set to a int') 382 | } 383 | smsMessage['delay_time'] = delayTime 384 | } 385 | 386 | this.payload = JUtil.extend(this.payload, { 387 | 'sms_message': smsMessage 388 | }) 389 | return this 390 | } 391 | 392 | function generateSendno () { 393 | return (MIN_SENDNO + Math.round(Math.random() * (MAX_SENDNO - MIN_SENDNO))) 394 | } 395 | 396 | function setOptions (sendno, time_to_live, override_msg_id, apns_production, big_push_duration, apns_collapse_id, third_party_channel) { 397 | if (sendno == null && time_to_live == null && override_msg_id == null && apns_production == null && 398 | big_push_duration == null && third_party_channel == null) { 399 | throw new JError.InvalidArgumentError("option's args cannot all be null.") 400 | } 401 | var options = {} 402 | 403 | if (sendno != null) { 404 | if (typeof sendno !== 'number') { 405 | throw new JError.InvalidArgumentError('Invalid options.sendno, it can only be set to the Number') 406 | } 407 | options['sendno'] = sendno 408 | } else { 409 | options['sendno'] = generateSendno() 410 | } 411 | 412 | if (time_to_live != null) { 413 | if (typeof time_to_live !== 'number') { 414 | throw new JError.InvalidArgumentError('Invalid options.time_to_live, it can only be set to the Number') 415 | } 416 | options['time_to_live'] = time_to_live 417 | } 418 | 419 | if (override_msg_id != null) { 420 | if (typeof override_msg_id !== 'number') { 421 | throw new JError.InvalidArgumentError('Invalid options.override_msg_id, it can only be set to the Number') 422 | } 423 | options['override_msg_id'] = override_msg_id 424 | } 425 | 426 | // true: 推送生产环境;false: 推送开发环境。 427 | if (apns_production != null) { 428 | if (typeof apns_production !== 'boolean') { 429 | throw new JError.InvalidArgumentError('Invalid options.apns_production, it can only be set to the Boolean') 430 | } 431 | options['apns_production'] = apns_production 432 | } else { 433 | options['apns_production'] = true // 如果不指定,默认推送生产环境。 434 | } 435 | 436 | if (big_push_duration != null) { // 又叫缓慢推送,设置后将在给定的 n 分钟内,匀速的向用户发送推送,最大值为 1400。 437 | if (typeof big_push_duration !== 'number') { 438 | throw new JError.InvalidArgumentError('Invalid options.big_push_duration, it can only be set to the Number') 439 | } 440 | 441 | if (big_push_duration > 1400 || big_push_duration <= 0) { 442 | throw new JError.InvalidArgumentError('Invalid options.big_push_duration, it should bigger than 0 and less than 1400') 443 | } 444 | options['big_push_duration'] = big_push_duration 445 | } 446 | 447 | if (apns_collapse_id != null) { 448 | options['apns_collapse_id'] = apns_collapse_id 449 | } 450 | 451 | if (third_party_channel != null) { 452 | if (typeof third_party_channel !== 'object') { 453 | throw new JError.InvalidArgumentError('Invalid options.third_party_channel, it can only be set to JSON Object') 454 | } 455 | options['third_party_channel'] = third_party_channel 456 | } 457 | 458 | this.payload = JUtil.extend(this.payload, { 459 | 'options': options 460 | }) 461 | return this 462 | } 463 | 464 | function toJSON () { 465 | this.payload.options = JUtil.extend({ 466 | 'sendno': generateSendno(), 467 | 'apns_production': false 468 | }, this.payload.options) 469 | return this.payload 470 | } 471 | 472 | async function send () { 473 | validate(this.payload) 474 | var body = this.toJSON() 475 | return this.client.sendPush(body) 476 | } 477 | 478 | async function sendValidate () { 479 | validate(this.payload) 480 | var body = this.toJSON() 481 | return this.client.validate(body) 482 | } 483 | 484 | // 定时任务 start 485 | // date: 必须为 YYYY-MM-DD HH:MM:SS 格式,如:"2014-02-15 12:00:00" 486 | function setSingleSchedule (date) { 487 | if (typeof date !== 'string') { 488 | throw new JError.InvalidArgumentError('date must be set to the string.') 489 | } 490 | var single = { 491 | 'time': date 492 | } 493 | this.trigger = JUtil.extend(this.trigger, { 494 | 'single': single 495 | }) 496 | return this 497 | } 498 | 499 | // 具体参数格式参照:http://docs.jiguang.cn/server/rest_api_push_schedule/#_4 500 | function setPeriodicalSchedule (start, end, time, timeUnit, frequency, point) { 501 | var periodical = { 502 | 'start': start, 503 | 'end': end, 504 | 'time': time, 505 | 'time_unit': timeUnit, 506 | 'frequency': frequency, 507 | 'point': point 508 | } 509 | this.trigger = JUtil.extend(this.trigger, { 510 | 'periodical': periodical 511 | }) 512 | return this 513 | } 514 | 515 | async function setSchedule (name, enabled) { 516 | if (typeof name !== 'string') { 517 | throw new JError.InvalidArgumentError('name must be set to string.') 518 | } 519 | if (typeof enabled !== 'boolean') { 520 | throw new JError.InvalidArgumentError('enabled must be set to boolean.') 521 | } 522 | validate(this.payload) 523 | this.payload.options = JUtil.extend({ 524 | 'sendno': generateSendno(), 525 | 'apns_production': false 526 | }, 527 | this.payload.options) 528 | var body = { 529 | 'name': name, 530 | 'enabled': enabled, 531 | 'trigger': this.trigger, 532 | 'push': this.payload 533 | } 534 | return this.client.setSchedule(body) 535 | } 536 | 537 | // scheduleId: 必填。 538 | // name, enabled 如果为 null,则代表不更新。 539 | async function updateSchedule (scheduleId, name, enabled) { 540 | if (typeof scheduleId !== 'string') { 541 | throw new JError.InvalidArgumentError('Schedule ID must be set to string.') 542 | } 543 | var body = {} 544 | if (name != null) { 545 | if (typeof name !== 'string') { 546 | throw new JError.InvalidArgumentError('name must be set to string.') 547 | } 548 | body = JUtil.extend(body, { 549 | 'name': name 550 | }) 551 | } 552 | if (enabled != null) { 553 | if (typeof enabled !== 'boolean') { 554 | throw new JError.InvalidArgumentError('enabled must be set to boolean.') 555 | } 556 | body = JUtil.extend(body, { 557 | 'enabled': enabled 558 | }) 559 | } 560 | if (!JUtil.isEmptyObject(this.trigger)) { 561 | body = JUtil.extend(body, { 562 | 'trigger': this.trigger 563 | }) 564 | } 565 | if (!JUtil.isEmptyObject(this.payload)) { 566 | validate(this.payload) 567 | this.payload.options = JUtil.extend({ 568 | 'sendno': generateSendno(), 569 | 'apns_production': false 570 | }, 571 | this.payload.options) 572 | body = JUtil.extend(body, { 573 | 'push': this.payload 574 | }) 575 | } 576 | return this.client.updateSchedule(scheduleId, body) 577 | } 578 | // 定时任务 end. 579 | 580 | /** 581 | * Verify the payload legitimacy, it will call by this.send() 582 | * @param payload 583 | */ 584 | function validate (payload) { 585 | var notification = payload.notification 586 | var message = payload.message 587 | if (!notification && !message) { 588 | throw new JError.InvalidArgumentError('Either or both notification and message must be set.') 589 | } 590 | } 591 | 592 | function calculateLength (str) { 593 | var ch = [] 594 | var st = [] 595 | var re = [] 596 | for (var i = 0; i < str.length; i++) { 597 | ch = str.charCodeAt(i) 598 | st = [] 599 | do { 600 | st.push(ch & 0xFF) 601 | ch = ch >> 8 602 | } while (ch) 603 | re = re.concat(st.reverse()) 604 | } 605 | // return an array of bytes. 606 | return re.length 607 | } 608 | 609 | function isIosExceedLength () { 610 | var ios 611 | var notification = this.payload.notification 612 | var message = this.payload.message 613 | var alert = notification.alert ? notification.alert : '' 614 | ios = calculateLength(JSON.stringify(JUtil.extend({ 615 | 'alert': alert 616 | }, notification.ios))) 617 | if (message != null) { 618 | var msgLen = calculateLength(JSON.stringify(message)) 619 | return msgLen >= 1000 620 | } 621 | return ios >= 2000 622 | } 623 | 624 | function isGlobalExceedLength () { 625 | var android = 0 626 | var winphone = 0 627 | var ios = false 628 | var notification = this.payload.notification 629 | var message = this.payload.message 630 | var platform = this.payload.platform 631 | 632 | var hasIOS = true 633 | if (platform !== ALL) { 634 | hasIOS = false 635 | for (var i = 0; i < platform.length; i++) { 636 | if (platform[i] === 'ios') { 637 | hasIOS = true 638 | break 639 | } 640 | } 641 | } 642 | 643 | if (hasIOS) { 644 | ios = this.isIosExceedLength() 645 | } 646 | 647 | if (notification != null) { 648 | var alert = notification.alert ? notification.alert : '' 649 | winphone = calculateLength(JSON.stringify(JUtil.extend({ 650 | 'alert': alert 651 | }, notification.winphone))) 652 | android = calculateLength(JSON.stringify(JUtil.extend({ 653 | 'alert': alert 654 | }, notification.android))) 655 | } 656 | if (message != null) { 657 | var msg_length = calculateLength(JSON.stringify(message)) 658 | winphone += msg_length 659 | android += msg_length 660 | } 661 | return ios || winphone > 1000 || android > 1000 662 | } 663 | 664 | // ------ PushPayload prototype 665 | PushPayload.prototype.setPlatform = setPlatform 666 | PushPayload.prototype.setAudience = setAudience 667 | PushPayload.prototype.setNotification = setNotification 668 | PushPayload.prototype.setMessage = setMessage 669 | PushPayload.prototype.setOptions = setOptions 670 | PushPayload.prototype.toJSON = toJSON 671 | PushPayload.prototype.send = send 672 | PushPayload.prototype.sendValidate = sendValidate 673 | PushPayload.prototype.setSingleSchedule = setSingleSchedule 674 | PushPayload.prototype.setPeriodicalSchedule = setPeriodicalSchedule 675 | PushPayload.prototype.setSchedule = setSchedule 676 | PushPayload.prototype.updateSchedule = updateSchedule 677 | PushPayload.prototype.isIosExceedLength = isIosExceedLength 678 | PushPayload.prototype.isGlobalExceedLength = isGlobalExceedLength 679 | PushPayload.prototype.setSmsMessage = setSmsMessage 680 | 681 | // ------ private constant define ------ 682 | var VALID_DEVICE_TYPES = ['ios', 'android', 'winphone'] 683 | var MIN_SENDNO = 100000 684 | var MAX_SENDNO = 4294967294 685 | var ALL = 'all' 686 | 687 | // ------ exports constants and methods ------- 688 | exports.ALL = ALL 689 | exports.tag = tag 690 | exports.tag_and = tag_and 691 | exports.tag_not = tag_not 692 | exports.alias = alias 693 | exports.registration_id = registration_id 694 | exports.segment = segment 695 | exports.abtest = abtest 696 | exports.ios = ios 697 | exports.android = android 698 | exports.winphone = winphone 699 | exports.setSchedule = setSchedule 700 | // class 701 | exports.PushPayload = PushPayload 702 | -------------------------------------------------------------------------------- /lib/JPush/Request2.js: -------------------------------------------------------------------------------- 1 | 2 | // ===== Request2 ===== 3 | var pkg = require('./../../package.json') 4 | var debug = require('debug')('jpush') 5 | /** 6 | * @param {JPushClient} client 7 | * @param {string} url 8 | * @param {import('http').RequestOptions} options 9 | * @param {(err, res, body) => void} callback 10 | */ 11 | module.exports = function Request2(client, url, options, callback) { 12 | const baseHeaders = { 13 | 'Authorization': 'Basic ' + Buffer.from(client.appkey + ':' + client.masterSecret, 'utf8').toString('base64'), 14 | 'User-Agent': 'JPush-API-NodeJS-Client ' + pkg.version, 15 | 'Connection': 'Keep-Alive', 16 | 'Charset': 'UTF-8', 17 | 'Content-Type': 'application/json' 18 | }; 19 | const { headers, body, ..._options } = options; 20 | if (client.isDebug) console.log(JSON.stringify(body)); 21 | return new Promise((resolve, reject) => { 22 | const myURL = new URL(client.proxy || url); 23 | const http = myURL.protocol === 'https:' ? require('https') : require('http'); 24 | const req = http.request( 25 | { 26 | host: myURL.hostname, 27 | port: myURL.port, 28 | path: myURL.pathname, 29 | headers: { ...baseHeaders, ...headers }, 30 | ..._options 31 | }, 32 | resp => { 33 | resp.on('data', (res) => { 34 | if (res instanceof Buffer) { 35 | res = res.toString(); 36 | if (resp.headers['content-type'].indexOf('application/json') > -1) res = JSON.parse(res); 37 | } 38 | if (client.isDebug) debug(res); 39 | resp.statusCode === 200 ? resolve(res) : reject(res); 40 | callback && callback(null, res, body); 41 | }); 42 | } 43 | ); 44 | req.on('error', (err) => { 45 | client.isDebug && debug(err); 46 | reject(err); 47 | callback && callback(err); 48 | }); 49 | if (body) req.write(JSON.stringify(body), err => err && reject(err)); 50 | req.end(); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /lib/JPush/util.js: -------------------------------------------------------------------------------- 1 | /* extend start */ 2 | var extend 3 | var _extend 4 | var _isObject 5 | 6 | _isObject = function (o) { 7 | return Object.prototype.toString.call(o) === '[object Object]' 8 | } 9 | 10 | _extend = function self (destination, source) { 11 | var property 12 | for (property in destination) { 13 | if (destination.hasOwnProperty(property)) { 14 | // 若 destination[property] 和 source[property] 都是对象,则递归。 15 | if (_isObject(destination[property]) && _isObject(source[property])) { 16 | self(destination[property], source[property]) 17 | } 18 | 19 | // 若 source[property] 已存在,则跳过。 20 | if (!source.hasOwnProperty(property)) { 21 | source[property] = destination[property] 22 | } 23 | } 24 | } 25 | } 26 | 27 | extend = function () { 28 | var arr = arguments 29 | var result = {} 30 | var i 31 | 32 | if (!arr.length) { 33 | return {} 34 | } 35 | 36 | for (i = arr.length - 1; i >= 0; i--) { 37 | if (_isObject(arr[i])) { 38 | _extend(arr[i], result) 39 | } 40 | } 41 | 42 | arr[0] = result 43 | return result 44 | } 45 | 46 | exports.extend = extend 47 | 48 | /* extend end */ 49 | 50 | exports.isEmptyObject = function (obj) { 51 | for (var t in obj) { 52 | return !1 53 | } 54 | return !0 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jpush-async", 3 | "version": "4.2.0", 4 | "description": "JPush's officially supported Node.js client library.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha -t 50000 --reporter spec", 8 | "lint": "standard --env=mocha" 9 | }, 10 | "engines": { 11 | "node": ">=7.6" 12 | }, 13 | "keywords": [ 14 | "jpush", 15 | "JPush API", 16 | "push", 17 | "极光推送" 18 | ], 19 | "author": "jpush", 20 | "license": "MIT", 21 | "dependencies": { 22 | "debug": "2.6.9", 23 | "request": "^2.88.0", 24 | "request-promise": "^4.2.2" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^22.10.7", 28 | "assert": "1.1.2", 29 | "mocha": "~1.12.1", 30 | "should": "~1.2.2", 31 | "standard": "^10.0.2" 32 | }, 33 | "homepage": "https://www.jiguang.cn/", 34 | "repository": { 35 | "type": "git", 36 | "url": "git@github.com:jpush/jpush-api-nodejs-client.git" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/jpush/jpush-api-nodejs-client/issues" 40 | }, 41 | "directories": { 42 | "doc": "doc", 43 | "test": "test" 44 | } 45 | } -------------------------------------------------------------------------------- /test/BaseTest.js.example: -------------------------------------------------------------------------------- 1 | exports.appKey = 'xxxxx' 2 | exports.masterSecret = 'xxxxx' 3 | exports.ALERT = 'xxxxx' 4 | exports.TITLE = 'xxxxx' 5 | exports.MSG_CONTENT = 'xxxxx' 6 | exports.TAG1 = 'xxxxx' 7 | exports.TAG2 = 'xxxxx' 8 | exports.TAG_ALL = 'xxxxx' 9 | exports.TAG_NO = 'xxxxx' 10 | exports.ALIAS1 = 'xxxxx' 11 | exports.ALIAS2 = 'xxxxx' 12 | exports.ALIAS_NO = 'xxxxx' 13 | exports.REGISTRATION_ID1 = 'xxxxx' 14 | exports.REGISTRATION_ID2 = 'xxxxx' 15 | exports.EXTRAS = {'key1': 'value1', 'key2': 'value2'} 16 | exports.tooBig2000 = '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' 17 | exports.tooBig1200 = '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' 18 | -------------------------------------------------------------------------------- /test/DeviceTest.js: -------------------------------------------------------------------------------- 1 | var JPush = require('../index').JPush 2 | var Base = require('./BaseTest') 3 | var assert = require('assert') 4 | 5 | var client = JPush.buildClient(Base.appKey, Base.masterSecret) 6 | var oneSecond = 800 7 | 8 | var tagsToAdd = [ 'tag1', 'tag2' ] 9 | var tagsToRemove = [ 'tag3', 'tag4' ] 10 | 11 | client.updateDeviceTagAlias(Base.REGISTRATION_ID1, 'alias1', false, tagsToAdd, 12 | tagsToRemove, function (err, res) { 13 | if (!err && res) { 14 | assert.equal(res, 200, 'response error') 15 | } 16 | }) 17 | 18 | setTimeout(function () { 19 | client.getTagList(function (err, res) { 20 | console.log('got result' + res.tags) 21 | if (!err && res) { 22 | assert.ok(res.tags.length > 0, 'response error') 23 | } 24 | }) 25 | }, oneSecond) 26 | 27 | setTimeout(function () { 28 | client.getAliasDeviceList('alias1', null, function (err, res) { 29 | if (!err && res) { 30 | assert.ok(res.registration_ids[0] === Base.REGISTRATION_ID1, 'response error') 31 | } 32 | }) 33 | }, oneSecond * 2) 34 | 35 | setTimeout(function () { 36 | var toAddUsers = [ Base.REGISTRATION_ID1 ] 37 | var toRemoveUsers = [ Base.REGISTRATION_ID2 ] 38 | client.addRemoveDevicesFromTag('tag4', toAddUsers, toRemoveUsers, function (err, 39 | res) { 40 | if (!err && res) { 41 | assert.equal(res, 200, 'response error') 42 | } 43 | }) 44 | }, oneSecond * 3) 45 | 46 | setTimeout(function () { 47 | client.isDeviceInTag('tag4', Base.REGISTRATION_ID1, function (err, res) { 48 | if (!err && res) { 49 | console.log('got result' + res) 50 | assert.equal(res['result'], true, 'response error') 51 | } 52 | }) 53 | }, oneSecond * 4) 54 | 55 | setTimeout(function () { 56 | client.isDeviceInTag('tag4', Base.REGISTRATION_ID2, function (err, res) { 57 | if (!err && res) { 58 | console.log('got result' + res) 59 | assert.equal(res['result'], false, 'response error') 60 | } 61 | }) 62 | }, oneSecond * 5) 63 | 64 | setTimeout(function () { 65 | client.getTagList(function (err, res) { 66 | console.log('got resultasdasdasd' + res.tags) 67 | if (!err && res) { 68 | assert.ok(res.tags[0] !== undefined, 'response error') 69 | } 70 | }) 71 | }, oneSecond * 6) 72 | 73 | setTimeout(function () { 74 | client.deleteTag('tag4', null, function (err, res) { 75 | if (!err && res) { 76 | assert.equal(res, 200, 'response error') 77 | } 78 | }) 79 | }, oneSecond * 7) 80 | 81 | setTimeout(function () { 82 | client.getAliasDeviceList('alias1', null, function (err, res) { 83 | if (!err && res) { 84 | assert.ok(res.registration_ids !== undefined, 'response error') 85 | } 86 | }) 87 | }, oneSecond * 8) 88 | 89 | setTimeout(function () { 90 | client.deleteAlias('alias2', null, function (err, res) { 91 | if (!err && res) { 92 | assert.equal(res, 200, 'response error') 93 | } 94 | }) 95 | }, oneSecond * 9) 96 | 97 | setTimeout(function () { 98 | var tagsToAdd = [ 'tag1', 'tag2' ] 99 | var tagsToRemove = [ 'tag3', 'tag4' ] 100 | client.updateDeviceTagAlias(Base.REGISTRATION_ID1, 'alias1', false, tagsToAdd, 101 | tagsToRemove, function (err, res) { 102 | if (!err && res) { 103 | assert.equal(res, 200, 'response error') 104 | } 105 | }) 106 | }, oneSecond * 10) 107 | 108 | setTimeout(function () { 109 | client.getDeviceTagAlias(Base.REGISTRATION_ID1, function (err, res) { 110 | if (err) { 111 | if (err instanceof JPush.APIConnectionError) { 112 | console.log(err.message) 113 | } else if (err instanceof JPush.APIRequestError) { 114 | console.log(err.message) 115 | } 116 | } else { 117 | var tag = ['tag1', 'tag2'] 118 | assert.equal(res.tags.sort().toString(), tag.sort().toString(), 'response error') 119 | } 120 | }) 121 | }, oneSecond * 11) 122 | -------------------------------------------------------------------------------- /test/Payload.tests.js: -------------------------------------------------------------------------------- 1 | require('should') 2 | var JPush = require('../index').JPush 3 | var Base = require('./BaseTest') 4 | 5 | describe('PushPayload test', function () { 6 | var client 7 | before(function () { 8 | client = JPush.buildClient(Base.appKey, Base.masterSecret) 9 | }) 10 | after(function () {}) 11 | 12 | it('platform test1', function (done) { 13 | var json = { 14 | options: { 15 | sendno: 123456, 16 | apns_production: true 17 | }, 18 | platform: 'all' 19 | } 20 | var result = JSON.stringify(json) 21 | 22 | var payload = client.push().setPlatform(JPush.ALL).setOptions(123456) 23 | payload.toJSON().should.equal(result) 24 | done() 25 | }) 26 | 27 | it('platform test2', function (done) { 28 | var json = { 29 | options: { 30 | sendno: 123456, 31 | apns_production: true 32 | }, 33 | platform: ['ios', 'winphone'] 34 | } 35 | var result = JSON.stringify(json) 36 | 37 | var payload = client.push().setPlatform('ios', 'winphone').setOptions(123456) 38 | payload.toJSON().should.equal(result) 39 | done() 40 | }) 41 | 42 | it('platform test3', function (done) { 43 | var json = { 44 | options: { 45 | sendno: 123456, 46 | apns_production: true 47 | }, 48 | platform: ['ios', 'winphone'] 49 | } 50 | var result = JSON.stringify(json) 51 | 52 | var payload = client.push().setPlatform(['ios', 'winphone']).setOptions(123456) 53 | payload.toJSON().should.equal(result) 54 | done() 55 | }) 56 | 57 | it('audience test1', function (done) { 58 | var json = { 59 | options: { 60 | sendno: 123456, 61 | apns_production: true 62 | }, 63 | audience: 'all' 64 | } 65 | var result = JSON.stringify(json) 66 | 67 | var payload = client.push().setAudience(JPush.ALL).setOptions(123456) 68 | payload.toJSON().should.equal(result) 69 | done() 70 | }) 71 | 72 | it('audience test2', function (done) { 73 | var json = { 74 | options: { 75 | sendno: 123456, 76 | apns_production: true 77 | }, 78 | audience: { 79 | registration_id: ['id1', 'id2'], 80 | alias: ['alias1', 'alias2'], 81 | tag_and: ['tag3', 'tag4'], 82 | tag: ['tag1', 'tag2'] 83 | } 84 | } 85 | var result = JSON.stringify(json) 86 | 87 | var payload = client.push().setAudience( 88 | JPush.tag('tag1', 'tag2'), 89 | JPush.tag_and('tag3', 'tag4'), 90 | JPush.alias('alias1', 'alias2'), 91 | JPush.registration_id('id1', 'id2') 92 | ).setOptions(123456) 93 | payload.toJSON().should.equal(result) 94 | done() 95 | }) 96 | 97 | it('audience test2', function (done) { 98 | var json = { 99 | options: { 100 | sendno: 123456, 101 | apns_production: true 102 | }, 103 | audience: { 104 | registration_id: ['id1', 'id2'], 105 | alias: ['alias1', 'alias2'], 106 | tag_and: ['tag3', 'tag4'], 107 | tag: ['tag1', 'tag2'] 108 | } 109 | } 110 | var result = JSON.stringify(json) 111 | 112 | var payload = client.push().setAudience( 113 | JPush.tag('tag1,tag2'), 114 | JPush.tag_and('tag3,tag4'), 115 | JPush.alias('alias1,alias2'), 116 | JPush.registration_id('id1,id2') 117 | ).setOptions(123456) 118 | payload.toJSON().should.equal(result) 119 | done() 120 | }) 121 | 122 | it('message test1', function (done) { 123 | var json = { 124 | options: { 125 | sendno: 123456, 126 | apns_production: true 127 | }, 128 | message: { 129 | msg_content: 'msg content' 130 | } 131 | } 132 | var result = JSON.stringify(json) 133 | 134 | var payload = client.push().setMessage('msg content').setOptions(123456) 135 | payload.toJSON().should.equal(result) 136 | done() 137 | }) 138 | 139 | it('message test2', function (done) { 140 | var json = { 141 | options: { 142 | sendno: 123456, 143 | apns_production: true 144 | }, 145 | message: { 146 | msg_content: 'msg content', 147 | title: 'msg title', 148 | content_type: 'content type', 149 | extras: Base.EXTRAS 150 | } 151 | } 152 | var result = JSON.stringify(json) 153 | 154 | var payload = client.push().setMessage('msg content', 'msg title', 'content type', Base.EXTRAS) 155 | .setOptions(123456) 156 | payload.toJSON().should.equal(result) 157 | done() 158 | }) 159 | 160 | it('options test1', function (done) { 161 | var json = { 162 | options: { 163 | sendno: 123456, 164 | time_to_live: 60, 165 | override_msg_id: 654321, 166 | apns_production: true 167 | } 168 | } 169 | var result = JSON.stringify(json) 170 | 171 | var payload = client.push().setOptions(123456, 60, 654321, true) 172 | payload.toJSON().should.equal(result) 173 | done() 174 | }) 175 | 176 | it('notification test1', function (done) { 177 | var json = { 178 | options: { 179 | sendno: 123456, 180 | apns_production: true 181 | }, 182 | notification: { 183 | alert: Base.ALERT 184 | } 185 | } 186 | var result = JSON.stringify(json) 187 | 188 | var payload = client.push().setNotification(Base.ALERT).setOptions(123456) 189 | payload.toJSON().should.equal(result) 190 | done() 191 | }) 192 | 193 | it('notification test2', function (done) { 194 | var json = { 195 | options: { 196 | sendno: 123456, 197 | apns_production: true 198 | }, 199 | notification: { 200 | ios: { 201 | alert: Base.ALERT, 202 | sound: 'happy', 203 | badge: 5 204 | }, 205 | alert: Base.ALERT 206 | } 207 | } 208 | var result = JSON.stringify(json) 209 | 210 | var payload = client.push().setNotification(Base.ALERT, JPush.ios(Base.ALERT, 'happy', 5)) 211 | .setOptions(123456) 212 | payload.toJSON().should.equal(result) 213 | done() 214 | }) 215 | 216 | it('notification test3', function (done) { 217 | var json = { 218 | options: { 219 | sendno: 123456, 220 | apns_production: true 221 | }, 222 | notification: { 223 | winphone: { 224 | alert: Base.ALERT, 225 | title: Base.TITLE, 226 | _open_page: 'open page', 227 | extras: Base.EXTRAS 228 | }, 229 | android: { 230 | alert: Base.ALERT, 231 | title: Base.TITLE, 232 | builder_id: 1, 233 | extras: Base.EXTRAS 234 | }, 235 | ios: { 236 | alert: Base.ALERT, 237 | sound: 'happy', 238 | badge: 2, 239 | 'content-available': true, 240 | extras: Base.EXTRAS 241 | }, 242 | alert: Base.ALERT 243 | } 244 | } 245 | var result = JSON.stringify(json) 246 | 247 | var payload = client.push().setNotification( 248 | Base.ALERT, JPush.ios(Base.ALERT, 'happy', 2, true, Base.EXTRAS), 249 | JPush.android(Base.ALERT, Base.TITLE, 1, Base.EXTRAS), 250 | JPush.winphone(Base.ALERT, Base.TITLE, 'open page', Base.EXTRAS) 251 | ).setOptions(123456) 252 | payload.toJSON().should.equal(result) 253 | done() 254 | }) 255 | 256 | it('ios length validate fail test', function (done) { 257 | var isVaildate = client.push().setPlatform(JPush.ALL) 258 | .setAudience(JPush.ALL) 259 | .setNotification(JPush.ios(Base.tooBig2000)) 260 | .isIosExceedLength() 261 | 262 | isVaildate.should.equal(true) 263 | done() 264 | }) 265 | 266 | it('ios length validate success test', function (done) { 267 | var isVaildate = client.push().setPlatform(JPush.ALL) 268 | .setAudience(JPush.ALL) 269 | .setNotification(JPush.ios(Base.ALERT)) 270 | .isIosExceedLength() 271 | 272 | isVaildate.should.equal(false) 273 | done() 274 | }) 275 | 276 | it('ios length validate fail1 test', function (done) { 277 | var isVaildate = client.push().setPlatform(JPush.ALL) 278 | .setAudience(JPush.ALL) 279 | .setNotification(JPush.ios(Base.tooBig2000)) 280 | .isGlobalExceedLength() 281 | 282 | isVaildate.should.equal(true) 283 | done() 284 | }) 285 | 286 | it('ios length validate fail2 test', function (done) { 287 | var isVaildate = client.push().setPlatform(JPush.ALL) 288 | .setAudience(JPush.ALL) 289 | .setNotification(Base.tooBig1200, JPush.ios(Base.ALERT)) 290 | .isGlobalExceedLength() 291 | 292 | isVaildate.should.equal(true) 293 | done() 294 | }) 295 | 296 | it('ios length validate success test', function (done) { 297 | var isVaildate = client.push().setPlatform(JPush.ALL) 298 | .setAudience(JPush.ALL) 299 | .setNotification(Base.ALERT, JPush.ios(Base.ALERT)) 300 | .isGlobalExceedLength() 301 | 302 | isVaildate.should.equal(false) 303 | done() 304 | }) 305 | }) 306 | -------------------------------------------------------------------------------- /test/Push.tests.js: -------------------------------------------------------------------------------- 1 | var JPush = require('../index').JPush 2 | var Base = require('./BaseTest') 3 | 4 | describe('Push test', function () { 5 | var client 6 | this.timeout(30000) 7 | 8 | before(function () { 9 | client = JPush.buildClient(Base.appKey, Base.masterSecret) 10 | }) 11 | 12 | after(function () {}) 13 | 14 | it('Alert all test', function (done) { 15 | client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 16 | .setNotification(Base.ALERT).send(function (err, res) { 17 | if (!err && res) { 18 | done() 19 | } 20 | }) 21 | }) 22 | 23 | it('Push platform test1', function (done) { 24 | client.push().setPlatform('android').setAudience(JPush.ALL) 25 | .setNotification(Base.ALERT).send(function (err, res) { 26 | if (!err && res) { 27 | done() 28 | } 29 | }) 30 | }) 31 | 32 | it('Push platform test2', function (done) { 33 | client.push().setPlatform('android', 'ios', 'winphone').setAudience( 34 | JPush.ALL).setNotification(Base.ALERT).send(function (err, res) { 35 | if (!err && res) { 36 | done() 37 | } 38 | }) 39 | }) 40 | 41 | it('Push tags test', function (done) { 42 | client.push().setPlatform(JPush.ALL).setAudience(JPush.tag(Base.TAG1)) 43 | .setNotification(Base.ALERT).send(function (err, res) { 44 | if (!err && res) { 45 | done() 46 | } 47 | }) 48 | }) 49 | 50 | it('Push tags more test', function (done) { 51 | client.push().setPlatform(JPush.ALL).setAudience( 52 | JPush.tag(Base.TAG1, Base.TAG2)).setNotification(Base.ALERT) 53 | .send(function (err, res) { 54 | if (!err && res) { 55 | done() 56 | } 57 | }) 58 | }) 59 | 60 | it('Push alias test', function (done) { 61 | client.push().setPlatform(JPush.ALL).setAudience( 62 | JPush.alias(Base.ALIAS1)).setNotification(Base.ALERT).send( 63 | function (err, res) { 64 | if (!err && res) { 65 | done() 66 | } 67 | }) 68 | }) 69 | 70 | it('Push alias more test', function (done) { 71 | client.push().setPlatform(JPush.ALL).setAudience( 72 | JPush.alias(Base.ALIAS1, Base.ALIAS2)).setNotification( 73 | Base.ALERT).send(function (err, res) { 74 | if (!err && res) { 75 | done() 76 | } 77 | }) 78 | }) 79 | 80 | it('Push tag_and test', function (done) { 81 | client.push().setPlatform(JPush.ALL).setAudience( 82 | JPush.tag_and(Base.TAG1)).setNotification(Base.ALERT).send( 83 | function (err, res) { 84 | if (!err && res) { 85 | done() 86 | } 87 | }) 88 | }) 89 | 90 | /* it('Push tag_and more test', function(done) { 91 | 92 | client.push().setPlatform(JPush.ALL).setAudience( 93 | JPush.tag_and(Base.TAG1, Base.TAG_ALL)).setNotification( 94 | Base.ALERT).send(function(err, res) { 95 | if (!err && res) { 96 | done() 97 | } 98 | }) 99 | 100 | }); */ 101 | 102 | it('Push registration_id test', function (done) { 103 | client.push().setPlatform(JPush.ALL).setAudience( 104 | JPush.registration_id(Base.REGISTRATION_ID1)).setNotification( 105 | Base.ALERT).send(function (err, res) { 106 | if (!err && res) { 107 | done() 108 | } 109 | }) 110 | }) 111 | 112 | it('Push registration_id more test', function (done) { 113 | client.push().setPlatform(JPush.ALL).setAudience( 114 | JPush.registration_id(Base.REGISTRATION_ID1, 115 | Base.REGISTRATION_ID2)).setNotification(Base.ALERT) 116 | .send(function (err, res) { 117 | if (!err && res) { 118 | done() 119 | } 120 | }) 121 | }) 122 | 123 | // notification 124 | it('Push android', function (done) { 125 | client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 126 | .setNotification(JPush.android(Base.ALERT)).send( 127 | function (err, res) { 128 | if (!err && res) { 129 | done() 130 | } 131 | }) 132 | }) 133 | 134 | it('Push android full', function (done) { 135 | client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 136 | .setNotification(JPush.android(Base.ALERT, Base.TITLE, 1, Base.EXTRAS)) 137 | .send(function (err, res) { 138 | if (!err && res) { 139 | done() 140 | } 141 | }) 142 | }) 143 | 144 | // it('Push ios', function (done) { 145 | // client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 146 | // .setNotification(JPush.ios(Base.ALERT)).send( 147 | // function (err, res) { 148 | // if (!err && res) { 149 | // done() 150 | // } 151 | // }) 152 | // }) 153 | 154 | // it('Push ios full', function (done) { 155 | // client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 156 | // .setNotification( 157 | // JPush.ios(Base.ALERT, 'sound', 5, true, Base.EXTRAS)) 158 | // .send(function (err, res) { 159 | // if (!err && res) { 160 | // done() 161 | // } 162 | // }) 163 | // }) 164 | 165 | // it('Push winphone', function (done) { 166 | // client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 167 | // .setNotification(JPush.winphone(Base.ALERT)).send( 168 | // function (err, res) { 169 | // if (!err && res) { 170 | // done() 171 | // } 172 | // }) 173 | // }) 174 | // 175 | // it('Push winphone full', function (done) { 176 | // client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 177 | // .setNotification( 178 | // JPush.winphone(Base.ALERT, Base.TITLE, 'open page', 179 | // Base.EXTRAS)).send(function (err, res) { 180 | // if (!err && res) { 181 | // done() 182 | // } 183 | // }) 184 | // }) 185 | 186 | // message 187 | it('Push message test', function (done) { 188 | client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 189 | .setMessage(Base.MSG_CONTENT).send(function (err, res) { 190 | if (!err && res) { 191 | done() 192 | } 193 | }) 194 | }) 195 | 196 | it('Push message test2', function (done) { 197 | client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 198 | .setMessage(Base.MSG_CONTENT, Base.TITLE, 'content type', Base.EXTRAS) 199 | .send(function (err, res) { 200 | if (!err && res) { 201 | done() 202 | } 203 | }) 204 | }) 205 | 206 | it('Push notification and message', function (done) { 207 | client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 208 | .setNotification(Base.ALERT).setMessage(Base.MSG_CONTENT) 209 | .send(function (err, res) { 210 | if (!err && res) { 211 | done() 212 | } 213 | }) 214 | }) 215 | 216 | it('Options test1', function (done) { 217 | client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 218 | .setNotification(Base.ALERT).setOptions(123456, 60, null, true) 219 | .send(function (err, res) { 220 | if (!err && res) { 221 | done() 222 | } 223 | }) 224 | }) 225 | 226 | it('validate test1', function (done) { 227 | client.push().setPlatform(JPush.ALL).setAudience(JPush.ALL) 228 | .setNotification(Base.ALERT).sendValidate(function (err, res) { 229 | if (!err && res) { 230 | done() 231 | } 232 | }) 233 | }) 234 | }) 235 | --------------------------------------------------------------------------------