├── .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 |
--------------------------------------------------------------------------------