Browse Source

chore: add chat-ts-sdk

wxyyxc1992 5 years ago
commit
1defe3a447
45 changed files with 39514 additions and 0 deletions
  1. 12
    0
      .gitignore
  2. 0
    0
      .gitkeep
  3. 5
    0
      README.md
  4. 786
    0
      docs/message.md
  5. 11
    0
      lerna.json
  6. 14958
    0
      package-lock.json
  7. 85
    0
      package.json
  8. 14
    0
      packages/cts-adapter/types.d.ts
  9. 14
    0
      packages/cts-api/types.d.ts
  10. 61
    0
      packages/cts-core/example/index.ts
  11. 9
    0
      packages/cts-core/example/types.d.ts
  12. 96
    0
      packages/cts-core/package.json
  13. 5
    0
      packages/cts-core/postcss.config.js
  14. BIN
      packages/cts-core/public/favicon.ico
  15. 22
    0
      packages/cts-core/public/index.html
  16. 15
    0
      packages/cts-core/public/manifest.json
  17. 19
    0
      packages/cts-core/scripts/webpack/webpack.config.dev.js
  18. 10
    0
      packages/cts-core/scripts/webpack/webpack.config.umd.js
  19. 335
    0
      packages/cts-core/src/client.ts
  20. 0
    0
      packages/cts-core/src/index.ts
  21. 68
    0
      packages/cts-core/src/packet.ts
  22. 14
    0
      packages/cts-core/src/types/callback.ts
  23. 157
    0
      packages/cts-core/src/utils.ts
  24. 13
    0
      packages/cts-core/tsconfig.cjs.json
  25. 7
    0
      packages/cts-core/tsconfig.json
  26. 3
    0
      packages/cts-core/tslint.json
  27. 14
    0
      packages/cts-core/types.d.ts
  28. 11260
    0
      packages/cts-core/yarn.lock
  29. 5
    0
      postcss.config.js
  30. 1
    0
      scripts/jest/fileMock.js
  31. 18
    0
      scripts/jest/jest.config.js
  32. 1
    0
      scripts/jest/styleMock.js
  33. 166
    0
      scripts/template/template.ejs
  34. 52
    0
      scripts/template/template.js
  35. 0
    0
      scripts/tools/publish_pkgs.sh
  36. 0
    0
      scripts/tools/release_pkgs.sh
  37. 8
    0
      scripts/tools/upgrade_pkgs.sh
  38. 143
    0
      scripts/webpack/webpack.config.base.js
  39. 59
    0
      scripts/webpack/webpack.config.dev.js
  40. 102
    0
      scripts/webpack/webpack.config.prod.js
  41. 45
    0
      scripts/webpack/webpack.config.umd.js
  42. 39
    0
      tsconfig.json
  43. 6
    0
      tsconfig.test.json
  44. 43
    0
      tslint.json
  45. 10833
    0
      yarn.lock

+ 12
- 0
.gitignore View File

@@ -0,0 +1,12 @@
1
+# Node files
2
+node_modules
3
+
4
+# OS junk files
5
+.DS_Store
6
+
7
+# Project specific stuff
8
+.cache-loader
9
+@coverage
10
+*.log
11
+dist
12
+build

+ 0
- 0
.gitkeep View File


+ 5
- 0
README.md View File

