├── .gitignore ├── 3rd-party └── .gitignore ├── LICENSE ├── README.md ├── README_ZH.md ├── Third Party Open Source Software Notice.docx ├── build └── .gitignore ├── doc └── .gitignore ├── examples ├── common │ └── config.js ├── send_apns_message.js ├── send_condition_message.js ├── send_data_message.js ├── send_instance_app_message.js ├── send_notify_message.js ├── send_test_message.js ├── send_topic_message.js └── send_webpush_message.js ├── index.ts ├── package.json ├── src ├── auth │ └── auth.ts ├── hcm-namespace.ts ├── push │ ├── message-validator.ts │ ├── messaging.ts │ ├── modle │ │ ├── message-apns.ts │ │ ├── message-webpush.ts │ │ ├── message.ts │ │ └── topic.ts │ └── topic.ts └── utils │ ├── api-request.ts │ └── validator.ts ├── test └── .gitignore └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | .vscode 8 | logs/*.log* 9 | 10 | 11 | -------------------------------------------------------------------------------- /3rd-party/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | You must cause any modified files to carry prominent notices stating that You changed the files; and 39 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 41 | 42 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 43 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 44 | 45 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 46 | 47 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 48 | 49 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 50 | 51 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 52 | 53 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HMS Core Push Kit Sample Code (Node.js) 2 | English | [中文](README_ZH.md) 3 | ## Contents 4 | 5 | * [Introduction](#Introduction) 6 | * [Installation](#Installation) 7 | * [Environment Requirements](#Environment-Requirements) 8 | * [Configuration](#Configuration) 9 | * [Sample Code](#Sample-Code) 10 | * [Technical Support](#technical-support) 11 | * [License](#License) 12 | 13 | ## Introduction 14 | 15 | The sample code for Node.js encapsulates the server-side APIs of Push Kit, for your reference or direct use. 16 | 17 | The following table describes packages of Node.js sample code. 18 | | Package| Description 19 | | ---- | ----- 20 | | [examples](examples) | Sample code packages. 21 | | [utils](src/utils) | Package that provides methods for sending public network requests and for common verification. 22 | | [push](src/push) | Package where Push Kit server APIs are encapsulated. 23 | 24 | ## Installation 25 | 26 | Install Node.js on your device. 27 | 28 | Run the following command in the **nodejs-sdk** project: 29 | 30 | ```bash 31 | $ npm install 32 | ``` 33 | 34 | Run the following command: 35 | 36 | ```bash 37 | $ npm run build 38 | ``` 39 | 40 | ## Environment Requirements 41 | 42 | Node.js 8.13.0 or later. 43 | 44 | Note that the Node.js sample code can be used only in the server-side or background environments that you control, including most servers and serverless platforms (both on-premise and in the cloud). 45 | 46 | ## Configuration 47 | 48 | Start configuration with the **index.ts** file. Create an **HcmNamespace** object, and call the methods in the object to perform initialization. Access different modules, such as the messaging or topic module, and call the methods in each module. 49 | 50 | To use the functions provided by the packages in **examples**, set initialization and request parameters in the **config.js** file. 51 | 52 | 53 | | Parameter| Description| 54 | | ---- | ----- | 55 | | AppId|App ID, which is obtained from the app information.| 56 | | AppSecret|App secret, which is obtained from the app information.| 57 | | AuthUrl|URL for Huawei OAuth 2.0 to obtain a token. For details, please refer to [OAuth 2.0-based Authentication](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/oauth2-0000001212610981).| 58 | | PushUrl|Access address of Push Kit. For details, please refer to [Downlink Message Sending](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-server-dev-0000001050040110?ha_source=hms1).| 59 | 60 | 61 | | Request Parameter| Description| 62 | | ---- | ----- | 63 | | TargetTopic|Name of the topic to be subscribed to, unsubscribed from, or queried.| 64 | | TargetCondition|Combined condition expression for sending a message.| 65 | | TargetToken|Token of a target device.| 66 | 67 | 68 | ## Sample Code 69 | 70 | node.js sample code uses index.ts as the entry. Call methods on object HcmNamespace: messaging and topic. 71 | 72 | The following table lists methods in HcmNamespace. 73 | | Method | Description | 74 | | ---- | ---- | 75 | | messaging | The entry of the Messaging object, and verify the initialization input parameters. | 76 | | topic | The entry of the Topic object, and verify the initialization input parameters. | 77 | 78 | The following table lists methods in Messaging. 79 | | Method | Description | 80 | | ---- | ---- | 81 | | send | Verify if the token needs to be updated and call the method sendRequest. | 82 | | sendRequest | Sends a message to a device. | 83 | 84 | The following table lists methods in Topic. 85 | | Method | Description | 86 | | ---- | ---- | 87 | | subScribeTopic | Subscribe to topic. | 88 | | unSubScribeTopic | Unsubscribe topic. | 89 | | queryTopicList | Query subject list. | 90 | 91 | #### 1. Send an Android data message. 92 | Code location: [examples/send_data_message.js](examples/send_data_message.js) 93 | 94 | #### 2. Send an Android notification message. 95 | Code location: [examples/send_notify_message.js](examples/send_notify_message.js) 96 | 97 | #### 3. Send a message by topic. 98 | Code location: [examples/send_topic_message.js](examples/send_topic_message.js) 99 | 100 | #### 4. Send a message by conditions. 101 | Code location: [examples/send_condition_message.js](examples/send_condition_message.js) 102 | 103 | #### 5. Send a message to a Huawei quick app. 104 | Code location: [examples/send_instance_app_message.js](examples/send_instance_app_message.js) 105 | 106 | #### 6. Send a message through the WebPush agent. 107 | Code location: [examples/send_webpush_message.js](examples/send_webpush_message.js) 108 | 109 | #### 7. Send a message through the APNs agent. 110 | Code location: [examples/send_apns_message.js](examples/send_apns_message.js) 111 | 112 | #### 8. Send a test message. 113 | Code location: [examples/send_test_message.js](examples/send_test_message.js) 114 | 115 | ## Technical Support 116 | You can visit the [Reddit community](https://www.reddit.com/r/HuaweiDevelopers/) to obtain the latest information about HMS Core and communicate with other developers. 117 | 118 | If you have any questions about the sample code, try the following: 119 | - Visit [Stack Overflow](https://stackoverflow.com/questions/tagged/huawei-mobile-services?tab=Votes), submit your questions, and tag them with `huawei-mobile-services`. Huawei experts will answer your questions. 120 | - Visit the HMS Core section in the [HUAWEI Developer Forum](https://forums.developer.huawei.com/forumPortal/en/home?fid=0101187876626530001?ha_source=hms1) and communicate with other developers. 121 | 122 | If you encounter any issues when using the sample code, submit your [issues](https://github.com/HMS-Core/hms-push-serverdemo-nodejs/issues) or submit a [pull request](https://github.com/HMS-Core/hms-push-serverdemo-nodejs/pulls). 123 | 124 | ## License 125 | The sample code is licensed under [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). 126 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # 华为推送服务服务端Node.js示例代码 2 | [English](README.md) | 中文 3 | 4 | ## 目录 5 | 6 | * [简介](#简介) 7 | * [安装](#安装) 8 | * [环境要求](#环境要求) 9 | * [配置](#配置) 10 | * [示例代码](#示例代码) 11 | * [技术支持](#技术支持) 12 | * [授权许可](#授权许可) 13 | 14 | ## 简介 15 | 16 | Node.js示例代码对华为推送服务(HUAWEI Push Kit)服务端接口进行封装,供您参考使用。 17 | 18 | 示例代码目录结构如下: 19 | | 包名 | 说明 20 | | ---- | ----- 21 | | [examples](examples)|示例代码包 22 | | [utils](src/utils)|公共网络请求和公共验证方法包 23 | | [push](src/push)|接口封装包 24 | 25 | ## 安装 26 | 27 | 使用本示例代码前,请确保您的设备上已安装node.js开发环境。 28 | 29 | 在nodejs-sdk项目工程中进行如下操作: 30 | 31 | ```bash 32 | $ npm install 33 | ``` 34 | 35 | 运行示例程序: 36 | 37 | ```bash 38 | $ npm run build 39 | ``` 40 | 41 | ## 环境要求 42 | 43 | Node.js 8.13.0及以上版本。 44 | 45 | 注意Node.js示例代码只能在您控制的服务端或后端中使用,包括大部分服务端和无服务端平台(本地和云端)。 46 | 47 | ## 配置 48 | 49 | Node.js示例代码以index.ts文件为入口。创建一个HcmNamespace对象,调用其中的方法进行初始化操作,进入不同的方法模块,如消息或主题方法模块,再调用每个模块中的方法。 50 | 51 | 如需使用examples的各种功能,请在config.js中设置初始化的相关参数。 52 | 53 | 54 | | 参数 | 说明 | 55 | | ---- | ----- | 56 | | AppId|应用ID,从应用消息中获取 | 57 | | AppSecret|应用访问密钥,从应用信息中获取| 58 | | AuthUrl|华为OAuth 2.0获取token的地址。详情请参见[基于OAuth 2.0开放鉴权-客户端模式](https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/oauth2-0000001212610981)。| 59 | | PushUrl|推送服务的访问地址。详情请参见[推送服务-下行消息](https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/android-server-dev-0000001050040110?ha_source=hms1)。| 60 | 61 | 62 | | 需求参数 | 说明 | 63 | | ---- | ----- | 64 | | TargetTopic|订阅、退订或查询的主题名称 | 65 | | TargetCondition|消息的条件表达式组合| 66 | | TargetToken|目标设备token | 67 | 68 | 69 | ## 示例代码 70 | 71 | 本示例代码使用index.ts作为入口。调用HcmNamespace对象中的messaging和topic方法。 72 | 73 | HcmNamespace包括如下方法: 74 | | 方法 | 说明 | 75 | | ---- | ---- | 76 | | messaging | Messaging对象的入口,用于验证初始化输入的参数 | 77 | | topic | Topic对象的入口,用于验证初始化输入的参数 | 78 | 79 | Messaging包括如下方法: 80 | | 方法 | 说明 | 81 | | ---- | ---- | 82 | | send | 验证是否更新token并调用sendRequest方法 | 83 | | sendRequest | 向设备发送消息 | 84 | 85 | Topic包括如下方法: 86 | | 方法 | 说明 | 87 | | ---- | ---- | 88 | | subScribeTopic | 订阅主题 | 89 | | unSubScribeTopic | 退订主题 | 90 | | queryTopicList | 查询主题列表 | 91 | 92 | #### 1. 发送Android透传消息 93 | 代码位置: [examples/send_data_message.js](examples/send_data_message.js) 94 | 95 | #### 2. 发送Android通知栏消息 96 | 代码位置: [examples/send_notify_message.js](examples/send_notify_message.js) 97 | 98 | #### 3. 基于主题发送消息 99 | 代码位置: [examples/send_topic_message.js](examples/send_topic_message.js) 100 | 101 | #### 4. 基于条件发送消息 102 | 代码位置: [examples/send_condition_message.js](examples/send_condition_message.js) 103 | 104 | #### 5. 向华为快应用发送消息 105 | 代码位置: [examples/send_instance_app_message.js](examples/send_instance_app_message.js) 106 | 107 | #### 6. 基于WebPush代理发送消息 108 | 代码位置: [examples/send_webpush_message.js](examples/send_webpush_message.js) 109 | 110 | #### 7. 基于APNs代理发送消息 111 | 代码位置: [examples/send_apns_message.js](examples/send_apns_message.js) 112 | 113 | #### 8. 发送测试消息 114 | 代码位置: [examples/send_test_message.js](examples/send_test_message.js) 115 | 116 | ## 技术支持 117 | 如果您对HMS Core还处于评估阶段,可在[Reddit社区](https://www.reddit.com/r/HuaweiDevelopers/)获取关于HMS Core的最新讯息,并与其他开发者交流见解。 118 | 119 | 如果您对使用HMS示例代码有疑问,请尝试: 120 | - 开发过程遇到问题上[Stack Overflow](https://stackoverflow.com/questions/tagged/huawei-mobile-services?tab=Votes),在`huawei-mobile-services`标签下提问,有华为研发专家在线一对一解决您的问题。 121 | - 到[华为开发者论坛](https://developer.huawei.com/consumer/cn/forum/blockdisplay?fid=18?ha_source=hms1) HMS Core板块与其他开发者进行交流。 122 | 123 | 如果您在尝试示例代码中遇到问题,请向仓库提交[issue](https://github.com/HMS-Core/hms-push-serverdemo-nodejs/issues),也欢迎您提交[Pull Request](https://github.com/HMS-Core/hms-push-serverdemo-nodejs/pulls)。 124 | 125 | ## 授权许可 126 | 华为推送服务Node.js示例代码经过[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0)授权许可。 127 | -------------------------------------------------------------------------------- /Third Party Open Source Software Notice.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMS-Core/hms-push-serverdemo-nodejs/4ee1dcabe9524ddadb3ed4b5870c265cd8051214/Third Party Open Source Software Notice.docx -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /examples/common/config.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | //AppId of application, obtained from application information 18 | exports.AppId = "your appid"; 19 | //The application's secretkey, obtained from the application information 20 | exports.AppSecret = "your AppSecret"; 21 | //Obtain the token interface address from Huawei oauth2.0 service from the application information 22 | exports.AuthUrl = "https://oauth-login.cloud.huawei.com/oauth2/v3/token"; 23 | //Huawei push service access address 24 | exports.PushUrl = "https://push-api.cloud.huawei.com/v1"; 25 | 26 | //TargetToken the topic to be subscribed/unsubscribed 27 | exports.TargetTopic = "targetTopic"; 28 | //TargetCondition the condition of the devices operated 29 | exports.TargetCondition = "'targetTopic' in topics"; 30 | //TargetToken the token of the device operated 31 | exports.TargetToken = 'pushtoken'; 32 | //WebPushTokenArra the collection of the tokens of th devices operated 33 | exports.WebPushTokenArray = new Array('pushtoken1','pushtoken2'); 34 | //APNSTokenArray the collection of the tokens of th devices operated 35 | exports.APNSTokenArray = new Array('pushtoken1','pushtoken2'); 36 | //AndroidTokenArray the collection of the tokens of th devices operated 37 | exports.AndroidTokenArray = new Array('pushtoken1','pushtoken2'); 38 | //FastTokenArray the collection of the tokens of th devices operated 39 | exports.FastTokenArray = new Array('pushtoken1','pushtoken2'); 40 | -------------------------------------------------------------------------------- /examples/send_apns_message.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const hcm = require("../dist/index").default; 18 | const config = require("./common/config"); 19 | 20 | hcm.init({ 21 | appId: config.AppId, 22 | appSecret: config.AppSecret, 23 | authUrl: config.AuthUrl, 24 | pushUrl: config.PushUrl 25 | }); 26 | 27 | let mc = hcm.messaging().messaging; 28 | 29 | let headers = {HEAD_APNs_ID: "21349-141324"} 30 | let apns_alert = { 31 | title:"apnstest", 32 | body:"body", 33 | launch_image:"image", 34 | custom_data:{"key1": "value1", "key2": "value2"} 35 | } 36 | let apns_payload_aps = { 37 | alert:apns_alert, 38 | badge:1, 39 | sound:"wtewt.mp4", 40 | content_available:true, 41 | category:"category", 42 | thread_id:"id" 43 | } 44 | let payload = { 45 | aps:apns_payload_aps, 46 | acme_account:"jane.appleseed@apple.com", 47 | acme_message:"message123456" 48 | } 49 | let apns_hms_options = { 50 | target_user_type:1 51 | } 52 | let apns_push_config = { 53 | headers:headers, 54 | payload:payload, 55 | hms_options:apns_hms_options 56 | } 57 | let message = { 58 | apns:apns_push_config, 59 | token: config.APNSTokenArray 60 | }; 61 | mc.send(message, false).then(data => { 62 | console.log(data); 63 | }).catch(err => { 64 | console.log(err); 65 | }); 66 | -------------------------------------------------------------------------------- /examples/send_condition_message.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const hcm = require("../dist/index").default; 18 | const config = require("./common/config"); 19 | 20 | hcm.init({ 21 | appId: config.AppId, 22 | appSecret: config.AppSecret, 23 | authUrl: config.AuthUrl, 24 | pushUrl: config.PushUrl 25 | }); 26 | 27 | let mc = hcm.messaging().messaging; 28 | 29 | let notification = { 30 | title:'sample title', 31 | body:'sample message body' 32 | } 33 | let clickAction = { 34 | type: 2, 35 | url: "https://www.huawei.com" 36 | } 37 | let androidNotification = { 38 | icon:'/raw/ic_launcher2', 39 | color:'#AACCDD', 40 | sound:'/raw/shake', 41 | default_sound:true, 42 | tag:'tagBoom', 43 | click_action:clickAction, 44 | body_loc_key:'M.String.body', 45 | body_loc_args:['boy', 'dog'], 46 | title_loc_key:'M.String.title', 47 | title_loc_args:["Girl", "Cat"], 48 | channel_id:'Your Channel ID', 49 | notify_summary:'some summary', 50 | multi_lang_key: { 51 | title_key: { 52 | en: "添加好友请求", 53 | zh: "添加好友请求", 54 | ru: "添加好友请求" 55 | }, 56 | body_key: { 57 | en: "My name is %s, I am from %s.", 58 | zh: "我叫%s,来自%s。", 59 | ru: "我叫%s,来自%s。" 60 | } 61 | }, 62 | style:1, 63 | big_title:'test condition lkq', 64 | big_body:'Big Boom Body', 65 | auto_clear:86400000, 66 | notify_id:486, 67 | group:'Group1', 68 | importance:"HIGH", 69 | light_settings:{ 70 | color:{ 71 | alpha:0, red:0, green:1, blue:1 72 | }, 73 | light_on_duration:"3.5", 74 | light_off_duration:"5S", 75 | badge:{ 76 | add_num:1, clazz:'Classic' 77 | }, 78 | visibility:"PUBLIC", 79 | foreground_show:true 80 | } 81 | } 82 | let androidConfig = { 83 | collapse_key:-1, 84 | urgency:"HIGH", 85 | ttl:"10000s", 86 | bi_tag:'the_sample_bi_tag_for_receipt_service', 87 | notification:androidNotification 88 | } 89 | let message = { 90 | notification: notification, 91 | android: androidConfig, 92 | condition: config.TargetCondition 93 | }; 94 | mc.send(message, false).then(data => { 95 | console.log(data); 96 | }).catch(err => { 97 | console.log(err); 98 | }); -------------------------------------------------------------------------------- /examples/send_data_message.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const hcm = require("../dist/index").default; 18 | const config = require("./common/config"); 19 | 20 | hcm.init({ 21 | appId: config.AppId, 22 | appSecret: config.AppSecret, 23 | authUrl: config.AuthUrl, 24 | pushUrl: config.PushUrl 25 | }); 26 | 27 | let mc = hcm.messaging().messaging; 28 | 29 | let AndroidConfig = { 30 | collapse_key: -1, 31 | urgency: "HIGH", 32 | ttl: "10000s", 33 | bi_tag: "the_sample_bi_tag_for_receipt_service" 34 | } 35 | let message = { 36 | data: "test", 37 | android: AndroidConfig, 38 | token: config.AndroidTokenArray 39 | 40 | }; 41 | mc.send(message, false).then(data => { 42 | console.log(data); 43 | }).catch(err => { 44 | console.log(err); 45 | }); -------------------------------------------------------------------------------- /examples/send_instance_app_message.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const hcm = require("../dist/index").default; 18 | const config = require("./common/config"); 19 | 20 | hcm.init({ 21 | appId: config.FastAppId, 22 | appSecret: config.FastAppSecret, 23 | authUrl: config.AuthUrlOn, 24 | pushUrl: config.PushUrlOn 25 | }); 26 | 27 | let mc = hcm.messaging().messaging; 28 | 29 | let androidConfig = { 30 | collapse_key: -1, 31 | urgency:"HIGH", 32 | ttl: "10000s", 33 | bi_tag: "the_sample_bi_tag_for_receipt_service", 34 | fast_app_target: 1 35 | } 36 | 37 | let message = { 38 | data: "{\"pushtype\":0,\"pushbody\":{\"title\":\"This is a test message of fastApp\",\"description\":\"Happy new year!\",\"page\":\"/\",\"params\":{\"key1\":\"test1\",\"key2\":\"test2\"},\"ringtone\":{\"vibration\":\"true\",\"breathLight\":\"true\"}}}", 39 | android: androidConfig, 40 | token: config.FastTokenArray 41 | }; 42 | mc.send(message, false).then(data => { 43 | console.log(data); 44 | }).catch(err => { 45 | console.log(err); 46 | }); -------------------------------------------------------------------------------- /examples/send_notify_message.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const hcm = require("../dist/index").default; 18 | const config = require("./common/config"); 19 | 20 | hcm.init({ 21 | appId: config.AppId, 22 | appSecret: config.AppSecret, 23 | authUrl: config.AuthUrl, 24 | pushUrl: config.PushUrl 25 | }); 26 | 27 | let mc = hcm.messaging().messaging; 28 | 29 | let notification = { 30 | title: "sample title", 31 | body: "sample message body" 32 | } 33 | let androidNotification = { 34 | icon: '/raw/ic_launcher2', 35 | color: '#AACCDD', 36 | sound: '/raw/shake', 37 | default_sound: true, 38 | tag: 'tagBoom', 39 | click_action: { 40 | type: 2, 41 | url: "https://www.huawei.com" 42 | }, 43 | body_loc_key: 'M.String.body', 44 | body_loc_args: ['boy', 'dog'], 45 | title_loc_key: 'M.String.title', 46 | title_loc_args: ["Girl", "Cat"], 47 | channel_id: 'Your Channel ID', 48 | notify_summary: 'some summary', 49 | multi_lang_key: { 50 | title_key: { 51 | en: "添加好友请求", 52 | zh: "添加好友请求", 53 | ru: "添加好友请求" 54 | }, 55 | body_key: { 56 | en: "My name is %s, I am from %s.", 57 | zh: "我叫%s,来自%s。", 58 | ru: "我叫%s,来自%s。" 59 | } 60 | }, 61 | style: 1, 62 | big_title: 'test notify lkq', 63 | big_body: 'Big Boom Body', 64 | auto_clear: 86400000, 65 | notify_id: 486, 66 | group: 'Group1', 67 | auto_cancel: true, 68 | importance: "HIGH", 69 | light_settings: { 70 | color: { 71 | alpha:0, red:0, green:1, blue:1 72 | }, 73 | light_on_duration: "3.5", 74 | light_off_duration: "5S" 75 | }, 76 | badge:{ 77 | add_num: 1, clazz: 'Classic' 78 | }, 79 | visibility: 'PUBLIC', 80 | foreground_show:true 81 | } 82 | let androidConfig = { 83 | collapse_key: -1, 84 | urgency:"HIGH", 85 | ttl: "10000s", 86 | bi_tag: "the_sample_bi_tag_for_receipt_service", 87 | notification: androidNotification 88 | } 89 | let message = { 90 | notification: notification, 91 | android: androidConfig, 92 | token: config.AndroidTokenArray 93 | 94 | }; 95 | 96 | mc.send(message, false).then(data => { 97 | console.log(data); 98 | }).catch(err => { 99 | console.log(err); 100 | }); -------------------------------------------------------------------------------- /examples/send_test_message.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const hcm = require("../dist/index").default; 18 | const config = require("./common/config"); 19 | 20 | hcm.init({ 21 | appId: config.AppId, 22 | appSecret: config.AppSecret, 23 | authUrl: config.AuthUrl, 24 | pushUrl: config.PushUrl 25 | }); 26 | 27 | let mc = hcm.messaging().messaging; 28 | 29 | let notification = { 30 | title: "test title", 31 | body: "test body" 32 | } 33 | let androidNotification = { 34 | icon: 'https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png', 35 | color: '#AACCDD', 36 | default_sound: true, 37 | tag: 'tagBoom', 38 | click_action: { 39 | type: 1, 40 | intent:"intent" 41 | }, 42 | body_loc_key: 'M.String.body', 43 | body_loc_args: ['boy', 'dog'], 44 | title_loc_key: 'RingRing', 45 | title_loc_args: ["Girl", "Cat"], 46 | channel_id: 'Your Channel ID', 47 | notify_summary: 'some summary', 48 | multi_lang_key: { 49 | title_key: { 50 | en: "添加好友请求", 51 | zh: "添加好友请求", 52 | ru: "添加好友请求" 53 | }, 54 | body_key: { 55 | en: "My name is %s, I am from %s.", 56 | zh: "我叫%s,来自%s。", 57 | ru: "我叫%s,来自%s。" 58 | } 59 | }, 60 | style: 1, 61 | big_title: 'test message', 62 | big_body: 'Big Boom Body', 63 | auto_clear: 86400000, 64 | notify_id: 486, 65 | group: 'Group1', 66 | importance: "HIGH", 67 | light_settings: { 68 | color: { 69 | alpha:0, red:0, green:1, blue:1 70 | }, 71 | light_on_duration: "3.5", 72 | light_off_duration: "5S" 73 | }, 74 | badge:{ 75 | add_num:99, 76 | set_num:99, 77 | class:"Classic" 78 | }, 79 | ticker:"i am a ticker", 80 | auto_cancel:false, 81 | when:"2019-11-05", 82 | use_default_vibrate:true, 83 | use_default_light:false, 84 | visibility: 'PUBLIC', 85 | vibrate_config:[ 86 | "1.5", 87 | "2.000000001", 88 | "3" 89 | ], 90 | foreground_show:true 91 | } 92 | let androidConfig = { 93 | collapse_key: -1, 94 | urgency:"HIGH", 95 | ttl: "10000s", 96 | bi_tag: "the_sample_bi_tag_for_receipt_service", 97 | notification: androidNotification 98 | } 99 | let message = { 100 | data:"{key1:value1}", 101 | notification: notification, 102 | android: androidConfig, 103 | token: config.AndroidTokenArray 104 | 105 | }; 106 | 107 | mc.send(message, false).then(data => { 108 | console.log(data); 109 | }).catch(err => { 110 | console.log(err); 111 | }); 112 | -------------------------------------------------------------------------------- /examples/send_topic_message.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const hcm = require("../dist/index").default; 18 | const config = require("./common/config"); 19 | 20 | hcm.init({ 21 | appId: config.AppId, 22 | appSecret: config.AppSecret, 23 | authUrl: config.AuthUrl, 24 | pushUrl: config.PushUrl 25 | }); 26 | 27 | let mc = hcm.messaging().messaging; 28 | 29 | let notification = { 30 | title: "sample title", 31 | body: "sample message body" 32 | } 33 | let androidNotification = { 34 | icon: '/raw/ic_launcher2', 35 | color: '#AACCDD', 36 | sound: '/raw/shake', 37 | default_sound: true, 38 | tag: 'tagBoom', 39 | click_action: { 40 | type: 2, 41 | url: "https://www.huawei.com" 42 | }, 43 | body_loc_key: 'M.String.body', 44 | body_loc_args: ['boy', 'dog'], 45 | title_loc_key: 'M.String.title', 46 | title_loc_args: ["Girl", "Cat"], 47 | channel_id: 'Your Channel ID', 48 | notify_summary: 'some summary', 49 | multi_lang_key: { 50 | title_key: { 51 | en: "添加好友请求", 52 | zh: "添加好友请求", 53 | ru: "添加好友请求" 54 | }, 55 | body_key: { 56 | en: "My name is %s, I am from %s.", 57 | zh: "我叫%s,来自%s。", 58 | ru: "我叫%s,来自%s。" 59 | } 60 | }, 61 | style: 1, 62 | big_title: 'test topic lkq', 63 | big_body: 'Big Boom Body', 64 | auto_clear: 86400000, 65 | notify_id: 486, 66 | group: 'Group1', 67 | importance: "HIGH", 68 | light_settings: { 69 | color: { 70 | alpha:0, red:0, green:1, blue:1 71 | }, 72 | light_on_duration: "3.5", 73 | light_off_duration: "5S" 74 | }, 75 | badge:{ 76 | add_num: 1, clazz: 'Classic' 77 | }, 78 | visibility: 'PUBLIC', 79 | foreground_show:true 80 | } 81 | let androidConfig = { 82 | collapse_key: -1, 83 | urgency:"HIGH", 84 | ttl: "10000s", 85 | bi_tag: "the_sample_bi_tag_for_receipt_service", 86 | notification: androidNotification 87 | } 88 | let message = { 89 | notification: notification, 90 | android: androidConfig, 91 | topic: config.TargetTopic 92 | 93 | }; 94 | 95 | mc.send(message, false).then(data => { 96 | console.log(data); 97 | }).catch(err => { 98 | console.log(err); 99 | }); -------------------------------------------------------------------------------- /examples/send_webpush_message.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const hcm = require("../dist/index").default; 18 | const config = require("./common/config"); 19 | 20 | hcm.init({ 21 | appId: config.AppId, 22 | appSecret: config.AppSecret, 23 | authUrl: config.AuthUrl, 24 | pushUrl: config.PushUrl 25 | }); 26 | 27 | let mc = hcm.messaging().messaging; 28 | let headers = { 29 | ttl:"990", 30 | urgency:"low", 31 | topic:"12313" 32 | } 33 | let notification = { 34 | title:"notication string", 35 | body: "web push body", 36 | actions:[ 37 | { 38 | action: "123", 39 | icon: "https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png", 40 | title: "string" 41 | } 42 | ], 43 | badge: "string", 44 | dir: "auto", 45 | icon: "https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png", 46 | image: "string", 47 | lang: "string", 48 | renotify: true, 49 | require_interaction: true, 50 | silent: true, 51 | tag: "string", 52 | timestamp:1545201266, 53 | vibrate:[1,2,3] 54 | } 55 | let WebPushConfig = { 56 | headers:headers, 57 | notification: notification, 58 | hms_options:{ 59 | link:"https://www.huawei.com/" 60 | } 61 | } 62 | let message = { 63 | webpush: WebPushConfig, 64 | token: config.WebPushTokenArray 65 | } 66 | 67 | mc.send(message, false).then(data => { 68 | console.log(data); 69 | }).catch(err => { 70 | console.log(err); 71 | }); 72 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { HcmNamespace } from "./src/hcm-namespace"; 18 | 19 | const hcmAdmin = new HcmNamespace(); 20 | (hcmAdmin as any).default = hcmAdmin; 21 | 22 | export = hcmAdmin; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hcm-admin", 3 | "version": "0.0.1", 4 | "description": "HCM Node SDK.", 5 | "main": "./dist/index.js", 6 | "scripts": { 7 | "tsc": "tsc -w", 8 | "build": "tsc" 9 | }, 10 | "config": {}, 11 | "author": "guoyu", 12 | "license": "ISC", 13 | "dependencies": { 14 | "request": "^2.88.0", 15 | "request-promise": "^4.2.4" 16 | }, 17 | "devDependencies": { 18 | "typescript": "3.1.6", 19 | "@types/node": "8.10.34", 20 | "@types/request": "^2.48.2", 21 | "@types/request-promise": "^4.1.44", 22 | "@types/mocha": "^5.2.7", 23 | "mocha": "5.2.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/auth/auth.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { HttpClient, HttpRequestConfig, HttpMethod } from "../utils/api-request"; 18 | import { HcmConfig } from "../hcm-namespace"; 19 | const REFRESH_TOKEN_METHOD: HttpMethod = "POST"; 20 | const ENDPOINT = "https://logintestlf.hwcloudtest.cn/oauth2/token"; 21 | 22 | export class AuthClient { 23 | private _httpClient: HttpClient; 24 | private config: HcmConfig; 25 | private _token: string; 26 | constructor(conf: HcmConfig) { 27 | this._httpClient = new HttpClient(); 28 | this.config = conf; 29 | } 30 | 31 | get httpClient(): HttpClient { 32 | return this._httpClient; 33 | } 34 | 35 | get token(): string { 36 | return this._token; 37 | } 38 | 39 | public async refreshToken() { 40 | let option: HttpRequestConfig = {} as any; 41 | option.uri = this.config.authUrl ? this.config.authUrl : ENDPOINT; 42 | option.headers = { 43 | "Content-Type": "application/x-www-form-urlencoded" 44 | }; 45 | option.form = { 46 | grant_type: "client_credentials", 47 | client_secret: this.config.appSecret, 48 | client_id: this.config.appId 49 | }; 50 | option.method = REFRESH_TOKEN_METHOD; 51 | option.json = true; 52 | return this._httpClient.sendWithRetry(option).then(res => { 53 | this._token = res.data.access_token; 54 | return this._token; 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/hcm-namespace.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Messaging } from "./push/messaging"; 18 | import { Topic } from "./push/topic"; 19 | import { MessagingConfig } from "./push/modle/message"; 20 | import { TopicConfig } from "./push/modle/topic"; 21 | import { AuthClient } from "./auth/auth"; 22 | 23 | export class HcmNamespace { 24 | private authClient: AuthClient; 25 | private config: HcmConfig; 26 | 27 | public init(conf: HcmConfig) { 28 | this.config = conf; 29 | this.authClient = new AuthClient(conf); 30 | } 31 | 32 | public async auth() { 33 | if (!this.checkInit()) { 34 | return; 35 | } 36 | let token = await this.authClient.refreshToken(); 37 | return token; 38 | } 39 | 40 | public messaging(conf?: MessagingConfig): HcmServiceNamespace { 41 | if (!this.checkInit()) { 42 | return; 43 | } 44 | if (!conf) { 45 | conf = { 46 | devappid: this.config.appId 47 | }; 48 | } 49 | conf.devappid = conf.devappid ? conf.devappid : this.config.appId; 50 | conf.messagingUrl = conf.messagingUrl ? conf.messagingUrl : this.config.pushUrl; 51 | let messaging = new Messaging(conf, this.authClient); 52 | 53 | return { messaging }; 54 | } 55 | 56 | public topic(tconf?: TopicConfig): HcmServiceNamespace { 57 | if (!this.checkInit()) { 58 | return; 59 | } 60 | if (!tconf) { 61 | tconf = { 62 | devappid: this.config.appId 63 | }; 64 | } 65 | tconf.devappid = tconf.devappid ? tconf.devappid : this.config.appId; 66 | tconf.topicUrl = tconf.topicUrl ? tconf.topicUrl : this.config.pushUrl; 67 | let topic = new Topic(tconf, this.authClient); 68 | 69 | return { topic }; 70 | } 71 | 72 | private checkInit() { 73 | if (!this.config || !this.config.appId || !this.config.appSecret) { 74 | throw new Error("appId or appsecret is null, please init Hcm first!"); 75 | } 76 | return true; 77 | } 78 | } 79 | 80 | export interface HcmServiceNamespace { 81 | [key: string]: T; 82 | } 83 | 84 | export interface HcmConfig { 85 | appId: string; 86 | appSecret: string; 87 | authUrl?: string; 88 | pushUrl?: string 89 | } 90 | -------------------------------------------------------------------------------- /src/push/message-validator.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Message,Notification, AndroidConfig, AndroidNotification, ClickAction, LightSettings } from "./modle/message"; 18 | import { WebPushConfig } from "./modle/message-webpush"; 19 | import * as validator from "../utils/validator"; 20 | 21 | const PriorityHight = "HIGH", 22 | PriorityNormal = "NORMAL"; 23 | 24 | const ImportanceHight = "HIGH", 25 | ImportanceNormal = "NORMAL", 26 | ImportanceLow = "LOW"; 27 | 28 | const DirAuto = "auto", 29 | DirLtr = "ltr", 30 | DirRtl = "rtl"; 31 | 32 | const UrgencyHight = "high", 33 | UrgencyNormal = "normal", 34 | UrgencyLow = "low", 35 | UrgencyVeryLow = "very-low"; 36 | 37 | const SytleBigText = 1, 38 | SytleBigPicture = 2; 39 | 40 | const TypeIntent = 1, 41 | TypeUrl = 2, 42 | TypeApp = 3, 43 | TypeRichResource = 4; 44 | 45 | const Pattern = new RegExp("\\d+|\\d+[sS]|\\d+.\\d{1,9}|\\d+.\\d{1,9}[sS]"); 46 | const colorPattern = new RegExp("^#[0-9a-fA-F]{6}$"); 47 | const TTLINIT = "86400"; 48 | 49 | export function validateMessage(message: Message) { 50 | if (!message) { 51 | throw new Error("message must not be null!"); 52 | } 53 | validateFieldTarget(message.token, message.topic, message.condition); 54 | if(message.webpush){ 55 | validateWebPushConfig(message.webpush, message.notification); 56 | } 57 | return validateAndroidConfig(message.android, message.notification); 58 | } 59 | 60 | function validateFieldTarget(token: Array, ...params: Array) { 61 | let count = 0; 62 | if (token) { 63 | count++; 64 | } 65 | params && 66 | params.forEach(pa => { 67 | if (validator.isNonEmptyString(pa)) { 68 | count++; 69 | } 70 | }); 71 | if (count === 1) { 72 | return; 73 | } 74 | throw new Error("token, topic or condition must be choice one"); 75 | } 76 | 77 | function validateWebPushConfig(webPushConfig: WebPushConfig, notification?: Notification){ 78 | if (!validator.isNonEmptyString(webPushConfig.notification.title)&&!validator.isNonEmptyString(notification.title)) { 79 | throw new Error("title must not be empty"); 80 | } 81 | if (!validator.isNonEmptyString(webPushConfig.notification.body)&&!validator.isNonEmptyString(notification.body)) { 82 | throw new Error("body must not be empty"); 83 | } 84 | if (validator.isNonEmptyString(webPushConfig.notification.dir) && 85 | (webPushConfig.notification.dir !== DirAuto && 86 | webPushConfig.notification.dir !== DirLtr && 87 | webPushConfig.notification.dir !== DirRtl)) { 88 | throw new Error("dir must be 'auto', 'ltr' or 'rtl'"); 89 | } 90 | if (validator.isNonEmptyString(webPushConfig.headers.urgency) && 91 | (webPushConfig.headers.urgency !== UrgencyHight && 92 | webPushConfig.headers.urgency !== UrgencyNormal && 93 | webPushConfig.headers.urgency !== UrgencyLow&& 94 | webPushConfig.headers.urgency !== UrgencyVeryLow)) { 95 | throw new Error("urgency must be 'HIGH', 'LOW' , 'low' or 'very-low'"); 96 | } 97 | } 98 | 99 | function validateAndroidConfig(androidConfig: AndroidConfig, notification?: Notification) { 100 | if (!androidConfig) { 101 | return; 102 | } 103 | if (androidConfig.collapse_key < -1 || androidConfig.collapse_key > 100) { 104 | throw new Error("collapse_key must be in interval [-1 - 100]"); 105 | } 106 | 107 | if ( 108 | validator.isNonEmptyString(androidConfig.urgency) && 109 | (androidConfig.urgency !== PriorityHight && 110 | androidConfig.urgency !== PriorityNormal) 111 | ) { 112 | throw new Error("priority must be 'HIGH', 'NORMAL'"); 113 | } 114 | 115 | if (!androidConfig.ttl) { 116 | androidConfig.ttl = TTLINIT; 117 | } 118 | if (validator.isNonEmptyString(androidConfig.ttl) && !Pattern.exec(androidConfig.ttl)) { 119 | throw new Error("Wrong input format"); 120 | } 121 | 122 | if(!androidConfig.fast_app_target){ 123 | androidConfig.fast_app_target = 2; 124 | } 125 | // validate android notification 126 | return validateAndroidNotification(androidConfig.notification, notification); 127 | } 128 | 129 | function validateAndroidNotification(androidNotification: AndroidNotification, notification?:Notification) { 130 | if (!notification) { 131 | return; 132 | } 133 | 134 | if (!validator.isNonEmptyString(androidNotification.title)&&!validator.isNonEmptyString(notification.title)) { 135 | throw new Error("title must not be empty"); 136 | } 137 | 138 | if (!validator.isNonEmptyString(androidNotification.body)&&!validator.isNonEmptyString(notification.body)) { 139 | throw new Error("body must not be empty"); 140 | } 141 | 142 | switch (androidNotification.style) { 143 | case SytleBigText: 144 | if (!validator.isNonEmptyString(androidNotification.big_title)) { 145 | throw new Error("big_title must not be empty when style is 1"); 146 | } 147 | 148 | if (!validator.isNonEmptyString(androidNotification.big_body)) { 149 | throw new Error("big_body must not be empty when style is 1"); 150 | } 151 | break; 152 | case SytleBigPicture: 153 | if (!validator.isNonEmptyString(androidNotification.big_picture)) { 154 | throw new Error("big_picture must not be empty when style is 2"); 155 | } 156 | } 157 | 158 | if (androidNotification.color && !colorPattern.exec(androidNotification.color)) { 159 | throw new Error("color must be in the form #RRGGBB"); 160 | } 161 | 162 | if(validator.isNonEmptyString(androidNotification.group)&&validator.isNonEmptyString(androidNotification.notify_id)) { 163 | throw new Error("notify_id must be empty when group exist"); 164 | } 165 | 166 | if(validator.isNonEmptyString(androidNotification.importance)&& 167 | androidNotification.importance !== ImportanceHight && 168 | androidNotification.importance !== ImportanceNormal && 169 | androidNotification.importance !== ImportanceLow) 170 | { 171 | throw new Error("importance must be 'LOW', 'NORMAL' or 'HIGH'"); 172 | } 173 | 174 | // validate click action 175 | validateClickAction(androidNotification.click_action); 176 | 177 | // validate light setting 178 | return(validateLightSetting(androidNotification.light_settings)); 179 | } 180 | 181 | function validateClickAction(clickAction: ClickAction) { 182 | if (!clickAction) { 183 | throw new Error("click_action object must not be null"); 184 | } 185 | 186 | switch (clickAction.type) { 187 | case TypeIntent: 188 | if (!validator.isNonEmptyString(clickAction.intent)&&!validator.isNonEmptyString(clickAction.action)) { 189 | throw new Error("intent and action have at least one when type is 1"); 190 | } 191 | break; 192 | case TypeUrl: 193 | if (!validator.isNonEmptyString(clickAction.url)) { 194 | throw new Error("url must not be empty when type is 2"); 195 | } 196 | break; 197 | case TypeApp: 198 | break; 199 | case TypeRichResource: 200 | if (!validator.isNonEmptyString(clickAction.rich_resource)) { 201 | throw new Error("rich_resource must not be empty when type is 4"); 202 | } 203 | break; 204 | default: 205 | throw new Error("type must be in the interval [1 - 4]"); 206 | } 207 | } 208 | 209 | function validateLightSetting(lightSettings: LightSettings) { 210 | if(!lightSettings) { 211 | return; 212 | } 213 | if(!lightSettings.color){ 214 | throw new Error("color must not be empty"); 215 | } 216 | if (validator.isNonEmptyString(lightSettings.light_off_duration) && !Pattern.exec(lightSettings.light_off_duration)) { 217 | throw new Error("Wrong input format"); 218 | } 219 | if (validator.isNonEmptyString(lightSettings.light_on_duration) && !Pattern.exec(lightSettings.light_on_duration)) { 220 | throw new Error("Wrong input format"); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/push/messaging.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { MessagingConfig, Message, MsgRequest, MsgResponse } from "./modle/message"; 18 | import { AuthClient } from "../auth/auth"; 19 | import { HttpClient, HttpRequestConfig, ENDPOINT, TOKENTIMEOUTERR } from "../utils/api-request"; 20 | import { validateMessage } from "./message-validator"; 21 | 22 | const SEND_METHOD = "POST"; 23 | export class Messaging { 24 | private config: MessagingConfig; 25 | private authClient: AuthClient; 26 | private _httpClient: HttpClient; 27 | 28 | constructor(conf: MessagingConfig, auth: AuthClient) { 29 | this.config = conf; 30 | this.authClient = auth; 31 | this._httpClient = new HttpClient(conf.retryConfig); 32 | } 33 | 34 | public async send(message: Message, validationOnly: boolean = false, dryRun: boolean = true) { 35 | let request: MsgRequest = { 36 | validate_only: validationOnly, 37 | message 38 | }; 39 | if (!this.authClient) { 40 | throw new Error("can't refresh token because getting auth client fail"); 41 | } 42 | if (!this.authClient.token) { 43 | await this.authClient.refreshToken(); 44 | } 45 | let result = await this.sendRequest(request, dryRun); 46 | if (result.code === TOKENTIMEOUTERR) { 47 | await this.authClient.refreshToken(); 48 | result = await this.sendRequest(request, dryRun); 49 | } 50 | return result; 51 | } 52 | 53 | private async sendRequest(req: MsgRequest, dryRun?: boolean): Promise { 54 | validateMessage(req.message); 55 | let option: HttpRequestConfig = {} as any; 56 | let url = this.config.messagingUrl ? this.config.messagingUrl : ENDPOINT; 57 | option.uri = `${url}/${this.config.devappid}/messages:send`; 58 | option.headers = { 59 | "Content-Type": "application/json;charset=utf-8", 60 | Authorization: `Bearer ${this.authClient.token}` 61 | }; 62 | option.body = req; 63 | option.method = SEND_METHOD; 64 | option.json = true; 65 | if (dryRun) { 66 | return this._httpClient.sendWithRetry(option).then(res => { 67 | let data = res.data; 68 | return data; 69 | }); 70 | } 71 | return this._httpClient.send(option).then(res => { 72 | let data = res.data; 73 | return data; 74 | }); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/push/modle/message-apns.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export interface ApnsConfig { 18 | headers?: {[key: string]: string}; 19 | payload: ApnsPayload; 20 | hms_options: ApnsOptions; 21 | } 22 | 23 | export interface ApnsOptions { 24 | target_user_type: number; 25 | } 26 | 27 | export interface ApnsPayload { 28 | aps: Aps; 29 | acme_account: string; 30 | acme_message: string; 31 | } 32 | 33 | export interface Aps { 34 | alert?: string | AlertDictionary; 35 | badge?: number; 36 | sound?: string; 37 | content_available?: boolean; 38 | category?: string; 39 | thread_id?: string; 40 | } 41 | 42 | export interface AlertDictionary { 43 | title?: string; 44 | body?: string; 45 | title_loc_key?: string; 46 | title_loc_args?: string[]; 47 | action_loc_key?: string; 48 | loc_key?: string; 49 | loc_args?: string[]; 50 | launch_image?: string; 51 | } -------------------------------------------------------------------------------- /src/push/modle/message-webpush.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export interface WebPushConfig { 18 | headers?: WebPushHeaders; 19 | notification?: WebPushNotification; 20 | hmsOptions?: WebPushOptions; 21 | } 22 | 23 | 24 | export interface WebPushHeaders { 25 | ttl?: string; 26 | topic?: string; 27 | urgency?: string; 28 | } 29 | 30 | export interface WebPushNotification { 31 | title?: string; 32 | body?: string; 33 | icon?: string; 34 | image?: string; 35 | lang?: string; 36 | tag?: string; 37 | badge?: string; 38 | dir?: string; 39 | vibrate?: Array; 40 | renotify?: boolean; 41 | requireInteraction?: boolean; 42 | silent?: boolean; 43 | timestamp?: number; 44 | actions?: Array; 45 | } 46 | 47 | export interface WebPushAction { 48 | action?: string; 49 | icon?: string; 50 | title?: string; 51 | } 52 | 53 | export interface WebPushOptions { 54 | link?: string; 55 | } -------------------------------------------------------------------------------- /src/push/modle/message.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { RetryConfig } from "../../utils/api-request"; 18 | import { WebPushConfig } from "./message-webpush"; 19 | import { ApnsConfig } from "./message-apns"; 20 | 21 | export interface MessagingConfig { 22 | devappid: string; 23 | messagingUrl?: string; 24 | retryConfig?: RetryConfig; 25 | } 26 | 27 | export interface MsgRequest { 28 | validate_only: boolean; 29 | message: Message; 30 | } 31 | 32 | export interface AndroidConfig { 33 | collapse_key?: number; 34 | urgency?: string; 35 | ttl?: string; 36 | bi_tag?: string; 37 | fast_app_target?: number; 38 | data?: string; 39 | notification?: AndroidNotification; 40 | } 41 | 42 | export interface AndroidNotification { 43 | title?: string; 44 | body?: string; 45 | icon?: string; 46 | color?: string; 47 | sound?: string; 48 | default_sound?:boolean; 49 | tag?: string; 50 | click_action?: ClickAction; 51 | body_loc_key?: string; 52 | body_loc_args?: Array; 53 | title_loc_key?: string; 54 | title_loc_args?: Array; 55 | multi_lang_key?: MultiLanguageKey; 56 | channel_id?: string; 57 | notify_summary?: string; 58 | image?: string; 59 | style: number; 60 | big_title?: string; 61 | big_body?: string; 62 | auto_clear?: number; 63 | notify_id?: number; 64 | group?: string; 65 | badge?: BadgeNotification; 66 | ticker?:string; 67 | auto_cancel?:boolean; 68 | when?:string; 69 | importance?:string; 70 | use_default_vibrate?:boolean; 71 | use_default_light?:boolean; 72 | vibrate_config?:Array; 73 | visibility?:string; 74 | light_settings?:LightSettings; 75 | foreground_show?:boolean; 76 | } 77 | 78 | export interface MultiLanguageKey { 79 | title_key?: MultiLanguageSelect; 80 | body_key?: MultiLanguageSelect; 81 | } 82 | 83 | export interface MultiLanguageSelect { 84 | en?: string; 85 | zh?: string; 86 | ru?: string; 87 | } 88 | 89 | export interface ClickAction { 90 | type: number; 91 | intent?: string; 92 | url?: string; 93 | rich_resource?: string; 94 | action?:string; 95 | } 96 | 97 | export interface BadgeNotification { 98 | add_num?: number; 99 | class?: string; 100 | set_num?:number; 101 | } 102 | 103 | export interface LightSettings { 104 | color?: Color; 105 | light_on_duration?: string; 106 | light_off_duration?:string; 107 | } 108 | 109 | export interface Color { 110 | alpha?: number; 111 | red?: number; 112 | green?:number; 113 | blue?:number; 114 | } 115 | 116 | export interface Notification { 117 | title?: string; 118 | body?: string; 119 | image?:string; 120 | } 121 | 122 | export interface Message { 123 | data?: string; 124 | notification?: Notification; 125 | android?: AndroidConfig; 126 | apns?: ApnsConfig; 127 | webpush?: WebPushConfig; 128 | token?: Array; 129 | topic?: string; 130 | condition?: string; 131 | } 132 | 133 | export interface MsgResponse { 134 | code: string; 135 | msg: string; 136 | requestId: string; 137 | } 138 | 139 | export interface ErrIndex { 140 | index?: number; 141 | reason?: string; 142 | } 143 | -------------------------------------------------------------------------------- /src/push/modle/topic.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { ErrIndex } from "./message"; 18 | 19 | export interface TopicConfig { 20 | devappid: string; 21 | topicUrl?: string; 22 | } 23 | 24 | export interface TopicRequest { 25 | topic: string; 26 | tokenArray: Array; 27 | } 28 | 29 | export interface QueryTopicRequest { 30 | token: string; 31 | } 32 | 33 | export interface TopicResponse { 34 | code: string; 35 | msg: string; 36 | requestId: string; 37 | failureCount: number; 38 | successCount: number; 39 | errors?: Array; 40 | } 41 | 42 | export interface QueryTopicResponse { 43 | code: string; 44 | msg: string; 45 | requestId: string; 46 | topics?:Array 47 | } 48 | 49 | export interface TopicBody { 50 | name?: string; 51 | addDate?: string; 52 | } -------------------------------------------------------------------------------- /src/push/topic.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { TopicConfig, TopicRequest, TopicResponse, QueryTopicRequest, QueryTopicResponse } from "./modle/topic"; 18 | import { AuthClient } from "../auth/auth"; 19 | import { HttpClient, HttpRequestConfig, ENDPOINT, TOKENTIMEOUTERR } from "../utils/api-request"; 20 | 21 | const SEND_METHOD = "POST"; 22 | export class Topic { 23 | private config: TopicConfig; 24 | private authClient: AuthClient; 25 | private _httpClient: HttpClient; 26 | 27 | constructor(conf: TopicConfig, auth: AuthClient) { 28 | this.config = conf; 29 | this.authClient = auth; 30 | this._httpClient = new HttpClient(); 31 | } 32 | 33 | // subscribeTopic 34 | // The developer server calls this interface to single or multiple users to complete the topic subscription operation 35 | public async subScribeTopic(req: TopicRequest) { 36 | if (!this.authClient) { 37 | throw new Error("can't refresh token because getting auth client fail"); 38 | } 39 | if (!this.authClient.token) { 40 | await this.authClient.refreshToken(); 41 | } 42 | let result = await this.getSubscribeTopicReq(req); 43 | if (result.code === TOKENTIMEOUTERR) { 44 | await this.authClient.refreshToken(); 45 | result = await this.getSubscribeTopicReq(req); 46 | } 47 | return result; 48 | } 49 | 50 | private async getSubscribeTopicReq(req:TopicRequest): Promise { 51 | let option: HttpRequestConfig = {} as any; 52 | let url = this.config.topicUrl ? this.config.topicUrl : ENDPOINT; 53 | option.uri = `${url}/${this.config.devappid}/topic:subscribe`; 54 | option.headers = { 55 | "Content-Type": "application/json;charset=utf-8", 56 | Authorization: `Bearer ${this.authClient.token}` 57 | }; 58 | option.body = req; 59 | option.method = SEND_METHOD; 60 | option.json = true; 61 | return this._httpClient.send(option).then(res => { 62 | let data = res.data; 63 | return data; 64 | }); 65 | } 66 | 67 | // unsubscribeTopic 68 | // The developer server calls this interface to single or multiple users to complete the topic unsubscription operation 69 | public async unSubScribeTopic(req: TopicRequest) { 70 | if (!this.authClient) { 71 | throw new Error("can't refresh token because getting auth client fail"); 72 | } 73 | if (!this.authClient.token) { 74 | await this.authClient.refreshToken(); 75 | } 76 | let result = await this.getUnSubscribeTopicReq(req); 77 | if (result.code === TOKENTIMEOUTERR) { 78 | await this.authClient.refreshToken(); 79 | result = await this.getUnSubscribeTopicReq(req); 80 | } 81 | return result; 82 | } 83 | 84 | private async getUnSubscribeTopicReq(req:TopicRequest): Promise { 85 | let option: HttpRequestConfig = {} as any; 86 | let url = this.config.topicUrl ? this.config.topicUrl : ENDPOINT; 87 | option.uri = `${url}/${this.config.devappid}/topic:unsubscribe`; 88 | option.headers = { 89 | "Content-Type": "application/json;charset=utf-8", 90 | Authorization: `Bearer ${this.authClient.token}` 91 | }; 92 | option.body = req; 93 | option.method = SEND_METHOD; 94 | option.json = true; 95 | return this._httpClient.send(option).then(res => { 96 | let data = res.data; 97 | return data; 98 | }); 99 | } 100 | 101 | // queryTopic 102 | // The developer server calls this interface to single or multiple users to complete the topic query operation 103 | public async queryTopicList(req: QueryTopicRequest) { 104 | if (!this.authClient) { 105 | throw new Error("can't refresh token because getting auth client fail"); 106 | } 107 | if (!this.authClient.token) { 108 | await this.authClient.refreshToken(); 109 | } 110 | let result = await this.getQueryTopic(req); 111 | if (result.code === TOKENTIMEOUTERR) { 112 | await this.authClient.refreshToken(); 113 | result = await this.getQueryTopic(req); 114 | } 115 | return result; 116 | } 117 | 118 | private async getQueryTopic(req: QueryTopicRequest): Promise { 119 | let option: HttpRequestConfig = {} as any; 120 | let url = this.config.topicUrl ? this.config.topicUrl : ENDPOINT; 121 | option.uri = `${url}/${this.config.devappid}/topic:list`; 122 | option.headers = { 123 | "Content-Type": "application/json;charset=utf-8", 124 | Authorization: `Bearer ${this.authClient.token}` 125 | }; 126 | option.body = req; 127 | option.method = SEND_METHOD; 128 | option.json = true; 129 | return this._httpClient.send(option).then(res => { 130 | let data = res.data; 131 | return data; 132 | }); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/utils/api-request.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2019 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * 2020.1.15-Changed Refer to send request method (send) and retry send 17 | * request method (sendWithRetry) 18 | * Huawei Technologies Co., Ltd. 19 | */ 20 | 21 | import rp from "request-promise"; 22 | import * as validator from "./validator"; 23 | async function send(options: HttpRequestConfig): Promise { 24 | options.resolveWithFullResponse = true; 25 | return rp(options) 26 | .then(result => { 27 | return Promise.resolve(createHttpResponse(result)); 28 | }) 29 | .catch(err => { 30 | return Promise.reject(err); 31 | }); 32 | } 33 | 34 | /** 35 | * Specifies how failing HTTP requests should be retried. 36 | */ 37 | export interface RetryConfig { 38 | /** Maximum number of times to retry a given request. */ 39 | maxRetries: number; 40 | 41 | /** HTTP status codes that should be retried. */ 42 | statusCodes?: number[]; 43 | 44 | /** Low-level I/O error codes that should be retried. */ 45 | ioErrorCodes?: string[]; 46 | 47 | /** 48 | * The multiplier for exponential back off. When the backOffFactor is setto 0, retries are not delayed. 49 | * When the backOffFactor is 1, retry duration is doubled each iteration. 50 | */ 51 | backOffFactor?: number; 52 | 53 | /** Maximum duration to wait before initiating a retry. */ 54 | maxDelayInMillis: number; 55 | } 56 | 57 | /** 58 | * Default retry configuration for HTTP requests. Retries up to 4 times on connection reset and timeout errors 59 | * as well as HTTP 503 errors. Exposed as a function to ensure that every HttpClient gets its own RetryConfig 60 | * instance. 61 | */ 62 | export function defaultRetryConfig(): RetryConfig { 63 | return { 64 | maxRetries: 4, 65 | statusCodes: [503], 66 | ioErrorCodes: ["ECONNRESET", "ETIMEDOUT"], 67 | backOffFactor: 0.5, 68 | maxDelayInMillis: 60 * 1000 69 | }; 70 | } 71 | 72 | /** 73 | * Ensures that the given RetryConfig object is valid. 74 | * 75 | * @param retry The configuration to be validated. 76 | */ 77 | function validateRetryConfig(retry: RetryConfig) { 78 | if (!validator.isNumber(retry.maxRetries) || retry.maxRetries < 0) { 79 | throw new Error("maxRetries must be a non-negative integer"); 80 | } 81 | 82 | if (typeof retry.backOffFactor !== "undefined") { 83 | if (!validator.isNumber(retry.backOffFactor) || retry.backOffFactor < 0) { 84 | throw new Error("backOffFactor must be a non-negative number"); 85 | } 86 | } 87 | 88 | if (!validator.isNumber(retry.maxDelayInMillis) || retry.maxDelayInMillis < 0) { 89 | throw new Error("maxDelayInMillis must be a non-negative integer"); 90 | } 91 | 92 | if (typeof retry.statusCodes !== "undefined" && !Array.isArray(retry.statusCodes)) { 93 | throw new Error("statusCodes must be an array"); 94 | } 95 | 96 | if (typeof retry.ioErrorCodes !== "undefined" && !Array.isArray(retry.ioErrorCodes)) { 97 | throw new Error("ioErrorCodes must be an array"); 98 | } 99 | } 100 | /** 101 | * Represents an HTTP response received from a remote server. 102 | */ 103 | export interface HttpResponse { 104 | readonly status: number; 105 | readonly headers: any; 106 | /** Response data as a raw string. */ 107 | readonly text: string; 108 | /** Response data as a parsed JSON object. */ 109 | readonly data: any; 110 | /** For multipart responses, the payloads of individual parts. */ 111 | readonly multipart?: Buffer[]; 112 | /** 113 | * Indicates if the response content is JSON-formatted or not. If true, data field can be used 114 | * to retrieve the content as a parsed JSON object. 115 | */ 116 | isJson(): boolean; 117 | } 118 | 119 | interface LowLevelResponse { 120 | statusCode: number; 121 | headers: any; 122 | body: string; 123 | request: any; 124 | } 125 | 126 | class DefaultHttpResponse implements HttpResponse { 127 | public readonly status: number; 128 | public readonly headers: any; 129 | public readonly text: string; 130 | private readonly request: string; 131 | private readonly parsedData: any; 132 | private readonly parseError: Error; 133 | 134 | /** 135 | * Constructs a new HttpResponse from the given LowLevelResponse. 136 | */ 137 | constructor(resp: LowLevelResponse) { 138 | this.status = resp.statusCode; 139 | this.headers = resp.headers; 140 | let body = resp.body || ""; 141 | this.text = validator.isString(body) ? body : JSON.stringify(body); 142 | 143 | try { 144 | this.parsedData = validator.isString(body) ? JSON.parse(body) : body; 145 | } catch (err) { 146 | this.parsedData = undefined; 147 | this.parseError = err; 148 | } 149 | this.request = resp.request; 150 | } 151 | 152 | get data(): any { 153 | if (this.isJson()) { 154 | return this.parsedData; 155 | } 156 | throw new Error( 157 | `Error while parsing response data: "${this.parseError.toString()}". Raw server ` + 158 | `response: "${this.text}". Status code: "${this.status}". Outgoing ` + 159 | `request: "${this.request}."` 160 | ); 161 | } 162 | 163 | public isJson(): boolean { 164 | return !!this.parsedData; 165 | } 166 | } 167 | function createHttpResponse(resp: LowLevelResponse): HttpResponse { 168 | if (!resp) { 169 | return; 170 | } 171 | return new DefaultHttpResponse(resp); 172 | } 173 | 174 | export class HttpClient { 175 | constructor(private readonly retry: RetryConfig = defaultRetryConfig()) { 176 | if (this.retry) { 177 | validateRetryConfig(this.retry); 178 | } 179 | } 180 | 181 | /** 182 | * Sends an HTTP request to a remote server. 183 | * 184 | * @param {HttpRequest} config HTTP request to be sent. 185 | * @return {Promise} A promise that resolves with the response details. 186 | */ 187 | public async send(config: HttpRequestConfig): Promise { 188 | return send(config) 189 | .then(resp => { 190 | return resp; 191 | }) 192 | .catch(err => { 193 | if (err.response) { 194 | throw new Error(JSON.stringify(createHttpResponse(err.response))); 195 | } 196 | if (err.error.code === "ETIMEDOUT") { 197 | throw new Error(`Error while making request: ${err.message}.`); 198 | } 199 | throw new Error(`Error while making request: ${err.message}. Error code: ${err.error.code}`); 200 | }); 201 | } 202 | 203 | /** 204 | * Meet the conditions, repeat the request. 205 | * 206 | * @param {HttpRequest} config HTTP request to be sent. 207 | * @return {Promise} A promise that resolves with the response details. 208 | */ 209 | public async sendWithRetry(config: HttpRequestConfig, retryAttempts: number = 0): Promise { 210 | return send(config) 211 | .then(resp => { 212 | return resp; 213 | }) 214 | .catch(err => { 215 | const [delayMillis, canRetry] = this.getRetryDelayMillis(retryAttempts, err); 216 | if (canRetry && delayMillis <= this.retry.maxDelayInMillis) { 217 | return this.waitForRetry(delayMillis).then(() => { 218 | return this.sendWithRetry(config, retryAttempts + 1); 219 | }); 220 | } 221 | if (err.response) { 222 | throw new Error(JSON.stringify(createHttpResponse(err.response))); 223 | } 224 | if (err.error.code === "ETIMEDOUT") { 225 | throw new Error(`Error while making request: ${err.message}.`); 226 | } 227 | throw new Error(`Error while making request: ${err.message}. Error code: ${err.error.code}`); 228 | }); 229 | } 230 | 231 | private async waitForRetry(delayMillis: number): Promise { 232 | if (delayMillis > 0) { 233 | return new Promise(resolve => { 234 | setTimeout(resolve, delayMillis); 235 | }); 236 | } 237 | return Promise.resolve(); 238 | } 239 | 240 | /** 241 | * Parses the Retry-After HTTP header as a milliseconds value. Return value is negative if the Retry-After header 242 | * contains an expired timestamp or otherwise malformed. 243 | */ 244 | private parseRetryAfterIntoMillis(retryAfter: string): number { 245 | const delaySeconds: number = parseInt(retryAfter, 10); 246 | if (!isNaN(delaySeconds)) { 247 | return delaySeconds * 1000; 248 | } 249 | 250 | const date = new Date(retryAfter); 251 | if (!isNaN(date.getTime())) { 252 | return date.getTime() - Date.now(); 253 | } 254 | return -1; 255 | } 256 | 257 | private backOffDelayMillis(retryAttempts: number): number { 258 | if (retryAttempts === 0) { 259 | return 0; 260 | } 261 | 262 | const backOffFactor = this.retry.backOffFactor || 0; 263 | const delayInSeconds = 2 ** retryAttempts * backOffFactor; 264 | return Math.min(delayInSeconds * 1000, this.retry.maxDelayInMillis); 265 | } 266 | 267 | /** 268 | * Checks if a failed request is eligible for a retry, and if so returns the duration to wait before initiating 269 | * the retry. 270 | * 271 | * @param {number} retryAttempts Number of retries completed up to now. 272 | * @param {LowLevelError} err The last encountered error. 273 | * @returns {[number, boolean]} A 2-tuple where the 1st element is the duration to wait before another retry, and the 274 | * 2nd element is a boolean indicating whether the request is eligible for a retry or not. 275 | */ 276 | private getRetryDelayMillis(retryAttempts: number, err: any): [number, boolean] { 277 | if (!this.isRetryEligible(retryAttempts, err)) { 278 | return [0, false]; 279 | } 280 | let response = err.response; 281 | let headers = response ? response.headers : undefined; 282 | if (headers && headers["retry-after"]) { 283 | const delayMillis = this.parseRetryAfterIntoMillis(headers["retry-after"]); 284 | if (delayMillis > 0) { 285 | return [delayMillis, true]; 286 | } 287 | } 288 | 289 | return [this.backOffDelayMillis(retryAttempts), true]; 290 | } 291 | 292 | private isRetryEligible(retryAttempts: number, err: any): boolean { 293 | if (!this.retry) { 294 | return false; 295 | } 296 | 297 | if (retryAttempts >= this.retry.maxRetries) { 298 | return false; 299 | } 300 | if (err.response) { 301 | const statusCodes = this.retry.statusCodes || []; 302 | return statusCodes.indexOf(err.response.status) !== -1; 303 | } 304 | 305 | const retryCodes = this.retry.ioErrorCodes || []; 306 | return retryCodes.indexOf(err.error.code) !== -1; 307 | } 308 | } 309 | 310 | export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD"; 311 | export interface HttpRequestConfig { 312 | method: HttpMethod; 313 | /** Target URL of the request. Should be a well-formed URL including protocol, hostname, port and path. */ 314 | uri: string; 315 | headers?: { [key: string]: string }; 316 | body?: string | object | Buffer; 317 | /** Connect and read timeout (in milliseconds) for the outgoing request. */ 318 | timeout?: number; 319 | json?: boolean; 320 | form?: object; 321 | resolveWithFullResponse?: boolean; 322 | } 323 | 324 | export const ENDPOINT = "https://pushtrslftest.hwcloudtest.cn:28446/v1"; 325 | export const TOKENTIMEOUTERR = "80200003"; 326 | export const TOKENFAILEDERR = "80200001"; 327 | -------------------------------------------------------------------------------- /src/utils/validator.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Number check. 19 | * 20 | * @param {any} value 21 | * @return {boolean} Whether the value is a number or not. 22 | */ 23 | export function isNumber(value: any): boolean { 24 | return typeof value === "number" && !isNaN(value); 25 | } 26 | 27 | /** 28 | * String check. 29 | * 30 | * @param {any} value 31 | * @return {boolean} Whether the value is a string or not. 32 | */ 33 | export function isString(value: any): value is string { 34 | return typeof value === "string"; 35 | } 36 | 37 | /** 38 | * Non-empty string check. 39 | * 40 | * @param {any} value 41 | * @return {boolean} Whether the value is a non-empty string or not. 42 | */ 43 | export function isNonEmptyString(value: any): value is string { 44 | return value !== "" && isString(value); 45 | } 46 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "experimentalDecorators": true, 8 | "emitDecoratorMetadata": true, 9 | "esModuleInterop": true, 10 | "newLine": "lf", 11 | "types": [ 12 | "node" 13 | ] 14 | }, 15 | "files": [ 16 | // "./node_modules/@types/mocha/index.d.ts", 17 | "./node_modules/@types/node/index.d.ts", 18 | ], 19 | "include": [ 20 | "src/**/*.ts", 21 | "index.ts" 22 | ], 23 | "exclude": [ 24 | "./node_modules", 25 | "./dist" 26 | ] 27 | } --------------------------------------------------------------------------------