tmc.js 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -4
- package/lib/consumer.js +3 -3
- package/lib/message.js +47 -42
- package/package.json +1 -1
- package/types/header-type.d.ts +21 -0
- package/types/index.d.ts +445 -426
- package/types/message-fields.d.ts +33 -0
- package/types/message-kind.d.ts +23 -0
- package/types/message-type.d.ts +17 -0
- package/types/message.d.ts +70 -14
- package/types/value-format.d.ts +21 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tmc.js
|
|
2
2
|
|
|
3
3
|
Events driven and chained Taobao Message Channel(TMC) for NodeJS
|
|
4
4
|
|
|
@@ -7,9 +7,9 @@ Events driven and chained Taobao Message Channel(TMC) for NodeJS
|
|
|
7
7
|
`npm i tmc.js`
|
|
8
8
|
|
|
9
9
|
```js
|
|
10
|
-
import
|
|
10
|
+
import Tmc from 'tmc.js';
|
|
11
11
|
|
|
12
|
-
new
|
|
12
|
+
new Tmc('your_app_key', 'your_app_secret')
|
|
13
13
|
.on('taobao_trade_TradeChanged', msg => console.info(msg))
|
|
14
14
|
.taobao_trade_TradeClose(msg => console.info(msg))
|
|
15
15
|
.connect();
|
|
@@ -17,7 +17,7 @@ new TMC('your_app_key', 'your_app_secret')
|
|
|
17
17
|
|
|
18
18
|
## API
|
|
19
19
|
|
|
20
|
-
**`new
|
|
20
|
+
**`new Tmc(appKey: string, appSecret: BinaryLike, groupName?: string | object, options?: object)`**
|
|
21
21
|
|
|
22
22
|
| 参数 | 类型 | 说明 |
|
|
23
23
|
| --- | --- | --- |
|
|
@@ -31,6 +31,135 @@ new TMC('your_app_key', 'your_app_secret')
|
|
|
31
31
|
| options.autoParseContentJson | `boolean` | 自动解析推送消息`$.content.content`字段为对象(默认`true`) |
|
|
32
32
|
| options.autoReplyConfirmation | `boolean` | 以推送的`$.content.id`字段自动`Confirm`消息(默认`true`) |
|
|
33
33
|
|
|
34
|
+
**`tmc.on(topic: string, listener: (this: Tmc, message: Message) => void) => Tmc`**
|
|
35
|
+
|
|
36
|
+
注册 `topic` 消息通知处理函数,默认已内置 [消息](./types/message.in.d.ts) 说明。
|
|
37
|
+
|
|
38
|
+
**`tmc[<topic>](fn: (this: Tmc, message: Message) => void) => Tmc`**
|
|
39
|
+
|
|
40
|
+
直接以 `topic` 为键值,注册消息通知处理函数。
|
|
41
|
+
|
|
42
|
+
**`tmc.reconnect(ms: number) => Tmc`**
|
|
43
|
+
|
|
44
|
+
当消费端 `onerror`/`onclose` 事件发生时,延迟 `ms` 毫秒自动重新建立连接。
|
|
45
|
+
|
|
46
|
+
**`tmc.connect(address?: string) => Tmc`**
|
|
47
|
+
|
|
48
|
+
消费端发起建立连接 `onopen` 事件,`address` 默认为 `ws://mc.api.taobao.com/`。
|
|
49
|
+
|
|
50
|
+
<details><summary>可选设置的 NODE_DEBUG=< label > 环境变量</summary>
|
|
51
|
+
|
|
52
|
+
| label | 说明 |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `tmc` | 开启全部`DEBUG`日志模式
|
|
55
|
+
| `tmc:onping` | 开启 `onping` 时的日志
|
|
56
|
+
| `tmc:onopen` | 开启 `onopen` 时的日志
|
|
57
|
+
| `tmc:onpull` | 开启 `onpull` 时的日志
|
|
58
|
+
| `tmc:onerror` | 开启 `onerror` 时的日志
|
|
59
|
+
| `tmc:onclose` | 开启 `onclose` 时的日志
|
|
60
|
+
| `tmc:onmessage` | 开启全部 `onmessage` 时的日志(即`From`淘宝消息)
|
|
61
|
+
| `tmc:onmessage:connect` | 开启消费端发起连接 `connect` 时的日志
|
|
62
|
+
| `tmc:onmessage:connectack` | 开启消费端回复连接 `connectack` 时的日志
|
|
63
|
+
| `tmc:onmessage:send` | 开启消费端接收到(即`From`淘宝) `send` 的消息时的日志
|
|
64
|
+
| `tmc:onmessage:sendack` | (暂未明确场景)
|
|
65
|
+
| `tmc:onmessage:send:confirm` | 开启消费端回复接收到的(即`From`淘宝消息),发送自动确认 `send:confirm` 时的日志
|
|
66
|
+
|
|
67
|
+
</details>
|
|
68
|
+
|
|
69
|
+
## 支持的TOPICS
|
|
70
|
+
|
|
71
|
+
<details><summary>共计 81+ 类别,439+ 消息数</summary>
|
|
72
|
+
|
|
73
|
+
| 类别 | 消息数 |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| 淘宝交易 | 20 |
|
|
76
|
+
| 淘宝退款 | 13 |
|
|
77
|
+
| 淘宝商品 | 13 |
|
|
78
|
+
| 淘宝分销 | 23 |
|
|
79
|
+
| 淘宝点点 | 12 |
|
|
80
|
+
| 淘宝火车票 | 5 |
|
|
81
|
+
| 平台消息 | 7 |
|
|
82
|
+
| 交易全链路 | 3 |
|
|
83
|
+
| 淘宝机票 | 14 |
|
|
84
|
+
| 导购平台 | 21 |
|
|
85
|
+
| 淘宝汽车票 | 4 |
|
|
86
|
+
| 服务市场 | 9 |
|
|
87
|
+
| 天猫服务 | 16 |
|
|
88
|
+
| 天猫美妆 | 2 |
|
|
89
|
+
| 聚石塔 | 9 |
|
|
90
|
+
| 淘宝物流 | 1 |
|
|
91
|
+
| 阿里通信 | 17 |
|
|
92
|
+
| 天猫魔盒 | 2 |
|
|
93
|
+
| OpenIM消息 | 1 |
|
|
94
|
+
| 网上法庭 | 8 |
|
|
95
|
+
| 电子发票 | 20 |
|
|
96
|
+
| 航旅度假交易 | 8 |
|
|
97
|
+
| YunOS YoC | 2 |
|
|
98
|
+
| 阿里物联 | 2 |
|
|
99
|
+
| 全球购跨境物流 | 1 |
|
|
100
|
+
| 零售plus | 8 |
|
|
101
|
+
| 客户运营平台API | 12 |
|
|
102
|
+
| AE-交易 | 10 |
|
|
103
|
+
| 五道口配送 | 1 |
|
|
104
|
+
| 百川 | 2 |
|
|
105
|
+
| 闲鱼 | 14 |
|
|
106
|
+
| 闲鱼回收商消息 | 5 |
|
|
107
|
+
| 零售通POS开放平台消息 | 4 |
|
|
108
|
+
| DPAAS | 6 |
|
|
109
|
+
| AliGenius | 1 |
|
|
110
|
+
| 智慧门店下行消息 | 2 |
|
|
111
|
+
| 渠道中心API | 4 |
|
|
112
|
+
| 五道口订单 | 22 |
|
|
113
|
+
| 信息平台-采购 | 1 |
|
|
114
|
+
| 1688服务市场 | 1 |
|
|
115
|
+
| 酒店商品消息api | 9 |
|
|
116
|
+
| 新零售终端下行消息 | 1 |
|
|
117
|
+
| 新零售终端上行消息 | 4 |
|
|
118
|
+
| 欢行开放平台 | 1 |
|
|
119
|
+
| 阿里发票 | 5 |
|
|
120
|
+
| 大麦票单状态 | 1 |
|
|
121
|
+
| 五道口营销 | 4 |
|
|
122
|
+
| 酒店签约中心消息 | 3 |
|
|
123
|
+
| 蜂鸟物流 | 6 |
|
|
124
|
+
| 商旅API | 3 |
|
|
125
|
+
| 阿里健康-O2O中台 | 2 |
|
|
126
|
+
| 业务平台新零售-消息上行 | 2 |
|
|
127
|
+
| 大麦第三方票务供应商接入 | 5 |
|
|
128
|
+
| TVOS应用审核平台 | 1 |
|
|
129
|
+
| Gifting送礼 | 1 |
|
|
130
|
+
| 五道口商品 | 2 |
|
|
131
|
+
| HOMEAI | 1 |
|
|
132
|
+
| HOMEAI消息对接 | 5 |
|
|
133
|
+
| 零售通_公共 | 8 |
|
|
134
|
+
| 酒店标准库基础信息变更消息 | 2 |
|
|
135
|
+
| 菜鸟发货工作台 | 1 |
|
|
136
|
+
| IOT-智能制造 | 2 |
|
|
137
|
+
| 智能制造API | 1 |
|
|
138
|
+
| IoT售后解决方案 | 1 |
|
|
139
|
+
| OpenMall-API | 5 |
|
|
140
|
+
| 闲鱼已验货 | 6 |
|
|
141
|
+
| 阿里健康三方机构 | 2 |
|
|
142
|
+
| 聚石塔监控告警 | 2 |
|
|
143
|
+
| 大资产拍卖Top端拍品消息 | 2 |
|
|
144
|
+
| AE-任务平台消息 | 1 |
|
|
145
|
+
| 天猫汽车 | 5 |
|
|
146
|
+
| 阿信消息通知前台类目 | 1 |
|
|
147
|
+
| 阿里健康追溯码 | 1 |
|
|
148
|
+
| 自动驾驶API | 3 |
|
|
149
|
+
| MMC五盘货项目 | 5 |
|
|
150
|
+
| 银泰开放平台消息 | 2 |
|
|
151
|
+
| 阿里智付 | 1 |
|
|
152
|
+
| 代发管理 | 2 |
|
|
153
|
+
| 蚂蚁采购 | 1 |
|
|
154
|
+
| 阿里健康&一树-电商中台对接 | 2 |
|
|
155
|
+
| 阿里健康-疫苗 | 1 |
|
|
156
|
+
|
|
157
|
+
</details>
|
|
158
|
+
|
|
159
|
+
## 链接
|
|
160
|
+
|
|
161
|
+
- [淘宝开放平台-消息服务使用介绍](https://open.taobao.com/doc.htm?docId=101663&docType=1)
|
|
162
|
+
|
|
34
163
|
## License
|
|
35
164
|
|
|
36
165
|
[MIT](LICENSE)
|
package/lib/consumer.js
CHANGED
|
@@ -29,7 +29,7 @@ function ipAddr() {
|
|
|
29
29
|
)?.address;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
class
|
|
32
|
+
class TaoMessageConsumer extends EventEmitter {
|
|
33
33
|
[kAddress] = 'ws://mc.api.taobao.com/';
|
|
34
34
|
|
|
35
35
|
[kGroupName] = 'default';
|
|
@@ -202,5 +202,5 @@ class TaoMessageComsumer extends EventEmitter {
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
module.exports =
|
|
206
|
-
module.exports.default =
|
|
205
|
+
module.exports = TaoMessageConsumer;
|
|
206
|
+
module.exports.default = TaoMessageConsumer;
|
package/lib/message.js
CHANGED
|
@@ -10,6 +10,9 @@ const {
|
|
|
10
10
|
TYPE, CODE, PHRASE, FLAG, TOKEN, CONTENT,
|
|
11
11
|
} = require('./message-fields');
|
|
12
12
|
|
|
13
|
+
const kData = Symbol('kData');
|
|
14
|
+
const kBuffer = Symbol('kBuffer');
|
|
15
|
+
|
|
13
16
|
class Message {
|
|
14
17
|
protocolVersion = 2;
|
|
15
18
|
|
|
@@ -49,7 +52,7 @@ class Message {
|
|
|
49
52
|
|
|
50
53
|
static from(buf) {
|
|
51
54
|
/* eslint-disable-next-line no-use-before-define */
|
|
52
|
-
return new Decoder(buf).
|
|
55
|
+
return new Decoder(buf).message;
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -77,53 +80,53 @@ function pick(size, method, ...value) {
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
class Encoder {
|
|
80
|
-
|
|
83
|
+
[kBuffer] = [];
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
[kData];
|
|
83
86
|
|
|
84
87
|
constructor(message) {
|
|
85
88
|
if (!(message instanceof Message)) {
|
|
86
89
|
throw new TypeError('Must be an instance Message here.');
|
|
87
90
|
}
|
|
88
|
-
this
|
|
91
|
+
this[kData] = message;
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
compress() {
|
|
92
|
-
this.put(this.
|
|
95
|
+
this.put(this[kData].protocolVersion).put(this[kData][TYPE]);
|
|
93
96
|
/* eslint-disable-next-line no-unused-expressions */
|
|
94
|
-
this
|
|
97
|
+
this[kData][CODE] && this.putShort(StatusCode).putShort(this[kData][CODE]);
|
|
95
98
|
/* eslint-disable-next-line no-unused-expressions */
|
|
96
|
-
this
|
|
99
|
+
this[kData][PHRASE] && this.putShort(StatusPhrase).writeCountedString(this[kData][PHRASE]);
|
|
97
100
|
/* eslint-disable-next-line no-unused-expressions */
|
|
98
|
-
this
|
|
101
|
+
this[kData][FLAG] && this.putShort(Flag).putInt(this[kData][FLAG]);
|
|
99
102
|
/* eslint-disable-next-line no-unused-expressions */
|
|
100
|
-
this
|
|
101
|
-
Object.entries(this
|
|
103
|
+
this[kData][TOKEN] && this.putShort(Token).writeCountedString(this[kData][TOKEN]);
|
|
104
|
+
Object.entries(this[kData][CONTENT] || {}).forEach(([k, v]) => this.putShort(Custom).writeCountedString(k).writeCustomValue(v));
|
|
102
105
|
this.putShort(EndOfHeaders);
|
|
103
106
|
|
|
104
107
|
return this;
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
put(value) {
|
|
108
|
-
this.
|
|
111
|
+
this[kBuffer].push(pick(1, writeInt8, Number(value)));
|
|
109
112
|
|
|
110
113
|
return this;
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
putShort(value) {
|
|
114
|
-
this.
|
|
117
|
+
this[kBuffer].push(pick(2, writeInt16LE, Number(value)));
|
|
115
118
|
|
|
116
119
|
return this;
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
putInt(value) {
|
|
120
|
-
this.
|
|
123
|
+
this[kBuffer].push(pick(4, writeInt32LE, Number(value)));
|
|
121
124
|
|
|
122
125
|
return this;
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
putLong(value) {
|
|
126
|
-
this.
|
|
129
|
+
this[kBuffer].push(pick(8, writeBigInt64LE, BigInt(value)));
|
|
127
130
|
|
|
128
131
|
return this;
|
|
129
132
|
}
|
|
@@ -132,7 +135,7 @@ class Encoder {
|
|
|
132
135
|
const len = value ? Buffer.byteLength(value) : 0;
|
|
133
136
|
this.putInt(len);
|
|
134
137
|
/* eslint-disable-next-line no-unused-expressions */
|
|
135
|
-
len && this.
|
|
138
|
+
len && this[kBuffer].push(pick(len, write, value, utf8));
|
|
136
139
|
|
|
137
140
|
return this;
|
|
138
141
|
}
|
|
@@ -173,15 +176,17 @@ class Encoder {
|
|
|
173
176
|
|
|
174
177
|
if (Buffer.isBuffer(value)) {
|
|
175
178
|
this.put(ByteArray).putInt(value.length);
|
|
176
|
-
this.
|
|
179
|
+
this[kBuffer].push(value);
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
return this;
|
|
180
183
|
}
|
|
181
184
|
|
|
182
|
-
get buffer() { return Buffer.concat(this.compress()
|
|
185
|
+
get buffer() { return Buffer.concat(this.compress()[kBuffer]); }
|
|
183
186
|
}
|
|
184
187
|
|
|
188
|
+
const kOffset = Symbol('kOffset');
|
|
189
|
+
|
|
185
190
|
const readInt8 = 'readInt8';
|
|
186
191
|
|
|
187
192
|
const readInt16LE = 'readInt16LE';
|
|
@@ -195,23 +200,23 @@ function read(buffer, method, offset, ...extra) {
|
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
class Decoder {
|
|
198
|
-
|
|
203
|
+
[kOffset] = 1;
|
|
199
204
|
|
|
200
|
-
|
|
205
|
+
[kBuffer];
|
|
201
206
|
|
|
202
|
-
|
|
207
|
+
[kData];
|
|
203
208
|
|
|
204
209
|
constructor(buf) {
|
|
205
210
|
if (!Buffer.isBuffer(buf)) {
|
|
206
211
|
throw new TypeError('Must be an instance Buffer here.');
|
|
207
212
|
}
|
|
208
|
-
this
|
|
209
|
-
this
|
|
213
|
+
this[kBuffer] = buf;
|
|
214
|
+
this[kData] = new Message();
|
|
210
215
|
this.extract();
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
extract() {
|
|
214
|
-
this.
|
|
219
|
+
this[kData].with(TYPE, this.reset().get());
|
|
215
220
|
|
|
216
221
|
let headerType;
|
|
217
222
|
do {
|
|
@@ -219,47 +224,47 @@ class Decoder {
|
|
|
219
224
|
this[`pick${headerType}`]();
|
|
220
225
|
} while (headerType !== EndOfHeaders);
|
|
221
226
|
|
|
222
|
-
this.reset();
|
|
227
|
+
return this.reset();
|
|
223
228
|
}
|
|
224
229
|
|
|
225
|
-
[`pick${EndOfHeaders}`]() { return this
|
|
230
|
+
[`pick${EndOfHeaders}`]() { return this[kData]; }
|
|
226
231
|
|
|
227
|
-
[`pick${Custom}`]() { return this.
|
|
232
|
+
[`pick${Custom}`]() { return this[kData].with(this.readCountedString(), this.readCustomValue()); }
|
|
228
233
|
|
|
229
|
-
[`pick${StatusCode}`]() { return this.
|
|
234
|
+
[`pick${StatusCode}`]() { return this[kData].with(CODE, this.getInt()); }
|
|
230
235
|
|
|
231
|
-
[`pick${StatusPhrase}`]() { return this.
|
|
236
|
+
[`pick${StatusPhrase}`]() { return this[kData].with(PHRASE, this.readCountedString()); }
|
|
232
237
|
|
|
233
|
-
[`pick${Flag}`]() { return this.
|
|
238
|
+
[`pick${Flag}`]() { return this[kData].with(FLAG, this.getInt()); }
|
|
234
239
|
|
|
235
|
-
[`pick${Token}`]() { return this.
|
|
240
|
+
[`pick${Token}`]() { return this[kData].with(TOKEN, this.readCountedString()); }
|
|
236
241
|
|
|
237
242
|
reset() {
|
|
238
|
-
this
|
|
243
|
+
this[kOffset] = 1;
|
|
239
244
|
return this;
|
|
240
245
|
}
|
|
241
246
|
|
|
242
247
|
get() {
|
|
243
|
-
const value = read(this
|
|
244
|
-
this
|
|
248
|
+
const value = read(this[kBuffer], readInt8, this[kOffset]);
|
|
249
|
+
this[kOffset] += 1;
|
|
245
250
|
return Number(value);
|
|
246
251
|
}
|
|
247
252
|
|
|
248
253
|
getShort() {
|
|
249
|
-
const value = read(this
|
|
250
|
-
this
|
|
254
|
+
const value = read(this[kBuffer], readInt16LE, this[kOffset]);
|
|
255
|
+
this[kOffset] += 2;
|
|
251
256
|
return Number(value);
|
|
252
257
|
}
|
|
253
258
|
|
|
254
259
|
getInt() {
|
|
255
|
-
const value = read(this
|
|
256
|
-
this
|
|
260
|
+
const value = read(this[kBuffer], readInt32LE, this[kOffset]);
|
|
261
|
+
this[kOffset] += 4;
|
|
257
262
|
return Number(value);
|
|
258
263
|
}
|
|
259
264
|
|
|
260
265
|
getLong() {
|
|
261
|
-
const value = read(this
|
|
262
|
-
this
|
|
266
|
+
const value = read(this[kBuffer], readBigInt64LE, this[kOffset]);
|
|
267
|
+
this[kOffset] += 8;
|
|
263
268
|
return BigInt(value);
|
|
264
269
|
}
|
|
265
270
|
|
|
@@ -268,8 +273,8 @@ class Decoder {
|
|
|
268
273
|
if (size === 0) {
|
|
269
274
|
return null;
|
|
270
275
|
}
|
|
271
|
-
const buf = this.
|
|
272
|
-
this
|
|
276
|
+
const buf = this[kBuffer].subarray(this[kOffset], this[kOffset] + size);
|
|
277
|
+
this[kOffset] += size;
|
|
273
278
|
return buf;
|
|
274
279
|
}
|
|
275
280
|
|
|
@@ -293,7 +298,7 @@ class Decoder {
|
|
|
293
298
|
|
|
294
299
|
[`parse${CountedString}`]() { return this.readCountedString(); }
|
|
295
300
|
|
|
296
|
-
get message() { return this
|
|
301
|
+
get message() { return this[kData]; }
|
|
297
302
|
}
|
|
298
303
|
|
|
299
304
|
module.exports = Message;
|
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
declare namespace HeaderType {
|
|
2
|
+
type EndOfHeaders = 0;
|
|
3
|
+
type Custom = 1;
|
|
4
|
+
type StatusCode = 2;
|
|
5
|
+
type StatusPhrase = 3;
|
|
6
|
+
type Flag = 4;
|
|
7
|
+
type Token = 5;
|
|
8
|
+
interface includes {
|
|
9
|
+
(value: any): boolean;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare class HeaderType {
|
|
14
|
+
static EndOfHeaders: HeaderType.EndOfHeaders;
|
|
15
|
+
static Custom: HeaderType.Custom;
|
|
16
|
+
static StatusCode: HeaderType.StatusCode;
|
|
17
|
+
static StatusPhrase: HeaderType.StatusPhrase;
|
|
18
|
+
static Flag: HeaderType.Flag;
|
|
19
|
+
static Token: HeaderType.Token;
|
|
20
|
+
static includes: HeaderType.includes;
|
|
21
|
+
}
|