@@ -0,0 +1,5 @@
1
+# chat-ts-sdk
2
+
3
+# About
4
+
5
+- [环信 IM SDK](http://docs-im.easemob.com/im/web/basics/message)

+ 786
- 0
docs/message.md View File

@@ -0,0 +1,786 @@
1
+**1. 初始化会话**
2
+
3
+- 请求地址:**/v1/session/start**
4
+- 输入参数:
5
+```
6
+{
7
+    "device": "pc",// 客户端设备信息:pc、pad、mobile、web等
8
+    "os": "linux", // 客户端运行的操作系统
9
+    "os_version": "3.6", // 操作系统版本
10
+    "app": "go-client", // 客户端运行的app
11
+    "app_version": "1.0", // // 客户端运行的app版本
12
+    "tag":{} // 用于发送自定义标记
13
+}
14
+```
15
+
16
+- 请求成功:
17
+```
18
+{
19
+    "value":"5a9fca2a2b93417e5c0378b6" // 会话ID
20
+}
21
+```
22
+
23
+**2. 把用户ID和session绑定在一起:通过用户名和密码登陆**
24
+- 请求地址:**/v1/session/bind/uid**
25
+- 输入参数:
26
+```
27
+{
28
+    "id":"217" // http登陆接口返回的用户id
29
+    "password": // http登陆接口返回的password
30
+}
31
+```
32
+
33
+- 请求成功:
34
+```
35
+返回内容为空
36
+```
37
+
38
+**3. 通过token登陆聊天服务**
39
+- 请求地址:**/v1/session/bind/uid/by/token**
40
+- 输入参数:
41
+```
42
+{
43
+    "token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyMTcsInBsYXRmb3JtIjoiaW9zIn0.JLx_CFhFIdpEnUBJUxjB0BQ-H3PAeeFm-fd1jG0Dun8" // http登陆接口返回的token
44
+}
45
+```
46
+
47
+- 请求成功:
48
+```
49
+返回内容为空
50
+```
51
+
52
+**4. 解除用户id和session的绑定即注销登录聊天服务**
53
+- 请求地址:**/v1/session/unbind/uid**
54
+- 输入参数:
55
+```
56
+返回内容为空
57
+```
58
+
59
+- 请求成功:
60
+```
61
+返回内容为空
62
+```
63
+
64
+**5. 修改在线状态**
65
+- 请求地址:**/v1/session/status**
66
+- 输入参数:
67
+```
68
+{
69
+    "status":"on" // on:在线 off:离线 busy:忙碌
70
+}
71
+```
72
+
73
+- 请求成功:
74
+```
75
+返回内容为空
76
+```
77
+
78
+**6. 获取当前登陆用户的设备信息**
79
+- 请求地址:**/v1/session/lists**
80
+- 输入参数:
81
+```
82
+参数为空
83
+```
84
+
85
+- 请求成功:
86
+```
87
+[{
88
+		"token": "5a9fd1772b93417e5c0378b8",
89
+		"device": "pc",
90
+		"os": "linux",
91
+		"os_version": "3.6",
92
+		"app": "go-client",
93
+		"app_version": "1.0",
94
+		"push_token": "c62c66585e1ba998326c0bfd2cccc6aa"
95
+	},
96
+	{
97
+		"token": "5a9fd1772b93417e5c0378b8",
98
+		"device": "pc",
99
+		"os": "linux",
100
+		"os_version": "3.6",
101
+		"app": "go-client",
102
+		"app_version": "1.0",
103
+		"push_token": "c62c66585e1ba998326c0bfd2cccc6aa"
104
+	}
105
+]
106
+```
107
+
108
+**7. 发送消息:接收消息需要有消息监听/v1/message/listener**
109
+- 请求地址:**/v1/send/message**
110
+- 输入参数:
111
+```
112
+{
113
+    "from": "217", // 消息发送者
114
+    "type": "chat", // 消息类型:chat:单聊 group: 群聊 room:聊天室
115
+    "to": "45", // 消息接收者
116
+    "msg": {
117
+        "message": "push message to you.",
118
+        "type": "text" // 消息类型:text audio image video
119
+    },
120
+    "ext": null // 扩展消息
121
+}
122
+```
123
+
124
+- 请求成功:
125
+```
126
+{
127
+    "msg_id":"5a9fd21d2b93417e5c0378ba", // 消息ID
128
+    "status":1, 消息状态 1:已发送 2:已送达 3:已读
129
+    "create_at":1520423453
130
+}
131
+```
132
+
133
+**8. 标记消息已送达**
134
+- 请求地址:**/v1/mark/message/serviced**
135
+- 输入参数:
136
+```
137
+{"msg_id":""}
138
+```
139
+
140
+- 请求成功:
141
+```
142
+返回内容为空
143
+```
144
+
145
+**9. 标记消息已送读**
146
+- 请求地址:**/v1/mark/messages/read**
147
+- 输入参数:
148
+```
149
+{"msg_id":""}
150
+```
151
+
152
+- 请求成功:
153
+```
154
+返回内容为空
155
+```
156
+
157
+**10. 增量获取所有会话信息**
158
+- 请求地址:**/v1/get/all/conversation**
159
+- 输入参数:
160
+```
161
+{
162
+    "last_pull":0 // 最后一次获取的时间 0的话是全量获取
163
+}
164
+```
165
+
166
+- 请求成功:
167
+```
168
+[{
169
+	"from": "62980",
170
+	"to": "217",
171
+	"type": "chat",
172
+	"msg": {
173
+		"message": "😊",
174
+		"type": "text"
175
+	},
176
+	"ext": {},
177
+	"num": 0,
178
+	"deleted": false,
179
+	"create_at": 1520217899,
180
+	"update_at": 1520217899
181
+}, {
182
+	"from": "22428",
183
+	"to": "217",
184
+	"type": "chat",
185
+	"msg": {
186
+		"message": "😄😄",
187
+		"type": "text"
188
+	},
189
+	"ext": {},
190
+	"num": 0,
191
+	"deleted": false,
192
+	"create_at": 1520282463,
193
+	"update_at": 1520282463
194
+}]
195
+```
196
+
197
+**11. 获取联系人列表**
198
+- 请求地址:**/v1/get/all/contact**
199
+- 输入参数:
200
+```
201
+{
202
+    "last_pull":0 // 最后一次获取的时间 0的话是全量获取
203
+}
204
+```
205
+
206
+- 请求成功:
207
+```
208
+{
209
+  "avatar_prefix": "https://links123-images.oss-cn-hangzhou.aliyuncs.com/avatar/",
210
+  "fans": [
211
+    {
212
+      "user_id": 22428,
213
+      "user_nickname": "paula",
214
+      "user_avatar": "2016/9/5/00fa5267353a2f3979bec4bec0df9c0b.png",
215
+      "user_mark": "",
216
+      "user_is_teacher": 0,
217
+      "user_first_letter": "P",
218
+      "type": 5,
219
+      "is_masking": false,
220
+      "is_stick": true,
221
+      "no_disturbing": false,
222
+      "create_at": 1520282388,
223
+      "update_at": 1520282406
224
+    },
225
+    {
226
+      "user_id": 62993,
227
+      "user_nickname": "lk8846wqw",
228
+      "user_avatar": "",
229
+      "user_mark": "",
230
+      "user_is_teacher": 0,
231
+      "user_first_letter": "L",
232
+      "type": 5,
233
+      "is_masking": true,
234
+      "is_stick": false,
235
+      "no_disturbing": false,
236
+      "create_at": 1520304485,
237
+      "update_at": 1520306097
238
+    }
239
+  ],
240
+  "followers": [
241
+    {
242
+      "friend_id": 217,
243
+      "friend_nickname": "",
244
+      "friend_avatar": "",
245
+      "friend_mark": "",
246
+      "friend_is_teacher": 1,
247
+      "friend_first_letter": "#",
248
+      "type": 1,
249
+      "is_masking": false,
250
+      "is_stick": false,
251
+      "no_disturbing": false,
252
+      "create_at": 1516809946,
253
+      "update_at": 1520350402
254
+    },
255
+    {
256
+      "friend_id": 62980,
257
+      "friend_nickname": "lk9290lrn",
258
+      "friend_avatar": "2018/1/25/734b55ddf5435fa8e173df8fcbd93d1b.png",
259
+      "friend_mark": "",
260
+      "friend_is_teacher": 0,
261
+      "friend_first_letter": "L",
262
+      "type": 5,
263
+      "is_masking": false,
264
+      "is_stick": false,
265
+      "no_disturbing": false,
266
+      "create_at": 1516851073,
267
+      "update_at": 1519272032
268
+    }"server_time": 1520424326
269
+  }
270
+```
271
+
272
+**12. 添加联系人**
273
+- 请求地址:**/v1/add/contact**
274
+- 输入参数:
275
+```
276
+{
277
+    "to_add_username":"45", // 被添加人
278
+    "reason":"I am paul, add me as your friend please." // 加好友原因
279
+}
280
+```
281
+
282
+- 请求成功:
283
+```
284
+{
285
+  "id": "5a9fd7dc2b93417e5c0378bd",
286
+  "from": "217",
287
+  "to": "45",
288
+  "reason": "I am paul, add me as your friend please.",
289
+  "status": 1,
290
+  "create_at": 1520424924,
291
+  "update_at": 1520424924
292
+}
293
+```
294
+
295
+**13. 删除联系人**
296
+- 请求地址:**/v1/delete/contact**
297
+- 输入参数:
298
+```
299
+{
300
+    "fid":""// 好友id
301
+}
302
+```
303
+
304
+- 请求成功:
305
+```
306
+返回内容为空
307
+```
308
+
309
+**14. 屏蔽联系人**
310
+- 请求地址:**/v1/add/contact/masking**
311
+- 输入参数:
312
+```
313
+{
314
+    "fid":""// 好友id
315
+}
316
+```
317
+
318
+- 请求成功:
319
+```
320
+返回内容为空
321
+```
322
+
323
+**15. 屏蔽联系人**
324
+- 请求地址:**/v1/remove/contact/masking**
325
+- 输入参数:
326
+```
327
+{
328
+    "fid":""// 好友id
329
+}
330
+```
331
+
332
+- 请求成功:
333
+```
334
+返回内容为空
335
+```
336
+
337
+**16. 置顶联系人**
338
+- 请求地址:**/v1/add/contact/stickg**
339
+- 输入参数:
340
+```
341
+{
342
+    "fid":""// 好友id
343
+}
344
+```
345
+
346
+- 请求成功:
347
+```
348
+返回内容为空
349
+```
350
+
351
+**16. 解除置顶联系人**
352
+- 请求地址:**/v1/remove/contact/stick**
353
+- 输入参数:
354
+```
355
+{
356
+    "fid":""// 好友id
357
+}
358
+```
359
+
360
+- 请求成功:
361
+```
362
+返回内容为空
363
+```
364
+
365
+**17. 消息免打扰**
366
+- 请求地址:**/v1/add/contact/no/disturbing**
367
+- 输入参数:
368
+```
369
+{
370
+    "fid":""// 好友id
371
+}
372
+```
373
+
374
+- 请求成功:
375
+```
376
+返回内容为空
377
+```
378
+
379
+**18. 取消消息免打扰**
380
+- 请求地址:**/v1/remove/contact/no/disturbing**
381
+- 输入参数:
382
+```
383
+{
384
+    "fid":""// 好友id
385
+}
386
+```
387
+
388
+- 请求成功:
389
+```
390
+返回内容为空
391
+```
392
+
393
+**19. 同意添加好友**
394
+- 请求地址:**/v1/add/contact/agree**
395
+- 输入参数:
396
+```
397
+{
398
+    "id":""// 消息id
399
+    "to":// 被添加好友的人
400
+}
401
+```
402
+
403
+- 请求成功:
404
+```
405
+返回内容为空
406
+```
407
+
408
+**20. 拒绝添加好友**
409
+- 请求地址:**/v1/add/contact/reject**
410
+- 输入参数:
411
+```
412
+{
413
+    "id":""// 消息id
414
+    "to":// 被拒绝的人
415
+}
416
+```
417
+
418
+- 请求成功:
419
+```
420
+返回内容为空
421
+```
422
+
423
+**21. 好友申请已送达**
424
+- 请求地址:**/v1/add/contact/serviced**
425
+- 输入参数:
426
+```
427
+{
428
+    "id":""// 消息id
429
+    "to":// 被送达人
430
+}
431
+```
432
+
433
+- 请求成功:
434
+```
435
+返回内容为空
436
+```
437
+
438
+**22. 创建聊天室**
439
+- 请求地址:**/v1/create/chatroom**
440
+- 输入参数:
441
+```
442
+{
443
+  "subject": "test",// 聊天室名称
444
+  "description": "test chatroom", // 聊天室描述
445
+  "welcome_message": "welcome to chatroom", // 欢迎词
446
+  "max": 1000 // 聊天室的最大人数
447
+}
448
+```
449
+
450
+- 请求成功:
451
+```
452
+{"cid":"5aa003dd2b93417e5c0378be"// 聊天室ID}
453
+```
454
+
455
+**23. 销毁聊天室**
456
+- 请求地址:**/v1/destroy/chatroom**
457
+- 输入参数:
458
+```
459
+{"room_id":"5a8bce182b934169cf517b41"}
460
+```
461
+
462
+- 请求成功:
463
+```
464
+返回内容为空
465
+```
466
+
467
+**23. 销毁聊天室**
468
+- 请求地址:**/v1/destroy/chatroom**
469
+- 输入参数:
470
+```
471
+{"room_id":"5a8bce182b934169cf517b41"}
472
+```
473
+
474
+- 请求成功:
475
+```
476
+返回内容为空
477
+```
478
+
479
+**24. 离开聊天室**
480
+- 请求地址:**/v1/leave/chatroom**
481
+- 输入参数:
482
+```
483
+{"room_id":"5a8bce182b934169cf517b41"}
484
+```
485
+
486
+- 请求成功:
487
+```
488
+返回内容为空
489
+```
490
+
491
+**25. 获取聊天室详情**
492
+- 请求地址:**/v1/get/chatroom/profile**
493
+- 输入参数:
494
+```
495
+{"room_id":"5a8bce182b934169cf517b41"}
496
+```
497
+
498
+- 请求成功:
499
+```
500
+{
501
+  "profile": {
502
+    "id": "5a8bce182b934169cf517b41", // 聊天室ID
503
+    "name": "new subject",
504
+    "description": "new description",
505
+    "notice": "new notice", // 聊天室公告
506
+    "welcome_message": "welcome to chatroom", 
507
+    "avatar": "",// 头像
508
+    "max": 1000, // 聊天室最大人数
509
+    "count": 0, // 聊天室当前人数
510
+    "cover": "", // 聊天室封面大图
511
+    "type": 1
512
+  },
513
+  "user_type": 9
514
+}
515
+```
516
+
517
+**26. 修改聊天室名称**
518
+- 请求地址:**/v1/update/chatroom/subject**
519
+- 输入参数:
520
+```
521
+{"room_id":"5a8bce182b934169cf517b41"}
522
+```
523
+
524
+- 请求成功:
525
+```
526
+返回内容为空
527
+```
528
+
529
+**27. 修改聊天室描述信息**
530
+- 请求地址:**/v1/update/chatroom/description**
531
+- 输入参数:
532
+```
533
+{"room_id":"5a8bce182b934169cf517b41"}
534
+```
535
+
536
+- 请求成功:
537
+```
538
+返回内容为空
539
+```
540
+
541
+**28. 添加管理员**
542
+- 请求地址:**/v1/add/chatroom/admin**
543
+- 输入参数:
544
+```
545
+{"room_id":"5a8bce182b934169cf517b41", "admin_id":""}
546
+```
547
+
548
+- 请求成功:
549
+```
550
+返回内容为空
551
+```
552
+
553
+**30. 批量添加管理员**
554
+- 请求地址:**/v1/add/chatroom/admins**
555
+- 输入参数:
556
+```
557
+{"room_id":"5a8bce182b934169cf517b41", "admins_id":[45, 217]}
558
+```
559
+
560
+- 请求成功:
561
+```
562
+返回内容为空
563
+```
564
+
565
+**29. 移除管理员**
566
+- 请求地址:**/v1/add/chatroom/admin**
567
+- 输入参数:
568
+```
569
+{"room_id":"5a8bce182b934169cf517b41", "admin_id":""}
570
+```
571
+
572
+- 请求成功:
573
+```
574
+返回内容为空
575
+```
576
+**29. 移除管理员**
577
+- 请求地址:**/v1/add/chatroom/admin**
578
+- 输入参数:
579
+```
580
+{"room_id":"5a8bce182b934169cf517b41", "admin_id":""}
581
+```
582
+
583
+- 请求成功:
584
+```
585
+返回内容为空
586
+```
587
+**30. 批量删除管理员**
588
+- 请求地址:**/v1/remove/chatroom/admins**
589
+- 输入参数:
590
+```
591
+{"room_id":"5a8bce182b934169cf517b41", "admins_id":[45, 217]}
592
+```
593
+
594
+- 请求成功:
595
+```
596
+返回内容为空
597
+```
598
+
599
+**30. 删除聊天室成员**
600
+- 请求地址:**/v1/remove/chatroom/member**
601
+- 输入参数:
602
+```
603
+{"room_id":"5a8bce182b934169cf517b41", "member":""}
604
+```
605
+
606
+- 请求成功:
607
+```
608
+返回内容为空
609
+```
610
+
611
+**31.加入聊天室**
612
+- 请求地址:**/v1/join/chatroom**
613
+- 输入参数:
614
+```
615
+{"room_id":"5a8bce182b934169cf517b41"}
616
+```
617
+
618
+- 请求成功:
619
+```
620
+[{
621
+		"from": "217",
622
+		"msg_id": "",
623
+		"type": "room",
624
+		"msg": {
625
+			"message": "Hi",
626
+			"type": "text"
627
+		},
628
+		"ext": {},
629
+		"to": "5a8bce182b934169cf517b41",
630
+		"create_at": 1520350764
631
+	},
632
+	{
633
+		"from": "62947",
634
+		"msg_id": "",
635
+		"type": "room",
636
+		"msg": {
637
+			"message": "😉😉😉😉",
638
+			"type": "text"
639
+		},
640
+		"ext": {},
641
+		"to": "5a8bcba72b934168d1edc2e0",
642
+		"create_at": 1520562559
643
+	}
644
+]
645
+```
646
+
647
+
648
+**32.获取置顶聊天室列表**
649
+- 请求地址:**/v1/fetch/stick/chatroom**
650
+- 输入参数:
651
+```
652
+{"cache_time":10位时间戳}
653
+```
654
+
655
+- 请求成功:
656
+```
657
+{
658
+  "server_time": 1520437237,
659
+  "avatar_prefix": "https://links123-images.oss-cn-hangzhou.aliyuncs.com/avatar/",
660
+  "chat_room": [
661
+    {
662
+      "id": "5a8bcba72b934168d1edc2e0",
663
+      "cn_name": "麦聊",
664
+      "en_name": "Wheat chat"
665
+      "avatar": "wheat.png"
666
+    },
667
+    {
668
+      "id": "5a8bce182b934169cf517b41",
669
+      "cn_name": "米聊",
670
+      "en_name": "Rice chat",
671
+      "avatar": "rice.png"
672
+    }
673
+  ]
674
+}
675
+```
676
+
677
+**32.获取聊天室历史消息**
678
+- 请求地址:**/v1/history/message**
679
+- 输入参数:
680
+```
681
+{
682
+"contact_id":"5a8bce182b934169cf517b41",
683
+"chat_type":"room"
684
+"start_time":int64,  // 最新一条消息的10位时间戳
685
+"limit":10, // 每次返回历史消息的条数
686
+}
687
+```
688
+
689
+- 请求成功:
690
+```
691
+[{
692
+		"from": "217",
693
+		"msg_id": "",
694
+		"type": "room",
695
+		"msg": {
696
+			"message": "Hi",
697
+			"type": "text"
698
+		},
699
+		"ext": {},
700
+		"to": "5a8bce182b934169cf517b41",
701
+		"create_at": 1520350764
702
+	},
703
+	{
704
+		"from": "62947",
705
+		"msg_id": "",
706
+		"type": "room",
707
+		"msg": {
708
+			"message": "😉😉😉😉",
709
+			"type": "text"
710
+		},
711
+		"ext": {},
712
+		"to": "5a8bcba72b934168d1edc2e0",
713
+		"create_at": 1520562559
714
+	}
715
+]
716
+```
717
+
718
+**33. 根据聊天室成员权限后去聊天室成员**
719
+- 请求地址:**/v1/fetch/chatroom/members**
720
+- 输入参数:
721
+```
722
+{"room_id":"5a8bce182b934169cf517b41", "authority":数字类型1 owner, 2 admin 9 普通成员}
723
+```
724
+
725
+- 请求成功:
726
+```
727
+[{
728
+	"user_id": 62982,
729
+	"nickname": "陽陽陽陽陽陽陽陽陽陽",
730
+	"avatar": "2018/1/2/732f5927510695b6ef203ed8e786fba5.png",
731
+	"mark": "",
732
+	"type": 9,
733
+	"first_letter": "Y"
734
+}, {
735
+	"user_id": 62980,
736
+	"nickname": "lk9290lrn",
737
+	"avatar": "2018/1/25/734b55ddf5435fa8e173df8fcbd93d1b.png",
738
+	"mark": "",
739
+	"type": 9,
740
+	"first_letter": "L"
741
+}]
742
+```
743
+
744
+**34.搜索聊天室历史消息**
745
+- 请求地址:**/v1/search/history/message**
746
+- 输入参数:
747
+```
748
+{
749
+"contact_id":"5a8bce182b934169cf517b41",
750
+"chat_type":"room"
751
+"keyword":"",  // 搜索的关键字,模糊匹配,输入的关键词越多返回的越准确
752
+"limit":10, // 每次返回历史消息的条数
753
+}
754
+```
755
+
756
+- 请求成功:
757
+```
758
+[{
759
+		"from": "217",
760
+		"msg_id": "",
761
+		"type": "room",
762
+		"msg": {
763
+			"message": "Hi",
764
+			"type": "text"
765
+		},
766
+		"ext": {},
767
+		"to": "5a8bce182b934169cf517b41",
768
+		"create_at": 1520350764
769
+	},
770
+	{
771
+		"from": "62947",
772
+		"msg_id": "",
773
+		"type": "room",
774
+		"msg": {
775
+			"message": "😉😉😉😉",
776
+			"type": "text"
777
+		},
778
+		"ext": {},
779
+		"to": "5a8bcba72b934168d1edc2e0",
780
+		"create_at": 1520562559
781
+	}
782
+]
783
+```
784
+
785
+
786
+

+ 11
- 0
lerna.json View File

@@ -0,0 +1,11 @@
1
+{
2
+  "version": "0.9.5",
3
+  "npmClient": "yarn",
4
+  "packages": ["packages/*"],
5
+  "command": {
6
+    "publish": {
7
+      "ignoreChanges": ["*.md"],
8
+      "message": "Release %s"
9
+    }
10
+  }
11
+}

+ 14958
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 85
- 0
package.json View File

@@ -0,0 +1,85 @@
1
+{
2
+  "name": "cts",
3
+  "version": "0.0.1",
4
+  "description": "cts, chat-ts-sdk",
5
+  "repository": {
6
+    "type": "git",
7
+    "url": "https://git.links123.net/Chevalier/chat-ts-sdk"
8
+  },
9
+  "license": "MIT",
10
+  "keywords": [
11
+    "react",
12
+    "redux",
13
+    "mobx",
14
+    "webpack",
15
+    "typescript"
16
+  ],
17
+  "author": "Chevalier",
18
+  "prettier": {
19
+    "printWidth": 100,
20
+    "singleQuote": true
21
+  },
22
+  "lint-staged": {
23
+    "*.{ts,tsx,scss,less,md}": [
24
+      "prettier --write",
25
+      "git add"
26
+    ]
27
+  },
28
+  "scripts": {
29
+    "bootstrap": "npm install && lerna clean --yes && lerna bootstrap",
30
+    "build": "npm run clean && lerna exec -- npm run build",
31
+    "clean": "lerna run clean --parallel",
32
+    "cleanCov": "rimraf @coverage",
33
+    "dev": "webpack-dev-server --config ./scripts/webpack/webpack.config.dev.js --hot",
34
+    "lint": "tslint -c tslint.json 'src/**/*.(ts|tsx)'",
35
+    "precommit": "lint-staged",
36
+    "prettier-all": "prettier --write 'src/**/*' '!src/{assets,datas}/**'",
37
+    "start": "webpack-dashboard -- npm run dev",
38
+    "test": "jest --config ./scripts/jest/jest.config.js",
39
+    "test:watch": "npm test -- --watch",
40
+    "test:cov": "npm run cleanCov && npm test -- --coverage",
41
+    "upgrade": "./scripts/tools/upgrade_pkgs.sh"
42
+  },
43
+  "devDependencies": {
44
+    "@types/jest": "24.0.12",
45
+    "@types/react-dom": "^16.8.4",
46
+    "autoprefixer": "9.5.1",
47
+    "awesome-typescript-loader": "^5.2.1",
48
+    "copy-webpack-plugin": "^5.0.3",
49
+    "css-loader": "2.1.1",
50
+    "enzyme": "^3.9.0",
51
+    "file-loader": "3.0.1",
52
+    "fork-ts-checker-webpack-plugin": "^1.3.0",
53
+    "html-webpack-plugin": "^3.2.0",
54
+    "html-webpack-template": "^6.2.0",
55
+    "jest": "24.8.0",
56
+    "jest-cli": "24.8.0",
57
+    "json-server": "0.14.2",
58
+    "lerna": "^3.13.4",
59
+    "less": "^3.9.0",
60
+    "less-loader": "^5.0.0",
61
+    "mini-css-extract-plugin": "^0.6.0",
62
+    "optimize-css-assets-webpack-plugin": "5.0.1",
63
+    "parallelshell": "^3.0.2",
64
+    "postcss-loader": "3.0.0",
65
+    "react-hot-loader": "^4.8.4",
66
+    "resolve-url-loader": "3.1.0",
67
+    "rimraf": "^2.6.3",
68
+    "style-loader": "0.23.1",
69
+    "ts-jest": "^24.0.2",
70
+    "ts-loader": "5.4.5",
71
+    "tsconfig-paths-webpack-plugin": "^3.2.0",
72
+    "tslint": "^5.16.0",
73
+    "tslint-config-prettier": "^1.18.0",
74
+    "tslint-react": "^4.0.0",
75
+    "typescript": "3.4.5",
76
+    "uglifyjs-webpack-plugin": "2.1.2",
77
+    "url-loader": "^1.1.2",
78
+    "wasm-loader": "^1.3.0",
79
+    "webpack": "^4.30.0",
80
+    "webpack-cli": "3.3.2",
81
+    "webpack-dashboard": "3.0.5",
82
+    "webpack-dev-server": "^3.3.1",
83
+    "workerize-loader": "^1.0.4"
84
+  }
85
+}

+ 14
- 0
packages/cts-adapter/types.d.ts View File

@@ -0,0 +1,14 @@
1
+declare module '*.less' {
2
+  const styles: Record<string, string>;
3
+  export = styles;
4
+}
5
+
6
+declare module '*.css' {
7
+  const content: any;
8
+  export default content;
9
+}
10
+
11
+declare module '*.svg' {
12
+  const content: string;
13
+  export default content;
14
+}

+ 14
- 0
packages/cts-api/types.d.ts View File

@@ -0,0 +1,14 @@
1
+declare module '*.less' {
2
+  const styles: Record<string, string>;
3
+  export = styles;
4
+}
5
+
6
+declare module '*.css' {
7
+  const content: any;
8
+  export default content;
9
+}
10
+
11
+declare module '*.svg' {
12
+  const content: string;
13
+  export default content;
14
+}

+ 61
- 0
packages/cts-core/example/index.ts View File

@@ -0,0 +1,61 @@
1
+import { Client } from '../src/client';
2
+
3
+const url = 'ws://127.0.0.1:8081';
4
+const client = new Client(
5
+  url,
6
+  new (class {
7
+    public onOpen(): void {
8
+      client.ping(
9
+        {},
10
+        new (class {
11
+          public onStart(): void {
12
+            console.log('start ping');
13
+          }
14
+
15
+          public onSuccess(data: string): void {
16
+            console.log('ping successful:', data);
17
+          }
18
+
19
+          public onError(code: number, message: string): void {
20
+            console.log('ping error:', message);
21
+          }
22
+
23
+          public onEnd(): void {
24
+            console.log('end ping');
25
+          }
26
+        })()
27
+      );
28
+
29
+      client.syncSend(
30
+        '/v1/healthy',
31
+        {},
32
+        new (class {
33
+          public onStart(): void {
34
+            console.log('start request');
35
+          }
36
+
37
+          public onSuccess(data: string): void {
38
+            console.log('request successful:', data);
39
+          }
40
+
41
+          public onError(code: number, message: string): void {
42
+            console.log('request error:', message);
43
+          }
44
+
45
+          public onEnd(): void {
46
+            console.log('end request');
47
+          }
48
+        })()
49
+      );
50
+    }
51
+
52
+    public onClose(ev: Event): void {
53
+      console.log('connection error', ev);
54
+      console.log(ev);
55
+    }
56
+
57
+    public onError(): void {
58
+      console.log('close connection');
59
+    }
60
+  })()
61
+);

+ 9
- 0
packages/cts-core/example/types.d.ts View File

@@ -0,0 +1,9 @@
1
+declare module '*.less' {
2
+  const styles: Record<string, string>;
3
+  export = styles;
4
+}
5
+
6
+declare module '*.css' {
7
+  const content: any;
8
+  export default content;
9
+}

+ 96
- 0
packages/cts-core/package.json View File

@@ -0,0 +1,96 @@
1
+{
2
+  "author": "Chevalier",
3
+  "description": "cts-core",
4
+  "license": "MIT",
5
+  "keywords": [
6
+    "webpack",
7
+    "react"
8
+  ],
9
+  "name": "cts-core",
10
+  "version": "0.0.1-alpha.1",
11
+  "repository": {
12
+    "type": "git",
13
+    "url": "https://git.links123.net/Chevalier/chat-ts-sdk"
14
+  },
15
+  "main": "dist/index.js",
16
+  "types": "dist/types/index.d.ts",
17
+  "prettier": {
18
+    "printWidth": 100,
19
+    "singleQuote": true
20
+  },
21
+  "lint-staged": {
22
+    "*.{ts,tsx,scss,less,md}": [
23
+      "prettier --write",
24
+      "git add"
25
+    ]
26
+  },
27
+  "scripts": {
28
+    "build": "npm run clean && npm run build:cjs && npm run copy && npm run build:umd",
29
+    "build:cjs": "tsc --project ./tsconfig.cjs.json && npm run copy",
30
+    "build:umd": "NODE_ENV=production webpack --config ./scripts/webpack/webpack.config.umd.js",
31
+    "clean": "rimraf dist",
32
+    "copy": "copyfiles -u 1 './src/**/*.(less|svg)' dist/cjs/",
33
+    "dev": "webpack-dev-server --config ./scripts/webpack/webpack.config.dev.js --hot",
34
+    "pub": "npm run clean && npm run build && npm publish",
35
+    "start": "npm run dev"
36
+  },
37
+  "dependencies": {
38
+    "node-int64": "^0.4.0",
39
+    "uuid": "^3.3.2"
40
+  },
41
+  "devDependencies": {
42
+    "@types/classnames": "^2.2.7",
43
+    "@types/crypto-js": "^3.1.43",
44
+    "@types/jest": "24.0.12",
45
+    "@types/node-int64": "^0.4.29",
46
+    "@types/siema": "^1.4.3",
47
+    "@types/uuid": "^3.4.4",
48
+    "autoprefixer": "9.5.1",
49
+    "awesome-typescript-loader": "^5.2.1",
50
+    "babel-core": "^6.26.3",
51
+    "classnames": "^2.2.6",
52
+    "copy-webpack-plugin": "^5.0.3",
53
+    "copyfiles": "^2.1.0",
54
+    "crypto-js": "^3.1.9-1",
55
+    "css-loader": "2.1.1",
56
+    "enzyme": "^3.9.0",
57
+    "file-loader": "3.0.1",
58
+    "fork-ts-checker-webpack-plugin": "^1.3.0",
59
+    "html-webpack-plugin": "^3.2.0",
60
+    "html-webpack-template": "^6.2.0",
61
+    "jest": "24.8.0",
62
+    "jest-cli": "24.8.0",
63
+    "json-server": "0.14.2",
64
+    "lerna": "^3.13.4",
65
+    "less": "^3.9.0",
66
+    "less-loader": "^5.0.0",
67
+    "mini-css-extract-plugin": "^0.6.0",
68
+    "optimize-css-assets-webpack-plugin": "5.0.1",
69
+    "parallelshell": "^3.0.2",
70
+    "postcss-loader": "3.0.0",
71
+    "react-hot-loader": "^4.8.4",
72
+    "react-router-dom": "^5.0.0",
73
+    "resolve-url-loader": "3.1.0",
74
+    "rimraf": "^2.6.3",
75
+    "style-loader": "0.23.1",
76
+    "svg-inline-loader": "^0.8.0",
77
+    "ts-jest": "^24.0.2",
78
+    "ts-loader": "5.4.5",
79
+    "tsconfig-paths-webpack-plugin": "^3.2.0",
80
+    "tslint": "^5.16.0",
81
+    "tslint-config-prettier": "^1.18.0",
82
+    "tslint-react": "^4.0.0",
83
+    "typescript": "3.4.5",
84
+    "uglifyjs-webpack-plugin": "2.1.2",
85
+    "url-loader": "^1.1.2",
86
+    "wasm-loader": "^1.3.0",
87
+    "webpack": "^4.30.0",
88
+    "webpack-cli": "3.3.2",
89
+    "webpack-dashboard": "3.0.5",
90
+    "webpack-dev-server": "^3.3.1",
91
+    "webpack-merge": "^4.2.1"
92
+  },
93
+  "files": [
94
+    "dist/"
95
+  ]
96
+}

+ 5
- 0
packages/cts-core/postcss.config.js View File

@@ -0,0 +1,5 @@
1
+const autoprefixer = require('autoprefixer');
2
+
3
+module.exports = {
4
+  plugins: [autoprefixer({ browsers: ['last 4 versions'], flexbox: 'no-2009' })]
5
+};

BIN
packages/cts-core/public/favicon.ico View File


+ 22
- 0
packages/cts-core/public/index.html View File

@@ -0,0 +1,22 @@
1
+<html lang="en">
2
+  <head>
3
+    <meta charset="UTF-8" />
4
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
5
+    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
6
+    <title>chat-ts-sdk</title>
7
+    <style></style>
8
+  </head>
9
+  <body>
10
+    <body>
11
+      <noscript>You need to enable JavaScript to run this app.</noscript>
12
+      <div id="demos">
13
+        Please open the Console
14
+      </div>
15
+
16
+      <script>
17
+        window.__DEV_APP__ = {};
18
+      </script>
19
+      <script src="./index.js"></script>
20
+    </body>
21
+  </body>
22
+</html>

+ 15
- 0
packages/cts-core/public/manifest.json View File

@@ -0,0 +1,15 @@
1
+{
2
+  "short_name": "RTW",
3
+  "name": "Micro Frontend Boilerplate",
4
+  "icons": [
5
+    {
6
+      "src": "favicon.ico",
7
+      "sizes": "64x64 32x32 24x24 16x16",
8
+      "type": "image/x-icon"
9
+    }
10
+  ],
11
+  "start_url": "./index.html",
12
+  "display": "standalone",
13
+  "theme_color": "#000000",
14
+  "background_color": "#ffffff"
15
+}

+ 19
- 0
packages/cts-core/scripts/webpack/webpack.config.dev.js View File

@@ -0,0 +1,19 @@
1
+const path = require('path');
2
+const merge = require('webpack-merge');
3
+
4
+const devConfig = require('../../../../scripts/webpack/webpack.config.dev');
5
+
6
+module.exports = merge(devConfig, {
7
+  entry: {
8
+    index: path.resolve(__dirname, '../../example/index.ts')
9
+  },
10
+  devServer: {
11
+    contentBase: path.resolve(__dirname, '../../public')
12
+  },
13
+  resolve: {
14
+    alias: {
15
+      react: path.resolve('./node_modules/react'),
16
+      'react-dom': path.resolve('./node_modules/react-dom')
17
+    }
18
+  }
19
+});

+ 10
- 0
packages/cts-core/scripts/webpack/webpack.config.umd.js View File

@@ -0,0 +1,10 @@
1
+const merge = require('webpack-merge');
2
+const path = require('path');
3
+
4
+const umdConfig = require('../../../../scripts/webpack/webpack.config.umd');
5
+
6
+module.exports = merge(umdConfig, {
7
+  entry: {
8
+    index: path.resolve(__dirname, '../../src/index.ts')
9
+  }
10
+});

+ 335
- 0
packages/cts-core/src/client.ts View File

@@ -0,0 +1,335 @@
1
+import { Packet } from './packet';
2
+import { Utils } from './utils';
3
+import { ReadyStateCallback, RequestCallback } from './types/callback';
4
+
5
+/**
6
+ * 初始化链接以及收发数据
7
+ */
8
+class Client {
9
+  private _maxPayload: number;
10
+  private listeners: Map<number, (data: string) => void>;
11
+  private requestHeader: string;
12
+  private responseHeader: string;
13
+  private url: string;
14
+  private reconnectTimes: number;
15
+  private reconnectLock: boolean;
16
+  private socket: WebSocket;
17
+  private readyStateCallback: ReadyStateCallback;
18
+
19
+  /**
20
+   * 构造函数,初始化客户端链接
21
+   * @param url websocket链接地址
22
+   * @param readyStateCallback 链接状态回调,可以处理onOpen、onClose、onError
23
+   */
24
+  public constructor(url: string, readyStateCallback: ReadyStateCallback) {
25
+    this.listeners = new Map<number, (data: string) => void>();
26
+    this.requestHeader = '';
27
+    this.requestHeader = '';
28
+    this._maxPayload = 1024 * 1024;
29
+    this.url = url;
30
+    this.reconnectTimes = 0;
31
+    this.readyStateCallback = readyStateCallback;
32
+    this.socket = this.connect();
33
+  }
34
+
35
+  /**
36
+   * 发送ping请求,来保持长连接
37
+   * @param param 请求参数,比如{"hello":"world"}
38
+   * @param requestCallback 请求状态回调
39
+   */
40
+  public ping(param: object, requestCallback: RequestCallback): void {
41
+    if (this.socket.readyState !== this.socket.OPEN) {
42
+      throw new Error('asyncSend: connection refuse');
43
+    }
44
+
45
+    const heartbeatOperator = 0;
46
+
47
+    this.listeners.set(
48
+      heartbeatOperator,
49
+      (data: string): void => {
50
+        const code = this.getResponseProperty('code');
51
+        if (code !== '') {
52
+          const message = this.getResponseProperty('message');
53
+          requestCallback.onError(Number(code), message);
54
+        } else {
55
+          requestCallback.onSuccess(data);
56
+        }
57
+
58
+        requestCallback.onEnd();
59
+      },
60
+    );
61
+
62
+    const p = new Packet();
63
+    this.send(
64
+      p.pack(heartbeatOperator, 0, this.requestHeader, JSON.stringify(param)),
65
+    );
66
+  }
67
+
68
+  /**
69
+   * 异步向服务端发送请求
70
+   * @param operator 路由地址
71
+   * @param param 请求参数,比如{"hello":"world"}
72
+   * @param callback 请求状态回调处理
73
+   */
74
+  public asyncSend(
75
+    operator: string,
76
+    param: object,
77
+    callback: RequestCallback,
78
+  ): void {
79
+    console.info('websocket send data', operator, this.requestHeader, param);
80
+
81
+    if (this.socket.readyState !== this.socket.OPEN) {
82
+      throw new Error('asyncSend: connection refuse');
83
+    }
84
+
85
+    callback.onStart();
86
+
87
+    const sequence = new Date().getTime();
88
+    const listener = Utils.crc32(operator) + sequence;
89
+    this.listeners.set(
90
+      listener,
91
+      (data: string): void => {
92
+        const code = this.getResponseProperty('code');
93
+        if (code !== '') {
94
+          const message = this.getResponseProperty('message');
95
+          callback.onError(Number(code), message);
96
+        } else {
97
+          callback.onSuccess(data);
98
+        }
99
+
100
+        callback.onEnd();
101
+
102
+        delete this.listeners[listener];
103
+      },
104
+    );
105
+
106
+    const p = new Packet();
107
+    this.send(
108
+      p.pack(
109
+        Utils.crc32(operator),
110
+        sequence,
111
+        this.requestHeader,
112
+        JSON.stringify(param),
113
+      ),
114
+    );
115
+  }
116
+
117
+  /**
118
+   * 同步方式向服务端发送请求
119
+   * @param operator 路由地址
120
+   * @param param 请求参数,比如{"hello":"world"}
121
+   * @param callback 请求状态回调处理
122
+   */
123
+  public async syncSend(
124
+    operator: string,
125
+    param: object,
126
+    callback: RequestCallback,
127
+  ): Promise<void> {
128
+    await this.asyncSend(operator, param, callback);
129
+  }
130
+
131
+  /**
132
+   * 添加消息监听
133
+   * @description 添加消息监听器,比如operator是/v1/message/listener,那么从服务端推送到/v1/message/listener的消息会进入到定义的listener里面进行处理
134
+   * @param operator 消息监听地址
135
+   * @param listener 定义如何处理从服务端返回的消息
136
+   */
137
+  public addMessageListener(
138
+    operator: string,
139
+    listener: (data: string) => void,
140
+  ): void {
141
+    this.listeners.set(Utils.crc32(operator), listener);
142
+  }
143
+
144
+  /**
145
+   * 移除消息监听
146
+   * @param operator 消息监听地址
147
+   */
148
+  public removeMessageListener(operator: string): void {
149
+    delete this.listeners[Utils.crc32(operator)];
150
+  }
151
+
152
+  /**
153
+   * 返回Websocket链接状态
154
+   * @returns Websocket的链接状态
155
+   */
156
+  public get readyState(): number {
157
+    return this.socket.readyState;
158
+  }
159
+
160
+  /**
161
+   * 设置可以处理的数据包上限
162
+   * @param maxPayload 最多可以处理的数据包大小
163
+   */
164
+  public set maxPayload(maxPayload: number) {
165
+    this._maxPayload = maxPayload;
166
+  }
167
+
168
+  /**
169
+   * 获取可以处理的数据包大小
170
+   */
171
+  public get maxPayload(): number {
172
+    return this._maxPayload;
173
+  }
174
+
175
+  /**
176
+   * 添加请求属性,会携带在数据帧里面发送到服务端
177
+   * @param key 属性名
178
+   * @param value 属性值
179
+   */
180
+  public setRequestProperty(key: string, value: string): void {
181
+    let v = this.getRequestProperty(key);
182
+
183
+    this.requestHeader = this.requestHeader.replace(key + '=' + v + ';', '');
184
+    this.requestHeader = this.requestHeader + key + '=' + value + ';';
185
+  }
186
+
187
+  /**
188
+   * 获取请求属性
189
+   * @param key 属性名
190
+   */
191
+  public getRequestProperty(key: string): string {
192
+    if (this.requestHeader !== undefined) {
193
+      let values = this.requestHeader.split(';');
194
+      for (let index in values) {
195
+        let kv = values[index].split('=');
196
+        if (kv[0] === key) {
197
+          return kv[1];
198
+        }
199
+      }
200
+    }
201
+
202
+    return '';
203
+  }
204
+
205
+  /**
206
+   * 设置响应属性,客户端基本用不到,都是服务端来进行设置
207
+   * @param key 属性名
208
+   * @param value 属性值
209
+   */
210
+  public setResponseProperty(key: string, value: string): void {
211
+    let v = this.getResponseProperty(key);
212
+
213
+    this.responseHeader = this.responseHeader.replace(key + '=' + v + ';', '');
214
+    this.responseHeader = this.responseHeader + key + '=' + value + ';';
215
+  }
216
+
217
+  /**
218
+   * 获取从服务端返回的属性
219
+   * @param key 获取响应属性
220
+   */
221
+  public getResponseProperty(key: string): string {
222
+    if (this.responseHeader !== undefined) {
223
+      let values = this.responseHeader.split(';');
224
+      for (let index in values) {
225
+        let kv = values[index].split('=');
226
+        if (kv[0] === key) {
227
+          return kv[1];
228
+        }
229
+      }
230
+    }
231
+
232
+    return '';
233
+  }
234
+
235
+  /**
236
+   * 创建websocket链接
237
+   */
238
+  private connect(): WebSocket {
239
+    const readyStateCallback = this.readyStateCallback;
240
+    let ws = new WebSocket(this.url);
241
+
242
+    ws.binaryType = 'blob';
243
+
244
+    ws.onopen = (ev): void => {
245
+      this.reconnectTimes = 0;
246
+
247
+      readyStateCallback.onOpen(ev);
248
+    };
249
+
250
+    ws.onclose = (ev): void => {
251
+      this.reconnect();
252
+
253
+      readyStateCallback.onClose(ev);
254
+    };
255
+
256
+    ws.onerror = (ev): void => {
257
+      this.reconnect();
258
+
259
+      readyStateCallback.onError(ev);
260
+    };
261
+
262
+    ws.onmessage = (ev): void => {
263
+      if (ev.data instanceof Blob) {
264
+        let reader = new FileReader();
265
+        reader.readAsArrayBuffer(ev.data);
266
+        reader.onload = (): void => {
267
+          try {
268
+            let packet = new Packet().unPack(reader.result as ArrayBuffer);
269
+            let packetLength = packet.headerLength + packet.bodyLength + 20;
270
+            if (packetLength > this._maxPayload) {
271
+              throw new Error('the packet is big than ' + this._maxPayload);
272
+            }
273
+
274
+            let operator = Number(packet.operator) + Number(packet.sequence);
275
+            if (this.listeners.has(operator)) {
276
+              if (packet.body === '') {
277
+                packet.body = '{}';
278
+              }
279
+
280
+              this.responseHeader = packet.header;
281
+
282
+              (this.listeners.get(operator) as (data: string) => void)(
283
+                JSON.parse(packet.body),
284
+              );
285
+            }
286
+
287
+            if (operator !== 0 && packet.body !== 'null') {
288
+              console.info('receive data', packet.body);
289
+            }
290
+          } catch (e) {
291
+            throw new Error(e);
292
+          }
293
+        };
294
+      } else {
295
+        throw new Error('unsupported data format');
296
+      }
297
+    };
298
+
299
+    return ws;
300
+  }
301
+
302
+  /**
303
+   * 断线重连
304
+   */
305
+  private reconnect(): void {
306
+    if (!this.reconnectLock) {
307
+      this.reconnectLock = true;
308
+      console.info('websocket reconnect in ' + this.reconnectTimes + 's');
309
+      // 尝试重连
310
+      setTimeout((): void => {
311
+        this.reconnectTimes++;
312
+        this.socket = this.connect();
313
+        this.reconnectLock = false;
314
+      }, this.reconnectTimes * 1000);
315
+    }
316
+  }
317
+
318
+  /**
319
+   * 向服务端发送数据请求
320
+   * @param data 向服务端传送的数据
321
+   */
322
+  private send(data: ArrayBuffer): void {
323
+    if (this.socket.readyState !== this.socket.OPEN) {
324
+      console.error('WebSocket is already in CLOSING or CLOSED state.');
325
+      return;
326
+    }
327
+    try {
328
+      this.socket.send(data);
329
+    } catch (e) {
330
+      console.log('send data error', e);
331
+    }
332
+  }
333
+}
334
+
335
+export { Client };

+ 0
- 0
packages/cts-core/src/index.ts View File


+ 68
- 0
packages/cts-core/src/packet.ts View File

@@ -0,0 +1,68 @@
1
+import { Utils } from './utils';
2
+import Int64 from 'node-int64';
3
+
4
+export class Packet {
5
+  private key: string = 'b8ca9aa66def05ff3f24919274bb4a66';
6
+  public operator: number;
7
+  public sequence: number;
8
+  public headerLength: number;
9
+  public bodyLength: number;
10
+  public header: string;
11
+  public body: string;
12
+
13
+  public pack(
14
+    operator: number,
15
+    sequence: number,
16
+    header: string,
17
+    body: string,
18
+  ): ArrayBuffer {
19
+    header = Utils.encrypt(header, this.key, this.key);
20
+    body = Utils.encrypt(body, this.key, this.key);
21
+
22
+    const headerLength = header.length;
23
+    const bodyLength = body.length;
24
+
25
+    const buf = new ArrayBuffer(20 + headerLength + bodyLength);
26
+    const dataView = new DataView(buf);
27
+    const nsBuf = new Int64(sequence).toBuffer();
28
+
29
+    dataView.setUint32(0, operator);
30
+    dataView.setUint32(12, headerLength);
31
+    dataView.setUint32(16, bodyLength);
32
+
33
+    let bufView = new Uint8Array(buf);
34
+    for (var i = 0; i < 8; i++) {
35
+      bufView[4 + i] = nsBuf[i];
36
+    }
37
+    for (let i = 0; i < headerLength; i++) {
38
+      bufView[20 + i] = header.charCodeAt(i);
39
+    }
40
+
41
+    for (let i = 0; i < bodyLength; i++) {
42
+      bufView[20 + headerLength + i] = body.charCodeAt(i);
43
+    }
44
+
45
+    return buf;
46
+  }
47
+
48
+  public unPack(data: ArrayBuffer | SharedArrayBuffer): Packet {
49
+    const dataView = new DataView(data);
50
+
51
+    this.operator = dataView.getUint32(0, false);
52
+    this.sequence = new Int64(
53
+      new Uint8Array(dataView.buffer.slice(4, 12)),
54
+    ).toNumber();
55
+    this.headerLength = dataView.getUint32(12, false);
56
+    this.bodyLength = dataView.getUint32(16, false);
57
+
58
+    const header = Utils.ab2str(
59
+      dataView.buffer.slice(20, 20 + this.headerLength),
60
+    );
61
+    const body = Utils.ab2str(dataView.buffer.slice(20 + this.headerLength));
62
+
63
+    this.header = Utils.decrypt(header, this.key, this.key);
64
+    this.body = Utils.decrypt(body, this.key, this.key);
65
+
66
+    return this;
67
+  }
68
+}

+ 14
- 0
packages/cts-core/src/types/callback.ts View File

@@ -0,0 +1,14 @@
1
+interface ReadyStateCallback {
2
+  onOpen(ev: Event): void;
3
+  onError(ev: Event): void;
4
+  onClose(ev: Event): void;
5
+}
6
+
7
+interface RequestCallback {
8
+  onStart(): void;
9
+  onSuccess(data: string): void;
10
+  onError(code: number, message: string): void;
11
+  onEnd(): void;
12
+}
13
+
14
+export { ReadyStateCallback, RequestCallback };

+ 157
- 0
packages/cts-core/src/utils.ts View File

@@ -0,0 +1,157 @@
1
+import { AES, enc, mode, pad } from 'crypto-js';
2
+
3
+declare global {
4
+  interface Window {
5
+    crcTable: number[];
6
+  }
7
+}
8
+
9
+class Utils {
10
+  private static code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(
11
+    '',
12
+  );
13
+
14
+  public static crc32(str: string): number {
15
+    const crcTable =
16
+      window.crcTable || (window.crcTable = Utils.makeCRCTable());
17
+    let crc = 0 ^ -1;
18
+
19
+    for (let i = 0; i < str.length; i++) {
20
+      crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xff];
21
+    }
22
+
23
+    return (crc ^ -1) >>> 0;
24
+  }
25
+
26
+  // ArrayBuffer 转为字符串,参数为 ArrayBuffer 对象
27
+  public static ab2str(buf: ArrayBuffer): string {
28
+    // 注意,如果是大型二进制数组,为了避免溢出,必须一个一个字符地转
29
+    if (buf && buf.byteLength < 1024) {
30
+      return String.fromCharCode.apply(null, new Uint8Array(buf));
31
+    }
32
+
33
+    const bufView = new Uint8Array(buf);
34
+    const len = bufView.length;
35
+    const byteStr = new Array(len);
36
+
37
+    for (let i = 0; i < len; i++) {
38
+      byteStr[i] = String.fromCharCode.call(null, bufView[i]);
39
+    }
40
+
41
+    return byteStr.join('');
42
+  }
43
+
44
+  // 字符串转为 ArrayBuffer 对象,参数为字符串
45
+  public static str2ab(str: string): ArrayBuffer {
46
+    const buf = new ArrayBuffer(str.length); // 每个字符占用2个字节
47
+    const bufView = new Uint8Array(buf);
48
+
49
+    for (let i = 0, strLen = str.length; i < strLen; i++) {
50
+      bufView[i] = str.charCodeAt(i);
51
+    }
52
+
53
+    return buf;
54
+  }
55
+
56
+  // 解密服务端传递过来的字符串
57
+  public static decrypt(data: string, key: string, iv: string): string {
58
+    const binData = Utils.stringToBin(data);
59
+    const base64Data = Utils.binToBase64(binData);
60
+
61
+    const bytes = AES.decrypt(base64Data, enc.Latin1.parse(key), {
62
+      iv: enc.Latin1.parse(iv),
63
+      mode: mode.CBC,
64
+      padding: pad.Pkcs7,
65
+    });
66
+
67
+    return bytes.toString(enc.Utf8);
68
+  }
69
+
70
+  // 加密字符串以后传递到服务端
71
+  public static encrypt(data: string, key: string, iv: string): string {
72
+    const result = AES.encrypt(data, enc.Latin1.parse(key), {
73
+      iv: enc.Latin1.parse(iv),
74
+      mode: mode.CBC,
75
+      padding: pad.Pkcs7,
76
+    });
77
+
78
+    return Utils.binToString(Utils.base64ToBin(result.toString()));
79
+  }
80
+
81
+  // 字节数组转换为base64编码
82
+  public static binToBase64(bitString: string): string {
83
+    const tail = bitString.length % 6;
84
+    const bitStringTemp1 = bitString.substr(0, bitString.length - tail);
85
+
86
+    let result = '';
87
+    let bitStringTemp2 = bitString.substr(bitString.length - tail, tail);
88
+
89
+    for (let i = 0; i < bitStringTemp1.length; i += 6) {
90
+      let index = parseInt(bitStringTemp1.substr(i, 6), 2);
91
+      result += Utils.code[index];
92
+    }
93
+
94
+    bitStringTemp2 += new Array(7 - tail).join('0');
95
+    if (tail) {
96
+      result += Utils.code[parseInt(bitStringTemp2, 2)];
97
+      result += new Array((6 - tail) / 2 + 1).join('=');
98
+    }
99
+
100
+    return result;
101
+  }
102
+
103
+  // base64编码转换为字节数组
104
+  public static base64ToBin(str: string): string {
105
+    let bitString = '';
106
+    let tail = 0;
107
+
108
+    for (let i = 0; i < str.length; i++) {
109
+      if (str[i] !== '=') {
110
+        let decode = this.code.indexOf(str[i]).toString(2);
111
+        bitString += new Array(7 - decode.length).join('0') + decode;
112
+      } else {
113
+        tail++;
114
+      }
115
+    }
116
+
117
+    return bitString.substr(0, bitString.length - tail * 2);
118
+  }
119
+
120
+  // 字符串转换为字节数组
121
+  public static stringToBin(str: string): string {
122
+    let result = '';
123
+    for (let i = 0; i < str.length; i++) {
124
+      let charCode = str.charCodeAt(i).toString(2);
125
+      result += new Array(9 - charCode.length).join('0') + charCode;
126
+    }
127
+
128
+    return result;
129
+  }
130
+
131
+  // 字节数组转化为字符串
132
+  public static binToString(bin: string): string {
133
+    let result = '';
134
+    for (let i = 0; i < bin.length; i += 8) {
135
+      result += String.fromCharCode(parseInt(bin.substr(i, 8), 2));
136
+    }
137
+
138
+    return result;
139
+  }
140
+
141
+  private static makeCRCTable(): number[] {
142
+    let c: number;
143
+    let crcTable: number[] = [];
144
+
145
+    for (let n = 0; n < 256; n++) {
146
+      c = n;
147
+      for (let k = 0; k < 8; k++) {
148
+        c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
149
+      }
150
+      crcTable[n] = c;
151
+    }
152
+
153
+    return crcTable;
154
+  }
155
+}
156
+
157
+export { Utils };

+ 13
- 0
packages/cts-core/tsconfig.cjs.json View File

@@ -0,0 +1,13 @@
1
+{
2
+  "extends": "./tsconfig.json",
3
+  "compilerOptions": {
4
+    "module": "commonjs",
5
+    "rootDir": "./src",
6
+    "outDir": "dist/cjs",
7
+    "declaration": true,
8
+    "declarationDir": "dist/types",
9
+    "sourceMap": false,
10
+    "paths": {}
11
+  },
12
+  "include": ["src/**/*"]
13
+}

+ 7
- 0
packages/cts-core/tsconfig.json View File

@@ -0,0 +1,7 @@
1
+{
2
+  "extends": "../../tsconfig.json",
3
+  "compilerOptions": {
4
+    "resolveJsonModule": true
5
+  },
6
+  "include": ["src/**/*", "example/**/*"]
7
+}

+ 3
- 0
packages/cts-core/tslint.json View File

@@ -0,0 +1,3 @@
1
+{
2
+  "extends": "../../tslint.json"
3
+}

+ 14
- 0
packages/cts-core/types.d.ts View File

@@ -0,0 +1,14 @@
1
+declare module '*.less' {
2
+  const styles: Record<string, string>;
3
+  export = styles;
4
+}
5
+
6
+declare module '*.css' {
7
+  const content: any;
8
+  export default content;
9
+}
10
+
11
+declare module '*.svg' {
12
+  const content: string;
13
+  export default content;
14
+}

+ 11260
- 0
packages/cts-core/yarn.lock
File diff suppressed because it is too large
View File


+ 5
- 0
postcss.config.js View File

@@ -0,0 +1,5 @@
1
+const autoprefixer = require('autoprefixer');
2
+
3
+module.exports = {
4
+  plugins: [autoprefixer({ browsers: ['last 4 versions'], flexbox: 'no-2009' })]
5
+};

+ 1
- 0
scripts/jest/fileMock.js View File

@@ -0,0 +1 @@
1
+module.exports = 'test-file-stub';

+ 18
- 0
scripts/jest/jest.config.js View File

@@ -0,0 +1,18 @@
1
+module.exports = {
2
+  coverageDirectory: '<rootDir>/@coverage',
3
+  globals: {
4
+    'ts-jest': {
5
+      tsConfig: 'tsconfig.test.json'
6
+    }
7
+  },
8
+  moduleFileExtensions: ['js', 'ts', 'tsx'],
9
+  moduleNameMapper: {
10
+    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
11
+      './fileMock.js',
12
+    '\\.(scss|css|less)$': './styleMock.js'
13
+  },
14
+  rootDir: '../../',
15
+  testRegex: '/__test__/.+\\.(test|spec)\\.tsx?$',
16
+  transform: { '^.+\\.tsx?$': 'ts-jest' },
17
+  verbose: true
18
+};

+ 1
- 0
scripts/jest/styleMock.js View File

@@ -0,0 +1 @@
1
+module.exports = {};

+ 166
- 0
scripts/template/template.ejs View File

@@ -0,0 +1,166 @@
1
+<% var item, key %><%
2
+htmlWebpackPlugin.options.appMountIds = htmlWebpackPlugin.options.appMountIds || [] %><%
3
+htmlWebpackPlugin.options.lang = htmlWebpackPlugin.options.lang || "en" %><%
4
+htmlWebpackPlugin.options.links = htmlWebpackPlugin.options.links || [] %><%
5
+htmlWebpackPlugin.options.meta = htmlWebpackPlugin.options.meta || [] %><%
6
+htmlWebpackPlugin.options.scripts = htmlWebpackPlugin.options.scripts || []
7
+%><!DOCTYPE html>
8
+<html lang="<%= htmlWebpackPlugin.options.lang %>"<% if (htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>>
9
+  <head>
10
+    <meta charset="utf-8">
11
+    <meta content="ie=edge" http-equiv="x-ua-compatible"><%
12
+
13
+    if (htmlWebpackPlugin.options.baseHref) { %>
14
+    <base href="<%= htmlWebpackPlugin.options.baseHref %>"><%
15
+    } %><%
16
+
17
+    if (Array.isArray(htmlWebpackPlugin.options.meta)) { %><%
18
+      for (item of htmlWebpackPlugin.options.meta) { %>
19
+    <meta<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %>><%
20
+      } %><%
21
+    } %><%
22
+
23
+    %>
24
+    <title><%= htmlWebpackPlugin.options.title %></title><%
25
+
26
+    if (htmlWebpackPlugin.files.favicon) { %>
27
+    <link href="<%= htmlWebpackPlugin.files.favicon %>" rel="shortcut icon" /><%
28
+    } %><%
29
+
30
+    if (htmlWebpackPlugin.options.mobile) { %>
31
+    <meta content="width=device-width, initial-scale=1" name="viewport"><%
32
+    } %><%
33
+
34
+    for (item of htmlWebpackPlugin.options.links) { %><%
35
+      if (typeof item === 'string' || item instanceof String) { item = { href: item, rel: 'stylesheet' } } %>
36
+    <link<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %> /><%
37
+    } %><%
38
+
39
+    for (key in htmlWebpackPlugin.files.css) { %><%
40
+      if (htmlWebpackPlugin.files.cssIntegrity) { %>
41
+    <link
42
+      href="<%= htmlWebpackPlugin.files.css[key] %>"
43
+      rel="stylesheet"
44
+      integrity="<%= htmlWebpackPlugin.files.cssIntegrity[key] %>"
45
+      crossorigin="<%= webpackConfig.output.crossOriginLoading %>" /><%
46
+      } else { %>
47
+    <link href="<%= htmlWebpackPlugin.files.css[key] %>" rel="stylesheet" /><%
48
+      } %><%
49
+    } %><%
50
+    if (htmlWebpackPlugin.options.headHtmlSnippet) { %>
51
+    <%= htmlWebpackPlugin.options.headHtmlSnippet %><%
52
+    } %>
53
+    <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
54
+    <script>
55
+      if ('addEventListener' in document) {
56
+        document.addEventListener('DOMContentLoaded', function() {
57
+          FastClick.attach(document.body);
58
+        }, false);
59
+      }
60
+      if(!window.Promise) {
61
+        document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
62
+      }
63
+    </script>
64
+    <style>
65
+    #root,body,html{height:100%}.async_script{display:none}.loader,.loader:after,.loader:before{background:#ed8c3d;-webkit-animation:load1 1s infinite ease-in-out;animation:load1 1s infinite ease-in-out;width:1em;height:4em}.loader{top:35%;color:#fff;text-indent:-9999em;margin:88px auto;position:relative;font-size:11px;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation-delay:-.16s;animation-delay:-.16s}.loader:after,.loader:before{position:absolute;top:0;content:''}.loader:before{left:-1.5em;-webkit-animation-delay:-.32s;animation-delay:-.32s}.loader:after{left:1.5em}@-webkit-keyframes load1{0%,100%,80%{box-shadow:0 0;height:4em}40%{box-shadow:0 -2em;height:5em}}@keyframes load1{0%,100%,80%{box-shadow:0 0;height:4em}40%{box-shadow:0 -2em;height:5em}}
66
+    </style>
67
+  </head>
68
+  <body><%
69
+    if (htmlWebpackPlugin.options.unsupportedBrowser) { %>
70
+    <style>.unsupported-browser { display: none; }</style>
71
+    <div class="unsupported-browser">
72
+      Sorry, your browser is not supported. Please upgrade to the latest version or switch your browser to use this
73
+      site. See <a href="http://outdatedbrowser.com/">outdatedbrowser.com</a> for options.
74
+    </div><%
75
+    } %><%
76
+
77
+    if (htmlWebpackPlugin.options.bodyHtmlSnippet) { %>
78
+    <%= htmlWebpackPlugin.options.bodyHtmlSnippet %><%
79
+    } %><%
80
+
81
+    if (htmlWebpackPlugin.options.appMountId) { %>
82
+    <div id="<%= htmlWebpackPlugin.options.appMountId %>"><%
83
+      if (htmlWebpackPlugin.options.appMountHtmlSnippet) { %>
84
+    <%= htmlWebpackPlugin.options.appMountHtmlSnippet %><%
85
+      } %>
86
+    </div><%
87
+    } %><%
88
+
89
+    for (item of htmlWebpackPlugin.options.appMountIds) { %>
90
+    <div id="<%= item %>"></div><%
91
+    } %><%
92
+
93
+    if (htmlWebpackPlugin.options.window) { %>
94
+    <script type="text/javascript"><%
95
+      for (key in htmlWebpackPlugin.options.window) { %>
96
+      window['<%= key %>'] = <%= JSON.stringify(htmlWebpackPlugin.options.window[key]) %>;<%
97
+      } %>
98
+    </script><%
99
+    } %><%
100
+
101
+    if (htmlWebpackPlugin.options.inlineManifestWebpackName) { %>
102
+    <%= htmlWebpackPlugin.files[htmlWebpackPlugin.options.inlineManifestWebpackName] %><%
103
+    } %><%
104
+
105
+    for (item of htmlWebpackPlugin.options.scripts) { %><%
106
+      if (typeof item === 'string' || item instanceof String) { item = { src: item, type: 'text/javascript' } } %>
107
+    <script<% for (key in item) { %> <%= key %>="<%= item[key] %>"<% } %>></script><%
108
+    } %><%
109
+
110
+    for (key in htmlWebpackPlugin.files.chunks) { %><%
111
+      if (htmlWebpackPlugin.files.jsIntegrity) { %>
112
+    <script
113
+      src="<%= htmlWebpackPlugin.files.chunks[key].entry %>"
114
+      type="text/javascript"
115
+      integrity="<%= htmlWebpackPlugin.files.jsIntegrity[htmlWebpackPlugin.files.js.indexOf(htmlWebpackPlugin.files.chunks[key].entry)] %>"
116
+      crossorigin="<%= webpackConfig.output.crossOriginLoading %>"></script><%
117
+      } else { %>
118
+    <script src="<%= htmlWebpackPlugin.files.chunks[key].entry %>" type="text/javascript"></script><%
119
+      } %><%
120
+    } %>
121
+
122
+    <% if (htmlWebpackPlugin.options.devServer) { %>
123
+      <script src="<%= htmlWebpackPlugin.options.devServer %>/webpack-dev-server.js" type="text/javascript"></script>
124
+    <% } %>
125
+
126
+    <div id="root">
127
+        <div class="loader" />
128
+    </div>
129
+  </body>
130
+  <script>
131
+     function loadScript(src, cb) {
132
+            //创建一个script元素
133
+            var el = document.createElement('script');
134
+            var loaded = false;
135
+            //设置加载完成的回调事件
136
+            el.onload = el.onreadystatechange = function () {
137
+                //防止重复加载
138
+                if ((el.readyState && el.readyState !== 'complete' && el.readyState !== 'loaded') || loaded) {
139
+                    return false;
140
+                }
141
+                //加载完成后将该脚本的onload设置为null
142
+                el.onload = el.onreadystatechange = null;
143
+                loaded = true;
144
+                cb && cb();
145
+            };
146
+            el.async = true;
147
+            el.src = src;
148
+            var body = document.getElementsByTagName('body')[0];
149
+            body.appendChild(el);
150
+        }
151
+        window.onload = function () {
152
+            var scripts = [];
153
+            [].forEach.call(document.getElementsByClassName('async_script'), function (a) {
154
+                scripts.push(a.getAttribute('href'));
155
+            });
156
+            for (var i = 0; i < scripts.length; i++) {
157
+                (function (j) {
158
+                    setTimeout(function () {
159
+                        loadScript(scripts[j]);
160
+                    }, j * 500);
161
+                }(i));
162
+            }
163
+        }
164
+  </script>
165
+  
166
+</html>

+ 52
- 0
scripts/template/template.js View File

@@ -0,0 +1,52 @@
1
+/* eslint-disable no-use-before-define */
2
+// 待渲染完整的页面
3
+// 这里的页面建议以最简形式,如果需要复杂的头设定使用react-helmet组件
4
+
5
+/**
6
+ * @function 生成同构直出的HTML界面模板
7
+ * @param html
8
+ * @param initialState
9
+ * @param scripts
10
+ * @param styles
11
+ * @return {string}
12
+ */
13
+export default (html, initialState = {}, scripts = [], styles = []) => {
14
+  return `
15
+    <!doctype html>
16
+    <html>
17
+      <head>
18
+        <meta charset="utf-8">
19
+        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
20
+        ${styleMapper(styles)}
21
+      </head>
22
+      <body>
23
+        <div id="root">${html}</div>        
24
+      </body>
25
+      <script>
26
+        window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
27
+      </script>
28
+      ${scriptMapper(scripts)}
29
+    </html>
30
+  `;
31
+};
32
+
33
+/**
34
+ * @function 将输入的样式文件路径转化为Link标签
35
+ * @param styles
36
+ * @return {*}
37
+ */
38
+const styleMapper = styles => {
39
+  return styles.map(style => {
40
+    return `<link href="${style}" media="screen, projection" rel="stylesheet" type="text/css" charSet="UTF-8"/>`;
41
+  });
42
+};
43
+
44
+const scriptMapper = scripts => {
45
+  const scriptTags = [];
46
+
47
+  for (let i = 0; i < scripts.length; i = i + 1) {
48
+    scriptTags.push(`<script src=${scripts[i]}></script>`);
49
+  }
50
+
51
+  return scriptTags;
52
+};

+ 0
- 0
scripts/tools/publish_pkgs.sh View File


+ 0
- 0
scripts/tools/release_pkgs.sh View File


+ 8
- 0
scripts/tools/upgrade_pkgs.sh View File

@@ -0,0 +1,8 @@
1
+#!/bin/bash
2
+set -ex
3
+
4
+ncu -u
5
+ 
6
+(cd ./packages/fc-hotkeys && ncu -u)
7
+(cd ./packages/fc-hotkeys-react && ncu -u)
8
+

+ 143
- 0
scripts/webpack/webpack.config.base.js View File

@@ -0,0 +1,143 @@
1
+const path = require('path');
2
+const process = require('process');
3
+const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
4
+const TSConfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
5
+const webpack = require('webpack');
6
+
7
+const rootPath = process.cwd();
8
+const packageName = require(path.resolve(rootPath, 'package.json'));
9
+
10
+const buildEnv = {
11
+  rootPath,
12
+  packageName,
13
+  src: path.resolve(rootPath, './src'),
14
+  public: path.resolve(rootPath, './public'),
15
+  build: path.resolve(rootPath, './build')
16
+};
17
+
18
+const moduleCSSLoader = {
19
+  loader: 'css-loader',
20
+  options: {
21
+    modules: false,
22
+    sourceMap: false,
23
+    importLoaders: 2,
24
+    localIdentName: '[name]__[local]'
25
+  }
26
+};
27
+
28
+const lessLoader = {
29
+  loader: 'less-loader',
30
+  options: {
31
+    modifyVars: {
32
+      'primary-color': '#5d4bff'
33
+    },
34
+    javascriptEnabled: true,
35
+    paths: [path.resolve(rootPath, './node_modules')]
36
+  }
37
+};
38
+
39
+const fontsOptions = {
40
+  limit: 8192,
41
+  mimetype: 'application/font-woff',
42
+  name: 'fonts/[name].[ext]'
43
+};
44
+
45
+module.exports = {
46
+  context: rootPath,
47
+  entry: {
48
+    index: path.resolve(buildEnv.rootPath, './example/index.ts')
49
+  },
50
+  resolve: {
51
+    alias: {
52
+      systemjs: path.resolve(rootPath, './node_modules/systemjs/dist/system-production.js')
53
+    },
54
+    extensions: ['.ts', '.tsx', '.js', '.jsx', '.css', 'less'],
55
+    plugins: [new TSConfigPathsPlugin()]
56
+  },
57
+  output: {
58
+    path: buildEnv.build,
59
+    // 设置所有资源的默认公共路径,Webpack 会自动将 import 的资源改写为该路径
60
+    publicPath: '/',
61
+    filename: '[name].js',
62
+    sourceMapFilename: '[name].map',
63
+    globalObject: 'this' // 避免全局使用 window
64
+  },
65
+  module: {
66
+    rules: [
67
+      {
68
+        test: /.*ts-worker.*/,
69
+        use: ['workerize-loader', 'ts-loader']
70
+      },
71
+      {
72
+        test: /\.(ts|tsx)?$/,
73
+        loader: 'awesome-typescript-loader',
74
+        exclude: /node_modules/,
75
+        options: {
76
+          useBabel: true
77
+        }
78
+      },
79
+      {
80
+        test: /\.(png|jpg|gif)$/,
81
+        use: [
82
+          {
83
+            loader: 'url-loader',
84
+            options: {
85
+              limit: 8192,
86
+              name: 'images/[name].[ext]'
87
+            }
88
+          }
89
+        ]
90
+      },
91
+      {
92
+        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
93
+        use: [
94
+          {
95
+            loader: 'url-loader',
96
+            options: fontsOptions
97
+          }
98
+        ]
99
+      },
100
+      {
101
+        test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
102
+        use: [
103
+          {
104
+            loader: 'file-loader',
105
+            options: fontsOptions
106
+          }
107
+        ]
108
+      },
109
+      {
110
+        test: /\.css$/,
111
+        use: ['style-loader', 'css-loader'],
112
+        include: [/node_modules/, buildEnv.src]
113
+      },
114
+      {
115
+        test: /\.less$/,
116
+        use: ['style-loader', moduleCSSLoader, lessLoader],
117
+        exclude: /node_modules/
118
+      },
119
+      {
120
+        test: /\.less$/,
121
+        use: ['style-loader', 'css-loader', lessLoader],
122
+        include: /node_modules/
123
+      },
124
+      { test: /\.svg$/, loader: 'svg-inline-loader' }
125
+    ]
126
+  },
127
+  plugins: [
128
+    new ForkTsCheckerWebpackPlugin({ checkSyntacticErrors: true, tslint: true }),
129
+    new webpack.WatchIgnorePlugin([/less\.d\.ts$/]),
130
+    new webpack.IgnorePlugin(/\.js\.map$/)
131
+  ],
132
+
133
+  // 定义非直接引用依赖,使用方式即为 var $ = require("jquery")
134
+  externals: {
135
+    window: 'window',
136
+    jquery: '$'
137
+  },
138
+  extra: {
139
+    moduleCSSLoader,
140
+    lessLoader,
141
+    buildEnv
142
+  }
143
+};

+ 59
- 0
scripts/webpack/webpack.config.dev.js View File

@@ -0,0 +1,59 @@
1
+const webpack = require('webpack');
2
+const path = require('path');
3
+const DashboardPlugin = require('webpack-dashboard/plugin');
4
+
5
+const baseConfig = require('./webpack.config.base');
6
+
7
+const config = {
8
+  ...baseConfig,
9
+  mode: 'development',
10
+  devtool: 'source-map',
11
+  plugins: [
12
+    ...baseConfig.plugins,
13
+
14
+    // 在控制台中输出可读的模块名
15
+    new webpack.NamedModulesPlugin(),
16
+
17
+    // 避免发出包含错误的模块
18
+    new webpack.NoEmitOnErrorsPlugin(),
19
+
20
+    // 定义控制变量
21
+    new webpack.DefinePlugin({
22
+      isProd: JSON.stringify(false)
23
+    }),
24
+    new DashboardPlugin()
25
+  ],
26
+  devServer: {
27
+    allowedHosts: ['0.0.0.0:8081'],
28
+    // 设置生成的 Bundle 的前缀路径
29
+    publicPath: '/',
30
+    // assets 中资源文件默认应该还使用 assets
31
+    contentBase: path.resolve(__dirname, '../../public'),
32
+    compress: true,
33
+    headers: {
34
+      'Access-Control-Allow-Origin': '*',
35
+      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
36
+      'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
37
+      'X-Content-Type-Options': 'nosniff',
38
+      'X-Frame-Options': 'DENY'
39
+    },
40
+    open: true,
41
+    overlay: {
42
+      warnings: true,
43
+      errors: true
44
+    },
45
+    host: '0.0.0.0',
46
+    port: 8080,
47
+    hot: false,
48
+    https: false,
49
+    disableHostCheck: true,
50
+    quiet: false
51
+  },
52
+  stats: {
53
+    children: false
54
+  }
55
+};
56
+
57
+delete config.extra;
58
+
59
+module.exports = config;

+ 102
- 0
scripts/webpack/webpack.config.prod.js View File

@@ -0,0 +1,102 @@
1
+const webpack = require('webpack');
2
+const path = require('path');
3
+
4
+const CopyWebpackPlugin = require('copy-webpack-plugin');
5
+const HtmlWebpackPlugin = require('html-webpack-plugin');
6
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
7
+const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
8
+
9
+const baseConfig = require('./webpack.config.base');
10
+
11
+const { buildEnv } = baseConfig.extra;
12
+
13
+const config = {
14
+  ...baseConfig,
15
+  devtool: false,
16
+  mode: 'production',
17
+  output: {
18
+    ...baseConfig.output,
19
+    filename: '[name].js'
20
+  },
21
+  module: {
22
+    rules: [
23
+      ...baseConfig.module.rules.filter(
24
+        rule => !['/\\.css$/', '/\\.less$/', '/\\.(scss|sass)$/'].includes(rule.test.toString())
25
+      ),
26
+      {
27
+        test: /\.css$/,
28
+        use: ['style-loader', 'css-loader', 'postcss-loader']
29
+      },
30
+      {
31
+        test: /\.less$/,
32
+        exclude: /node_modules/,
33
+        use: ['style-loader', 'css-loader', 'postcss-loader', baseConfig.extra.lessLoader]
34
+      },
35
+      {
36
+        test: /\.less$/,
37
+        include: /node_modules/,
38
+        use: ['style-loader', 'css-loader', 'postcss-loader', baseConfig.extra.lessLoader]
39
+      }
40
+    ]
41
+  },
42
+  plugins: [
43
+    ...baseConfig.plugins,
44
+    new webpack.DefinePlugin({
45
+      isProd: JSON.stringify(true)
46
+    }),
47
+
48
+    // 使用 Prepack 优化包体大小
49
+    // 暂时存在 Bug,等待修复
50
+    // 使用前 21 - 425
51
+    // 使用后 21 - 433
52
+    // new PrepackWebpackPlugin({
53
+    //   mathRandomSeed: '0'
54
+    // }),
55
+
56
+    // 必须将 CopyWebpackPlugin 与 HtmlWebpackPlugin 添加到末尾
57
+    new CopyWebpackPlugin([{ from: buildEnv.public, to: buildEnv.build }]),
58
+    new HtmlWebpackPlugin({
59
+      template: path.join(__dirname, '../template/template.ejs'),
60
+      title: 'Webpack React',
61
+      favicon: path.join(baseConfig.extra.buildEnv.public, 'favicon.ico'),
62
+      manifest: path.join(buildEnv.public, 'manifest.json'),
63
+      meta: [
64
+        { name: 'robots', content: 'noindex,nofollow' },
65
+        {
66
+          name: 'viewport',
67
+          content:
68
+            'width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no'
69
+        }
70
+      ],
71
+      appMountIds: ['root'],
72
+      inject: false,
73
+      minify: {
74
+        html5: true,
75
+        useShortDoctype: true,
76
+        collapseWhitespace: true,
77
+        conservativeCollapse: true,
78
+        preserveLineBreaks: true,
79
+        removeComments: true,
80
+        keepClosingSlash: true,
81
+        removeRedundantAttributes: true,
82
+        removeEmptyAttributes: true,
83
+        removeStyleLinkTypeAttributes: true
84
+      },
85
+      mobile: true,
86
+      scripts: ['./static.js']
87
+    })
88
+  ],
89
+  optimization: {
90
+    runtimeChunk: false,
91
+    minimizer: [
92
+      new UglifyJsPlugin({
93
+        exclude: /.*ts-worker.*/
94
+      }),
95
+      new OptimizeCSSAssetsPlugin({})
96
+    ]
97
+  }
98
+};
99
+
100
+delete config.extra;
101
+
102
+module.exports = config;

+ 45
- 0
scripts/webpack/webpack.config.umd.js View File

@@ -0,0 +1,45 @@
1
+const path = require('path');
2
+
3
+const prodConfig = require('./webpack.config.prod');
4
+const rootPath = process.cwd();
5
+
6
+const plugins = [...prodConfig.plugins];
7
+
8
+// 移除 CopyWebpackPlugin 与 HtmlWebpackPlugin
9
+plugins.pop();
10
+plugins.pop();
11
+
12
+const umdConfig = {
13
+  ...prodConfig,
14
+  output: {
15
+    filename: '[name].js',
16
+    path: path.resolve(rootPath, './dist'),
17
+    // 默认不允许挂载在全局变量下
18
+    // library: library,
19
+    libraryTarget: 'umd'
20
+  },
21
+  externals: {
22
+    // Don't bundle react or react-dom
23
+    react: {
24
+      commonjs: 'react',
25
+      commonjs2: 'react',
26
+      amd: 'React',
27
+      root: 'React'
28
+    },
29
+    'react-dom': {
30
+      commonjs: 'react-dom',
31
+      commonjs2: 'react-dom',
32
+      amd: 'ReactDOM',
33
+      root: 'ReactDOM'
34
+    },
35
+    'styled-components': {
36
+      commonjs: 'styled-components',
37
+      commonjs2: 'styled-components'
38
+    }
39
+  },
40
+  plugins
41
+};
42
+
43
+delete umdConfig.optimization;
44
+
45
+module.exports = umdConfig;

+ 39
- 0
tsconfig.json View File

@@ -0,0 +1,39 @@
1
+{
2
+  "compilerOptions": {
3
+    "baseUrl": "packages",
4
+    "lib": ["es6", "es7", "dom", "scripthost", "webworker"],
5
+    "jsx": "react",
6
+    "target": "es5",
7
+    "module": "commonjs",
8
+    "moduleResolution": "node",
9
+    "sourceMap": true,
10
+    "allowJs": false,
11
+    "outDir": "dist",
12
+    "experimentalDecorators": true,
13
+    "forceConsistentCasingInFileNames": true,
14
+    "removeComments": true,
15
+    "noImplicitReturns": true,
16
+    "noImplicitThis": true,
17
+    "noImplicitAny": true,
18
+    "allowUnusedLabels": true,
19
+    "strictNullChecks": true,
20
+    "suppressImplicitAnyIndexErrors": true,
21
+    "noUnusedLocals": true,
22
+    "allowSyntheticDefaultImports": true,
23
+    "skipLibCheck": true,
24
+    "paths": {}
25
+  },
26
+  "include": ["**/*.js", "**/*.ts", "**/*.tsx"],
27
+  "exclude": [
28
+    "assets",
29
+    "build",
30
+    "dist",
31
+    "indep-pkgs",
32
+    "node_modules",
33
+    "scripts",
34
+    "ssr",
35
+    "stories",
36
+    "__test__",
37
+    "test"
38
+  ]
39
+}

+ 6
- 0
tsconfig.test.json View File

@@ -0,0 +1,6 @@
1
+{
2
+  "extends": "./tsconfig.json",
3
+  "compilerOptions": {
4
+    "module": "commonjs"
5
+  }
6
+}

+ 43
- 0
tslint.json View File

@@ -0,0 +1,43 @@
1
+{
2
+  "extends": ["tslint-react", "tslint-config-prettier"],
3
+  "defaultSeverity": "warning",
4
+  "rules": {
5
+    "ban": false,
6
+    "class-name": true,
7
+    "comment-format": [true, "check-space"],
8
+    "curly": true,
9
+    "eofline": false,
10
+    "forin": true,
11
+    "jsdoc-format": true,
12
+    "jsx-no-lambda": false,
13
+    "jsx-no-multiline-js": false,
14
+    "label-position": true,
15
+    "no-any": true,
16
+    "no-arg": true,
17
+    "no-bitwise": true,
18
+    "no-console": [true, "log", "error", "debug", "info", "time", "timeEnd", "trace"],
19
+    "no-construct": true,
20
+    "no-debugger": true,
21
+    "no-duplicate-variable": true,
22
+    "no-empty": true,
23
+    "no-eval": true,
24
+    "no-shadowed-variable": true,
25
+    "no-string-literal": true,
26
+    "no-switch-case-fall-through": true,
27
+    "no-trailing-whitespace": false,
28
+    "no-unused-expression": true,
29
+    "no-use-before-declare": true,
30
+    "radix": true,
31
+    "switch-default": true,
32
+    "trailing-comma": [false],
33
+    "triple-equals": [true, "allow-null-check"],
34
+    "typedef": [true, "parameter", "property-declaration"],
35
+    "variable-name": [
36
+      true,
37
+      "ban-keywords",
38
+      "check-format",
39
+      "allow-leading-underscore",
40
+      "allow-pascal-case"
41
+    ]
42
+  }
43
+}

+ 10833
- 0
yarn.lock
File diff suppressed because it is too large
View File