├── .babelrc
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── LICENSE
├── README.md
├── attachment
├── qq_qrcode_universe_push.jpg
├── vue-chat-pic.png
├── vue-chat-rtc-audio.png
├── vue-chat-rtc-video.png
├── vue-chat-unread.png
├── vue-chat-video.png
├── vue-chat.png
└── wechat-full-screen-mode.png
├── build
├── build.js
├── check-versions.js
├── dev-client.js
├── dev-server.js
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
└── prod.env.js
├── favicon.ico
├── index.html
├── index.md
├── package-lock.json
├── package.json
├── src
├── App.vue
├── assets
│ ├── fonts
│ │ ├── iconfont.css
│ │ ├── iconfont.eot
│ │ ├── iconfont.js
│ │ ├── iconfont.json
│ │ ├── iconfont.svg
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ └── iconfont.woff2
│ └── img
│ │ ├── loading.gif
│ │ ├── logo.png
│ │ └── qrcode.png
├── components
│ ├── chatlist
│ │ └── chatlist.vue
│ ├── friendlist
│ │ └── friendlist.vue
│ ├── info
│ │ ├── info.vue
│ │ ├── man.png
│ │ └── woman.png
│ ├── menu
│ │ ├── addtip.vue
│ │ ├── groupInfo.vue
│ │ ├── personalCard.vue
│ │ ├── relayMessage.vue
│ │ └── rightMenu.vue
│ ├── message
│ │ └── message.vue
│ ├── mycard
│ │ └── mycard.vue
│ ├── search
│ │ ├── delete.png
│ │ └── search.vue
│ └── text
│ │ └── text.vue
├── constant
│ └── index.js
├── main.js
├── page
│ ├── chat
│ │ └── chat.vue
│ ├── friend
│ │ ├── friend.vue
│ │ └── searchfriend.vue
│ ├── group
│ │ ├── creategroup.vue
│ │ ├── delete.png
│ │ └── groupVideoCall.vue
│ ├── login
│ │ └── login.vue
│ └── main.vue
├── permission.js
├── router
│ └── index.js
├── store.js
├── webrtc
│ ├── callEndReason.js
│ ├── callSession.js
│ ├── callState.js
│ ├── engineCallback.js
│ ├── groupCallClient.js
│ ├── message
│ │ ├── callAnswerMessageContent.js
│ │ ├── callAnswerTMessageContent.js
│ │ ├── callByeMessageContent.js
│ │ ├── callModifyMessageContent.js
│ │ ├── callSignalMessageContent.js
│ │ └── callStartMessageContent.js
│ ├── participant.js
│ ├── sessionCallback.js
│ └── voipclient.js
└── websocket
│ ├── chatManager.js
│ ├── future
│ ├── futureResult.js
│ └── promiseResolve.js
│ ├── handler
│ ├── abstractmessagehandler.js
│ ├── addGroupMemberHandler.js
│ ├── connectackhandler.js
│ ├── createGroupHandler.js
│ ├── dismissGroupHandler.js
│ ├── friendAddRequestHandler.js
│ ├── friendRequestHandler.js
│ ├── getGroupInfoHandler.js
│ ├── getGroupMemberHandler.js
│ ├── getMinioUploadUrlHandler.js
│ ├── getUploadtokenHandler.js
│ ├── getfriendresultHandler.js
│ ├── getuserinfoHandler.js
│ ├── handleFriendRequestHandler.js
│ ├── kickGroupmemberHandler.js
│ ├── loadRemoteMessageHander.js
│ ├── messageHandler.js
│ ├── modifyMyInfoHandler.js
│ ├── notifyFriendHandler.js
│ ├── notifyFriendRequestHandler.js
│ ├── notifyMessageHandler.js
│ ├── notifyRecallMessageHandler.js
│ ├── quitGroupHandler.js
│ ├── recallMessageHandler.js
│ ├── receiveMessageHandler.js
│ ├── searchUserResultHandler.js
│ ├── sendMessageHandler.js
│ └── setFriendAliasRequestHandler.js
│ ├── index.js
│ ├── listener
│ └── onReceiverMessageListener.js
│ ├── message
│ ├── fileMessageContent.js
│ ├── imageMessageContent.js
│ ├── mediaMessageContent.js
│ ├── message.js
│ ├── messageConfig.js
│ ├── messageContent.js
│ ├── messageContentMediaType.js
│ ├── messageContentType.js
│ ├── messagePayload.js
│ ├── messageStatus.js
│ ├── modifyGroupInfoType.js
│ ├── myInfoType.js
│ ├── notification
│ │ ├── addGroupMemberNotification.js
│ │ ├── changeGroupNameNotification.js
│ │ ├── createGroupNotification.js
│ │ ├── dismissGroupNotification.js
│ │ ├── groupNotification.js
│ │ ├── kickoffGroupMemberNotification.js
│ │ ├── notificationMessageContent.js
│ │ ├── quitGroupNotification.js
│ │ └── recallMessageNotification.js
│ ├── persistFlag.js
│ ├── protomessage.js
│ ├── protomessageContent.js
│ ├── sendMessage.js
│ ├── textMessageContent.js
│ ├── unknownMessageContent.js
│ ├── unsupportMessageContent.js
│ ├── videoMessageContent.js
│ └── websocketprotomessage.js
│ ├── model
│ ├── conversation.js
│ ├── conversationInfo.js
│ ├── conversationType.js
│ ├── groupInfo.js
│ ├── groupMember.js
│ ├── groupMemberType.js
│ ├── groupType.js
│ ├── protoConversationInfo.js
│ ├── stateConversationInfo.js
│ ├── stateSelectChatMessage.js
│ ├── unReadCount.js
│ └── userInfo.js
│ ├── store
│ └── localstore.js
│ ├── utils
│ ├── StringUtil.js
│ ├── aes.js
│ ├── logger.js
│ └── timeUtils.js
│ └── websocketcli.js
└── static
├── .gitkeep
├── audio
├── incoming_call_ring.mp3
├── notify.mp3
└── outgoing_call_ring.mp3
├── css
└── reset.css
├── emoji
├── 100.gif
├── 101.gif
├── 102.gif
├── 103.gif
├── 104.gif
├── 105.gif
├── 106.gif
├── 107.gif
├── 108.gif
├── 109.gif
├── 110.gif
├── 111.gif
├── 112.gif
├── 113.gif
├── 114.gif
├── 115.gif
├── 116.gif
├── 117.gif
├── 118.gif
├── 119.gif
├── 120.gif
├── 121.gif
├── 122.gif
├── 123.gif
├── 124.gif
├── 125.gif
├── 126.gif
├── 127.gif
├── 128.gif
├── 129.gif
├── 130.gif
├── 131.gif
├── 132.gif
├── 133.gif
├── 134.gif
├── 135.gif
├── 136.gif
├── 137.gif
├── 138.gif
├── 139.gif
├── 140.gif
├── 141.gif
├── 142.gif
├── 143.gif
├── 144.gif
├── 145.gif
├── 146.gif
├── 147.gif
├── 148.gif
├── 149.gif
├── 150.gif
├── 151.gif
├── 152.gif
├── 153.gif
├── 154.gif
├── 155.gif
├── 156.gif
├── 157.gif
├── 158.gif
├── 159.gif
├── 160.gif
├── 161.gif
├── 162.gif
├── 163.gif
├── 164.gif
├── 165.gif
├── 166.gif
├── 167.gif
├── 168.gif
├── 169.gif
├── 170.gif
├── 171.gif
├── 172.gif
├── 173.gif
├── 174.gif
├── 175.gif
├── 176.gif
├── 177.gif
├── 178.gif
├── 179.gif
├── 180.gif
├── 181.gif
├── 182.gif
├── 183.gif
├── 184.gif
├── 185.gif
├── 186.gif
├── 187.gif
├── 188.gif
├── 189.gif
├── 190.gif
├── 191.gif
├── 192.gif
├── 193.gif
├── 194.gif
├── 195.gif
├── 196.gif
├── 197.gif
├── 198.gif
├── 199.gif
├── meinv.png
├── shangxin.png
└── weixiao.png
└── images
├── Guai.jpg
├── UserAvatar.jpg
├── father.jpg
├── icon__attachment-white.png
├── icon__download.png
├── microzz.jpg
├── mother.jpg
├── newfriend.jpg
├── orange.jpg
├── vue.jpg
├── 加菲猫.jpg
├── 大飞哥.jpg
├── 小姨妈.jpg
├── 悟空.jpg
├── 新之助.jpg
└── 萌萌俊.jpg
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", { "modules": false }],
4 | "stage-2"
5 | ],
6 | "plugins": ["transform-runtime"],
7 | "comments": false,
8 | "env": {
9 | "test": {
10 | "presets": ["env", "stage-2"],
11 | "plugins": [ "istanbul" ]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | // to edit target browsers: use "browserlist" field in package.json
6 | "autoprefixer": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](https://gitee.com/comsince/vue-chat)
3 | [](https://github.com/comsince/vue-chat)
4 |
5 | # 飞享
6 |
7 | 
8 |
9 | **NOTE:** [飞享]IM系统开始进行商业化探索,欢迎有需要的`个人`,`企业`, `工作室`使用,关于授权合作事项,请咨询QQ `1282212195`
10 |
11 | 该项目是`飞享`聊天系统客户端源码vue即时通讯web端实现,使用websocket进行消息通讯,支持文本,图片类型发送,支持实时音视频,支持音视频与[android-chat](https://github.com/fsharechat/android-chat)客户端互通
12 |
13 | # 项目截图
14 |
15 | * 消息提示
16 |
17 | 
18 |
19 | * 文字消息
20 |
21 | 
22 |
23 | * 图片消息
24 |
25 | 
26 |
27 | * 视频消息
28 |
29 | 
30 |
31 | # 项目演示
32 |
33 | * [项目公测地址](https://chat.comsince.cn)
34 | * 请选择其中任何一个帐号密码进行登录即可
35 |
36 | ```properties
37 | 帐号:13800000000, 13800000001, 13800000002
38 | 密码:556677
39 | ```
40 | * 暂时停止手机验证码注册登录,后续开通QQ群里面通知
41 |
42 | ## 版本规划
43 | ### V1.0.0
44 | * 登录认证流程
45 | * 实现朋友列表展示,用户信息获取
46 | * 会话信息拉取,会话消息缓存
47 | * 纯文本消息通讯
48 | * 支持图片,视频消息展示
49 | * 群会话功能
50 |
51 | ### V1.0.1
52 | * 增加全屏幕模式支持,点击用户头像即可切换
53 |
54 | 
55 |
56 | ### V1.0.2
57 | * 计划增加一对一音视频聊天功能
58 | * 实现与[android](https://github.com/fsharechat/android-chat)客户端音视频互通
59 |
60 | ### V1.0.3
61 | * 增加好友搜索,好友添加功能,形成功能闭环
62 |
63 | ### V1.0.4
64 | * 群组用户列表功能
65 |
66 | ### V1.0.5
67 | * 增加websocket异步回调接口
68 | * 增加创建群组功能
69 | * 退出群聊
70 | * 撤回消息
71 | * 群组踢人与拉人
72 | * 修改群名称
73 |
74 | 
75 |
76 | ### V1.0.6
77 | * 增加解散群组的功能
78 | * 优化群组退出与解散交互体验
79 | * 对于解散的群组与退出的群组,做删除会话处理
80 |
81 | ### V1.0.7
82 | * 增加删除消息的功能
83 | * 增加转发消息
84 |
85 | ### V1.0.8
86 | * 支持缩略图传输,防止android 客户端转发图片报错
87 |
88 | ### V1.0.9
89 | * 支持缩略图显示
90 |
91 | ### V1.0.14
92 | * 修复群组管理员撤回其他成员发送消息的问题
93 |
94 | ### V1.1.0
95 | * 加入群组音视频功能
96 |
97 | ### V1.1.3
98 | * 增加文件发送功能
99 | * 增加通知短音提示
100 | * 增加音视频通话铃声提示
101 | * 增加截图粘贴发送功能
102 |
103 | ### V1.1.4
104 | * 限制每条会话的消息条数,发送消息时才会删除过多的消息,接收消息时有可能会删除历史未读消息,所以接收时暂不删除过多的消息
105 |
106 |
107 | ## Build Setup
108 |
109 | ``` bash
110 | # install dependencies
111 | npm install
112 |
113 | # serve with hot reload at localhost:9080
114 | npm run dev
115 | # 运行请先检查如下配置:TCP服务配置,HTTPS配置,是否支持WSS,是否支持HTTPS,HTTP监听端口8081,HTTPS监听端口8443
116 |
117 | # build for production with minification
118 | npm run build
119 |
120 | # build for production and view the bundle analyzer report
121 | npm run build --report
122 | ```
123 |
124 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
125 |
126 |
127 |
128 | ## 参考项目
129 |
130 | * [Vue-chat](https://github.com/han960619/Vue-chat/)
131 |
132 | ## 依赖组件
133 | * [常用的 vue 视频插件](https://wangchaoke.cn/?p=372)
134 | * [西瓜播放器](http://h5player.bytedance.com/gettingStarted)
135 | * [图标Icon支持](https://www.iconfont.cn/manage/index?spm=a313x.7781069.1998910419.11&manage_type=myprojects&projectId=1698562)
136 |
137 | ## 推荐项目
138 |
139 | * [vue-wechat](https://github.com/zhaohaodang/vue-WeChat)
140 | * [vue-chat](https://github.com/aermin/vue-chat)
141 | * [QRCodeLogin](https://github.com/HeyJC/QRCodeLogin/blob/master/Web/auth/src/components/Input.vue) 说明二维码和密码登录的切换操作
142 |
143 |
144 | ## 开源协议
145 |
146 | 本项目使用非商业性署名协议,禁止演绎[Creative Commons Attribution Non Commercial 3.0 Unported](LICENSE)
147 |
148 | ## 一次性赞助
149 |
150 | 但是随着项目的增长,也需要相应的资金支持,你可以通过以下方式来赞助此项目
151 |
152 | | 支付宝 | 微信|
153 | | :--------: | :--------:|
154 | |
|
|
155 |
156 | ## QQ 群交流
157 |
158 | | QQ群 |
159 | | :--------: |
160 | |
|
161 |
162 | ## 技术支持
163 |
164 | 如果公司采用本项目或者需要有商业需求,需要二次开发,提供技术支持,联系QQ:`1282212195`
--------------------------------------------------------------------------------
/attachment/qq_qrcode_universe_push.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/attachment/qq_qrcode_universe_push.jpg
--------------------------------------------------------------------------------
/attachment/vue-chat-pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/attachment/vue-chat-pic.png
--------------------------------------------------------------------------------
/attachment/vue-chat-rtc-audio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/attachment/vue-chat-rtc-audio.png
--------------------------------------------------------------------------------
/attachment/vue-chat-rtc-video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/attachment/vue-chat-rtc-video.png
--------------------------------------------------------------------------------
/attachment/vue-chat-unread.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/attachment/vue-chat-unread.png
--------------------------------------------------------------------------------
/attachment/vue-chat-video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/attachment/vue-chat-video.png
--------------------------------------------------------------------------------
/attachment/vue-chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/attachment/vue-chat.png
--------------------------------------------------------------------------------
/attachment/wechat-full-screen-mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/attachment/wechat-full-screen-mode.png
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | var ora = require('ora')
6 | var rm = require('rimraf')
7 | var path = require('path')
8 | var chalk = require('chalk')
9 | var webpack = require('webpack')
10 | var config = require('../config')
11 | var webpackConfig = require('./webpack.prod.conf')
12 |
13 | var spinner = ora('building for production...')
14 | spinner.start()
15 |
16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
17 | if (err) throw err
18 | webpack(webpackConfig, function (err, stats) {
19 | spinner.stop()
20 | if (err) throw err
21 | process.stdout.write(stats.toString({
22 | colors: true,
23 | modules: false,
24 | children: false,
25 | chunks: false,
26 | chunkModules: false
27 | }) + '\n\n')
28 |
29 | console.log(chalk.cyan(' Build complete.\n'))
30 | console.log(chalk.yellow(
31 | ' Tip: built files are meant to be served over an HTTP server.\n' +
32 | ' Opening index.html over file:// won\'t work.\n'
33 | ))
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 | var shell = require('shelljs')
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node
14 | },
15 | ]
16 |
17 | if (shell.which('npm')) {
18 | versionRequirements.push({
19 | name: 'npm',
20 | currentVersion: exec('npm --version'),
21 | versionRequirement: packageConfig.engines.npm
22 | })
23 | }
24 |
25 | module.exports = function () {
26 | var warnings = []
27 | for (var i = 0; i < versionRequirements.length; i++) {
28 | var mod = versionRequirements[i]
29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
30 | warnings.push(mod.name + ': ' +
31 | chalk.red(mod.currentVersion) + ' should be ' +
32 | chalk.green(mod.versionRequirement)
33 | )
34 | }
35 | }
36 |
37 | if (warnings.length) {
38 | console.log('')
39 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
40 | console.log()
41 | for (var i = 0; i < warnings.length; i++) {
42 | var warning = warnings[i]
43 | console.log(' ' + warning)
44 | }
45 | console.log()
46 | process.exit(1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require('eventsource-polyfill')
3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4 |
5 | hotClient.subscribe(function (event) {
6 | if (event.action === 'reload') {
7 | window.location.reload()
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/build/dev-server.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | var config = require('../config')
4 | if (!process.env.NODE_ENV) {
5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
6 | }
7 |
8 | var opn = require('opn')
9 | var path = require('path')
10 | var express = require('express')
11 | var webpack = require('webpack')
12 | var proxyMiddleware = require('http-proxy-middleware')
13 | var webpackConfig = require('./webpack.dev.conf')
14 |
15 | // default port where dev server listens for incoming traffic
16 | var port = process.env.PORT || config.dev.port
17 | // automatically open browser, if not set will be false
18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser
19 | // Define HTTP proxies to your custom API backend
20 | // https://github.com/chimurai/http-proxy-middleware
21 | var proxyTable = config.dev.proxyTable
22 |
23 | var app = express()
24 | var compiler = webpack(webpackConfig)
25 |
26 | var devMiddleware = require('webpack-dev-middleware')(compiler, {
27 | publicPath: webpackConfig.output.publicPath,
28 | quiet: true
29 | })
30 |
31 | var hotMiddleware = require('webpack-hot-middleware')(compiler, {
32 | log: () => {}
33 | })
34 | // force page reload when html-webpack-plugin template changes
35 | compiler.plugin('compilation', function (compilation) {
36 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
37 | hotMiddleware.publish({ action: 'reload' })
38 | cb()
39 | })
40 | })
41 |
42 | // proxy api requests
43 | Object.keys(proxyTable).forEach(function (context) {
44 | var options = proxyTable[context]
45 | if (typeof options === 'string') {
46 | options = { target: options }
47 | }
48 | app.use(proxyMiddleware(options.filter || context, options))
49 | })
50 |
51 | // handle fallback for HTML5 history API
52 | app.use(require('connect-history-api-fallback')())
53 |
54 | // serve webpack bundle output
55 | app.use(devMiddleware)
56 |
57 | // enable hot-reload and state-preserving
58 | // compilation error display
59 | app.use(hotMiddleware)
60 |
61 | // serve pure static assets
62 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
63 | app.use(staticPath, express.static('./static'))
64 |
65 | var uri = 'http://localhost:' + port
66 |
67 | var _resolve
68 | var readyPromise = new Promise(resolve => {
69 | _resolve = resolve
70 | })
71 |
72 | console.log('> Starting dev server...')
73 | devMiddleware.waitUntilValid(() => {
74 | console.log('> Listening at ' + uri + '\n')
75 | // when env is testing, don't need open it
76 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
77 | opn(uri)
78 | }
79 | _resolve()
80 | })
81 |
82 | var server = app.listen(port)
83 |
84 | module.exports = {
85 | ready: readyPromise,
86 | close: () => {
87 | server.close()
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
4 |
5 | exports.assetsPath = function (_path) {
6 | var assetsSubDirectory = process.env.NODE_ENV === 'production'
7 | ? config.build.assetsSubDirectory
8 | : config.dev.assetsSubDirectory
9 | return path.posix.join(assetsSubDirectory, _path)
10 | }
11 |
12 | exports.cssLoaders = function (options) {
13 | options = options || {}
14 |
15 | var cssLoader = {
16 | loader: 'css-loader',
17 | options: {
18 | minimize: process.env.NODE_ENV === 'production',
19 | sourceMap: options.sourceMap
20 | }
21 | }
22 |
23 | // generate loader string to be used with extract text plugin
24 | function generateLoaders (loader, loaderOptions) {
25 | var loaders = [cssLoader]
26 | if (loader) {
27 | loaders.push({
28 | loader: loader + '-loader',
29 | options: Object.assign({}, loaderOptions, {
30 | sourceMap: options.sourceMap
31 | })
32 | })
33 | }
34 |
35 | // Extract CSS when that option is specified
36 | // (which is the case during production build)
37 | if (options.extract) {
38 | return ExtractTextPlugin.extract({
39 | use: loaders,
40 | fallback: 'vue-style-loader',
41 | publicPath: '../../'
42 | })
43 | } else {
44 | return ['vue-style-loader'].concat(loaders)
45 | }
46 | }
47 |
48 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
49 | return {
50 | css: generateLoaders(),
51 | postcss: generateLoaders(),
52 | less: generateLoaders('less'),
53 | sass: generateLoaders('sass', { indentedSyntax: true }),
54 | scss: generateLoaders('sass'),
55 | stylus: generateLoaders('stylus'),
56 | styl: generateLoaders('stylus')
57 | }
58 | }
59 |
60 | // Generate loaders for standalone style files (outside of .vue)
61 | exports.styleLoaders = function (options) {
62 | var output = []
63 | var loaders = exports.cssLoaders(options)
64 | for (var extension in loaders) {
65 | var loader = loaders[extension]
66 | output.push({
67 | test: new RegExp('\\.' + extension + '$'),
68 | use: loader
69 | })
70 | }
71 | return output
72 | }
73 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var config = require('../config')
3 | var isProduction = process.env.NODE_ENV === 'production'
4 |
5 | module.exports = {
6 | loaders: utils.cssLoaders({
7 | sourceMap: isProduction
8 | ? config.build.productionSourceMap
9 | : config.dev.cssSourceMap,
10 | extract: isProduction
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var config = require('../config')
4 | var vueLoaderConfig = require('./vue-loader.conf')
5 |
6 | function resolve (dir) {
7 | return path.join(__dirname, '..', dir)
8 | }
9 |
10 | module.exports = {
11 | entry: {
12 | app: './src/main.js'
13 | },
14 | output: {
15 | path: config.build.assetsRoot,
16 | filename: '[name].js',
17 | publicPath: process.env.NODE_ENV === 'production'
18 | ? config.build.assetsPublicPath
19 | : config.dev.assetsPublicPath
20 | },
21 | resolve: {
22 | extensions: ['.js', '.vue', '.json'],
23 | alias: {
24 | 'vue$': 'vue/dist/vue.esm.js',
25 | '@': resolve('src')
26 | }
27 | },
28 | module: {
29 | rules: [
30 | {
31 | test: /\.vue$/,
32 | loader: 'vue-loader',
33 | options: vueLoaderConfig
34 | },
35 | {
36 | test: /\.js$/,
37 | loader: 'babel-loader',
38 | include: [resolve('src'), resolve('test'), resolve('/node_modules/webrtc-adapter/src')]
39 | },
40 | {
41 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
42 | loader: 'url-loader',
43 | options: {
44 | limit: 10000,
45 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
46 | }
47 | },
48 | {
49 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
50 | loader: 'url-loader',
51 | options: {
52 | limit: 10000,
53 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
54 | }
55 | }
56 | ]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var webpack = require('webpack')
3 | var config = require('../config')
4 | var merge = require('webpack-merge')
5 | var baseWebpackConfig = require('./webpack.base.conf')
6 | var HtmlWebpackPlugin = require('html-webpack-plugin')
7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
8 | var path = require('path');
9 |
10 | // add hot-reload related code to entry chunks
11 | Object.keys(baseWebpackConfig.entry).forEach(function (name) {
12 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
13 | })
14 |
15 | module.exports = merge(baseWebpackConfig, {
16 | module: {
17 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
18 | },
19 | // cheap-module-eval-source-map is faster for development
20 | devtool: '#cheap-module-eval-source-map',
21 | plugins: [
22 | new webpack.DefinePlugin({
23 | 'process.env': config.dev.env
24 | }),
25 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
26 | new webpack.HotModuleReplacementPlugin(),
27 | new webpack.NoEmitOnErrorsPlugin(),
28 | // https://github.com/ampedandwired/html-webpack-plugin
29 | new HtmlWebpackPlugin({
30 | filename: 'index.html',
31 | template: 'index.html',
32 | inject: true,
33 | favicon: path.resolve('favicon.ico'),
34 | }),
35 | new FriendlyErrorsPlugin()
36 | ]
37 | })
38 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var webpack = require('webpack')
4 | var config = require('../config')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var CopyWebpackPlugin = require('copy-webpack-plugin')
8 | var HtmlWebpackPlugin = require('html-webpack-plugin')
9 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
11 |
12 | var env = config.build.env
13 |
14 | var webpackConfig = merge(baseWebpackConfig, {
15 | module: {
16 | rules: utils.styleLoaders({
17 | sourceMap: config.build.productionSourceMap,
18 | extract: true
19 | })
20 | },
21 | devtool: config.build.productionSourceMap ? '#source-map' : false,
22 | output: {
23 | path: config.build.assetsRoot,
24 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
25 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
26 | },
27 | plugins: [
28 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
29 | new webpack.DefinePlugin({
30 | 'process.env': env
31 | }),
32 | new webpack.optimize.UglifyJsPlugin({
33 | compress: {
34 | warnings: false
35 | },
36 | sourceMap: true
37 | }),
38 | // extract css into its own file
39 | new ExtractTextPlugin({
40 | filename: utils.assetsPath('css/[name].[contenthash].css')
41 | }),
42 | // Compress extracted CSS. We are using this plugin so that possible
43 | // duplicated CSS from different components can be deduped.
44 | new OptimizeCSSPlugin({
45 | cssProcessorOptions: {
46 | safe: true
47 | }
48 | }),
49 | // generate dist index.html with correct asset hash for caching.
50 | // you can customize output by editing /index.html
51 | // see https://github.com/ampedandwired/html-webpack-plugin
52 | new HtmlWebpackPlugin({
53 | filename: config.build.index,
54 | template: 'index.html',
55 | inject: true,
56 | favicon: path.resolve('favicon.ico'),
57 | minify: {
58 | removeComments: true,
59 | collapseWhitespace: true,
60 | removeAttributeQuotes: true
61 | // more options:
62 | // https://github.com/kangax/html-minifier#options-quick-reference
63 | },
64 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
65 | chunksSortMode: 'dependency'
66 | }),
67 | // split vendor js into its own file
68 | new webpack.optimize.CommonsChunkPlugin({
69 | name: 'vendor',
70 | minChunks: function (module, count) {
71 | // any required modules inside node_modules are extracted to vendor
72 | return (
73 | module.resource &&
74 | /\.js$/.test(module.resource) &&
75 | module.resource.indexOf(
76 | path.join(__dirname, '../node_modules')
77 | ) === 0
78 | )
79 | }
80 | }),
81 | // extract webpack runtime and module manifest to its own file in order to
82 | // prevent vendor hash from being updated whenever app bundle is updated
83 | new webpack.optimize.CommonsChunkPlugin({
84 | name: 'manifest',
85 | chunks: ['vendor']
86 | }),
87 | // copy custom static assets
88 | new CopyWebpackPlugin([
89 | {
90 | from: path.resolve(__dirname, '../static'),
91 | to: config.build.assetsSubDirectory,
92 | ignore: ['.*']
93 | }
94 | ])
95 | ]
96 | })
97 |
98 | if (config.build.productionGzip) {
99 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
100 |
101 | webpackConfig.plugins.push(
102 | new CompressionWebpackPlugin({
103 | asset: '[path].gz[query]',
104 | algorithm: 'gzip',
105 | test: new RegExp(
106 | '\\.(' +
107 | config.build.productionGzipExtensions.join('|') +
108 | ')$'
109 | ),
110 | threshold: 10240,
111 | minRatio: 0.8
112 | })
113 | )
114 | }
115 |
116 | if (config.build.bundleAnalyzerReport) {
117 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
118 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
119 | }
120 |
121 | module.exports = webpackConfig
122 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var prodEnv = require('./prod.env')
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"'
6 | })
7 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | var path = require('path')
3 |
4 | module.exports = {
5 | build: {
6 | env: require('./prod.env'),
7 | index: path.resolve(__dirname, '../dist/index.html'),
8 | assetsRoot: path.resolve(__dirname, '../dist'),
9 | assetsSubDirectory: 'static',
10 | assetsPublicPath: '/',
11 | productionSourceMap: true,
12 | // Gzip off by default as many popular static hosts such as
13 | // Surge or Netlify already gzip all static assets for you.
14 | // Before setting to `true`, make sure to:
15 | // npm install --save-dev compression-webpack-plugin
16 | productionGzip: false,
17 | productionGzipExtensions: ['js', 'css'],
18 | // Run the build command with an extra argument to
19 | // View the bundle analyzer report after build finishes:
20 | // `npm run build --report`
21 | // Set to `true` or `false` to always turn it on or off
22 | bundleAnalyzerReport: process.env.npm_config_report
23 | },
24 | dev: {
25 | env: require('./dev.env'),
26 | port: 9080,
27 | autoOpenBrowser: true,
28 | assetsSubDirectory: 'static',
29 | assetsPublicPath: '/',
30 | proxyTable: {},
31 | // CSS Sourcemaps off by default because relative paths are "buggy"
32 | // with this option, according to the CSS-Loader README
33 | // (https://github.com/webpack/css-loader#sourcemaps)
34 | // In our experience, they generally work as expected,
35 | // just be aware of this issue when enabling this option.
36 | cssSourceMap: false
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/favicon.ico
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 飞享IM
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wechat",
3 | "version": "1.1.6",
4 | "description": "基于fshare的开源即时通讯web客户端",
5 | "author": "comsince",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "start": "node build/dev-server.js",
10 | "build": "node build/build.js"
11 | },
12 | "dependencies": {
13 | "@wcjiang/notify": "^2.0.12",
14 | "axios": "^0.19.0",
15 | "crypto-js": "^3.1.9-1",
16 | "element-ui": "^2.13.0",
17 | "kurento-utils": "^6.14.0",
18 | "pinyin": "^2.9.0",
19 | "qiniu-js": "^2.5.5",
20 | "stylus": "^0.54.5",
21 | "stylus-loader": "^3.0.1",
22 | "uuid-js": "^0.7.5",
23 | "v-viewer": "^1.5.1",
24 | "vue": "^2.5.2",
25 | "vue-axios": "^2.1.4",
26 | "vue-router": "^3.0.1",
27 | "vuex": "^3.0.1",
28 | "webrtc-adapter": "^7.5.1",
29 | "xgplayer": "^2.4.7",
30 | "xgplayer-vue": "^1.1.5"
31 | },
32 | "devDependencies": {
33 | "autoprefixer": "^6.7.2",
34 | "babel-core": "^6.22.1",
35 | "babel-loader": "^6.2.10",
36 | "babel-plugin-transform-runtime": "^6.22.0",
37 | "babel-preset-env": "^1.3.2",
38 | "babel-preset-stage-2": "^6.22.0",
39 | "babel-register": "^6.22.0",
40 | "chalk": "^1.1.3",
41 | "connect-history-api-fallback": "^1.3.0",
42 | "copy-webpack-plugin": "^4.0.1",
43 | "css-loader": "^0.28.0",
44 | "eventsource-polyfill": "^0.9.6",
45 | "express": "^4.14.1",
46 | "extract-text-webpack-plugin": "^2.0.0",
47 | "file-loader": "^0.11.1",
48 | "friendly-errors-webpack-plugin": "^1.1.3",
49 | "html-webpack-plugin": "^2.28.0",
50 | "http-proxy-middleware": "^0.17.3",
51 | "webpack-bundle-analyzer": "^2.2.1",
52 | "semver": "^5.3.0",
53 | "shelljs": "^0.7.6",
54 | "opn": "^4.0.2",
55 | "optimize-css-assets-webpack-plugin": "^1.3.0",
56 | "ora": "^1.2.0",
57 | "rimraf": "^2.6.0",
58 | "url-loader": "^0.5.8",
59 | "vue-loader": "^11.3.4",
60 | "vue-style-loader": "^2.0.5",
61 | "vue-template-compiler": "^2.2.6",
62 | "webpack": "^2.3.3",
63 | "webpack-dev-middleware": "^1.10.0",
64 | "webpack-hot-middleware": "^2.18.0",
65 | "webpack-merge": "^4.1.0"
66 | },
67 | "engines": {
68 | "node": ">= 4.0.0",
69 | "npm": ">= 3.0.0"
70 | },
71 | "browserslist": [
72 | "> 1%",
73 | "last 2 versions",
74 | "not ie <= 8"
75 | ]
76 | }
77 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
24 |
25 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {font-family: "iconfont";
2 | src: url('iconfont.eot?t=1592274269382'); /* IE9 */
3 | src: url('iconfont.eot?t=1592274269382#iefix') format('embedded-opentype'), /* IE6-IE8 */
4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAy4AAsAAAAAF1wAAAxrAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCGNAqcCJY0ATYCJANgCzIABCAFhG0HgkgbjhMjEaac1Znsrwu4Q2pxlyQy2IObrVRoQS6vgYCib9QxOJH4IiO5qF+ow/eNfD6asz87syHZ3YSNIpaKUxrUQ1PFrWIWapq6UlGHe0epaDhfeu6XbnWqb5qigXvxVnttfwLZhMWW2FkAIGzCGPqKGBjm9r1elL5r9UfHVLmp/Mh2kRVXwMsKwuZGZq3NEiDU5pzCUfHDAgj4/+de7UtZdgxvGFdhembVe0k+PEhPbzk/I0qJX4egajeFx89jXjnD/AGAA9SrmnUzzg8CRpseqIuCj4xV3x0IAAGBCAax9+yXDzUYFCd0GzlsSA3UsUqwkgkE6jJVznUZiAs81NwSrhHAKnf8ydcII2qAA0+h7il7cMZApCi0dTV1/u9EOXWirj8PwPl6AAUQDIABnJy7iQigPgt2hZCnMrQA6AyreJNqhSoqxV8JUtopwUqokqikKg6lUClRBivDlBHKktYuraWtq///v1Zp38KUVQw9dOUTdNalgwtDCm3/hQdoIcMACySYYIYAEWp4gUIDFYzwhAcIdOChBwMHEBXoKgEAaS8bvQCFmiZoAUVlggwoviYYAMXPBAug+JsgAUoQwHQn2wEwI4MBCMhQACIyEYAamYI9vkwFQJEOABpkIRxzkiUAjMjBADyRQwF4IIcBIMgRAHTIJZjAA61dTNADraUmMKB1dQYclPC2r2MD0AtgYQDfDNx1rqDa5SG4BI+BM503teBSvTkQWcdkWTDAn6lWkr8syUxzFuvVVtKw9hLxN8tDzKgykzR+GslDI795b7OfJHkbzWZvKdggyRrewztohu5skz+3/Umk54WnQXRHc3H1uqWWDYTW1Ztr1m8a6wndjHNPAti2pgjd+eZAfufT6Gn8hg0W3bp1JrZ+vdlz48ZSad/RWqvfGalvbOgnPwbFPsRDvyopGdWosbBWSwmqleKswhpUpUCw5vvVmqbMD2pVVQuFNSA2JcaNTY75lOwV/UzZcC8SVDdF+gmw9k2l4yR1AHfjp8PSpmfEodQILz53R2rF2rhNu9PKvdQ60Sq/emdaZe36+C01f1Pj9ZU2DGBOhannK87sci8urTt1PO9lEu5qg6GBDIMsI0SmQUVmud1uo6J75VN6NCY2V0caKBEXcwp729cOq8KuLcKV3/OK+XM2AhF/p24tQgeS7ms/eqX6Am67jM/P2lBtDOMJA93QVVjhdrhFzLYrtpVmwFZvmRMpwhnsk72aSIJQDSF1wxxT0OhOb7S5Ba1ALweRBwT8Dqn8Lvul9xqBN8gUtUFIZfBHZuxM2Q5ckZNrVe6uHZViZEGwPXNyof4Gyc7EZS+fMDqpDxk4JDurzJwhxBxc9SKi6ECLSvpCJIgWyEzewOwortYPzon1dkMr8LJwMOj3+9DAqweg6uJXZxtaweAqzA2qFZHJAzQAa6hvUFVWdnV5Ze0T2ucIhYHhY3DlBbWF8bk0JEoBzNxnPo7Wxr5BtMrdIJ7zarVi/2VEM+1eIDTHpKl2fDnMP1TrAwg/W968IW8TiBoESNvZebCe/xCbZ+/OMffnHiBzBtuCgQjxcdKYbMnnJ3cEvtGF0NU8Sk5updHND/Lk4/8oiVJLcqM8H5ZnZ2X20BZjB1ETDkCMirKstZ3BvW6rw31ZDGbAIYFlhdB8/nJhQEax+0mfwLbYvfJaZFy39hyBqKrmDOzS9/UpzO9/fWUzuF6tCwOiYBdzEYUWKKRxjeGs9zPtdUCn5wotMp7v6exp8Z2IfJpCn7Yv+x0NOO8CR+u8r+6PsNx42pbSV5fIehWjtA1W7Iac14SGenlTpyawOVx5Vj2jUcQuj7Vt8jghd/xC5KPnAXXbw+74Fj9xa3/Qup+44fUz5L0DdLkv5T6eJ15e3ctDXdvFDZENiE7PGRvu8d4jfOx7hrPRjPeaRtd7V6Pm0H7Drve20FCbNSzC53WYfHKzOFncfJLzcm/1U/STzQvd4nf4sFbvq9c661FH/lXFKSkzirdar+VcUWYm3tS28TPo7HH11+N1SatcYC2QW8UwP02IEDbviti1WTjtGG/tFqFxRbg0Ilsk5jkPG6cY6w1TrPXyFFJPCulnB/IUPM+Pjinu634zX7/YIK5LBAtPnM50+nP8P3+9veLQzTZ+1puHDkkGP046dP3wIAPCal/VcmtjF8oLFy1c2mUprNSZ3RDcaDnGFIUd06Aolh3G0LadoD89JXrKzRPdXP3Za6v1NWl2qmCvhdVmr7t9chXt3nZeLd1bQsx49FGU74KRmDbo3/uSSFI8bVwFKSd2HSmOjMf5F9tO3IUawdQk2nRkxCbXJE633byaGyuadHdh/KBxe+I+DCousgNi8yA/mtj54rwgJLniJk0SrlquCs5pDZYGIXZbb89ed7QdlJ9U621vl+1EDjdp+/ZeiU2s9cjNrct5+D3BzX+LR8VGgWreidJ17tebrUxXj8bJJGgyVzN3Xi3RRPbiarjauXNrOQjRODdWlerwvXHAUGqoCOufGXlq1PcPj8yMfybD51y6O3xz/d1OvnccUro2ddjEdClVe/uQz8JR/97crU+IFRMO6trq+Lr6j35XVfYy3rk/bGTzDwU4cdCFDVrDT/dfauNGfGCG1GJRVG62P5/WbBDZP7dbONa1520/P7McQT+n32D//MOs9PUTxZKE9A22N33gSppGV9+ovLyMzBknFZlUEnx+EIk6vaowg6STqjxNKi/94AOJqMvrpgysaHM1rXpcBD2Td12vHPzF14Ls5R7jMpk0bLCSHFwzxN3dLX1XVV22+a6H5uMkS8LontP3s/jv+lsa+C27QGoKD/eP7Tp7ht+MRdK4blVh+RVeVdwrZv2Hv8YuBH6rvZNQwZV6z51YbosLWZR4qU943nZSRvepXjyiktgfle5jR9SfY3enopJ2mQtXFQT27bv6iHXs0fbhfarHFvNl+GLy7YdzWzUjjYOnpzc9vW0LPYr4UUVk4kRSBKG8iINALStsNTyNFBUT0qmSpZ2mFxeTVMLvP6ScpeanVQske8SIbOJym93CqcxMP7FHxqp142JHz5y0yy3kZCxNsU1uezJsQZfY9KguOZWCO9I2lCO7bghiWXi/eSnxBfMj1qTNz+hc6NRk+TSMGia0aDIDHf72YcMyAnoGZIpui1tcQa9e5VZoWhBts6bae5/BYP6V1fpKpnmq+FdhzBocS7cnzJiR7pkXYfe0f6e6z9RqJtL3BSUlQ3AhoMFmt1MHhdv87I124Tvzd4JdO/XjhqbFgY7AxU0NH0/VFog5oWKB0W7Xfi98ZxpSp3o5h5i+N01Zu8bRD1m7dvL8NX/vcptbxL+bjvUtbIYv/nOQykrScyZxUOUgMeRAPRuurMpGX/3Js9ZPuVIwderZzb/jhQt43G/0N/Xxv70pp4ysFf+VIqUfxLXi/yf9T/4vBsIPJw9I3yBuXOHBpS8m9ekUkOc3y2Ptx/CrGsjSAgYW/5JinHfecDhv8f0M/6Zkxl1GOGZt/B7A/8oW6usuOE6baW6bT3bSDlAb6Bzu/3gwJ0GdYsHt6zSp4cPvaFjZhHzuY3Yzj2fIGQzc/eAGvUXtBIcYaCENH++j7ev616i270nLaeCEw5zS8OcRSsSaE1zMX9qhwJpeWqo8E35Vi799T3mry6xwk/OphvoH9moC/F11ah7F7pKqypvd/JumPdoib4yCOuAACLof8Fd1nQEtc3LNtZ0/7q45xBw8EIAp1GhvsPxgzENEBFZBjWQsoBscNxZhxHhQEKYB0BULDphAxgnMQYcbmELGBwbLd2MeFnyLVZAJxQIKiTGmiI4rxMGrAe5AsGjDMKZSGjVu4HX/AuVzbqJJd/4fGItb2XV4lWz+hBJMHxF2qW6ck0waKtjH4d4gz4lVhlKIXZg4V70sFjIvMoypqA3waoA7e3nBRJuSGFMpT+4GqT//Bcrn3JTs97bkHxiLPnP9b8J+axifoqy137modqluOOFJZu5qqGA+hAe5biCmyh8shdiFkhZB9WIhjifrinB8t3hNnAMgQH1E7w0hFVXTDdOyHdfzz+/cvXf/wcNHj3UmJFhhOXeW5WWc+EB6KvNW8WgLaorcJn6L515ZWczWD8SsLVW1/N6Gm/YGC91uNKovIAcHQWTGOXFRWx9YylGMJLdUqkGDiGOvqqhuyF+PN5x0uZdwirJ+W8cfsscxWKIA6kVY2mYgstX/+oGY0BXz2JJ43u1hgI4lbz11nL+nDqvK6rsrKFN3vvJc+IpaDQ==') format('woff2'),
5 | url('iconfont.woff?t=1592274269382') format('woff'),
6 | url('iconfont.ttf?t=1592274269382') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
7 | url('iconfont.svg?t=1592274269382#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family: "iconfont" !important;
12 | font-size: 16px;
13 | font-style: normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-shipin:before {
19 | content: "\e669";
20 | }
21 |
22 | .icon-shanchu-fangkuang:before {
23 | content: "\e791";
24 | }
25 |
26 | .icon-zengjiashuzi:before {
27 | content: "\e641";
28 | }
29 |
30 | .icon-jianshanchu-xianxingfangkuang:before {
31 | content: "\e75a";
32 | }
33 |
34 | .icon-zengjia2:before {
35 | content: "\e689";
36 | }
37 |
38 | .icon-yichu:before {
39 | content: "\e656";
40 | }
41 |
42 | .icon-zengjia:before {
43 | content: "\e668";
44 | }
45 |
46 | .icon-delete-br:before {
47 | content: "\e63d";
48 | }
49 |
50 | .icon-loading-solid:before {
51 | content: "\e647";
52 | }
53 |
54 | .icon-fasongshibai:before {
55 | content: "\e62c";
56 | }
57 |
58 | .icon-pengyou1:before {
59 | content: "\e631";
60 | }
61 |
62 | .icon-yaoqinghaoyou:before {
63 | content: "\e61c";
64 | }
65 |
66 | .icon-jiahaoyou:before {
67 | content: "\e603";
68 | }
69 |
70 | .icon-ai-video:before {
71 | content: "\e66b";
72 | }
73 |
74 | .icon-biaoqing:before {
75 | content: "\e666";
76 | }
77 |
78 | .icon-dkw_xiaoxi:before {
79 | content: "\e606";
80 | }
81 |
82 | .icon-dianhua:before {
83 | content: "\e729";
84 | }
85 |
86 | .icon-pengyou:before {
87 | content: "\e61a";
88 | }
89 |
90 | .icon-sousuo:before {
91 | content: "\e659";
92 | }
93 |
94 | .icon-tuichu:before {
95 | content: "\e61f";
96 | }
97 |
98 | .icon-tupian:before {
99 | content: "\e623";
100 | }
101 |
102 | .icon-wenjian:before {
103 | content: "\e61b";
104 | }
105 |
106 | .icon-guaduan:before {
107 | content: "\e640";
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/assets/fonts/iconfont.eot
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "1698562",
3 | "name": "vue-chat",
4 | "font_family": "iconfont",
5 | "css_prefix_text": "icon-",
6 | "description": "",
7 | "glyphs": [
8 | {
9 | "icon_id": "5100253",
10 | "name": "视频",
11 | "font_class": "shipin",
12 | "unicode": "e669",
13 | "unicode_decimal": 58985
14 | },
15 | {
16 | "icon_id": "4425816",
17 | "name": "删除-方框",
18 | "font_class": "shanchu-fangkuang",
19 | "unicode": "e791",
20 | "unicode_decimal": 59281
21 | },
22 | {
23 | "icon_id": "4770755",
24 | "name": "增加数字",
25 | "font_class": "zengjiashuzi",
26 | "unicode": "e641",
27 | "unicode_decimal": 58945
28 | },
29 | {
30 | "icon_id": "6129045",
31 | "name": "9减、删除-线性方框",
32 | "font_class": "jianshanchu-xianxingfangkuang",
33 | "unicode": "e75a",
34 | "unicode_decimal": 59226
35 | },
36 | {
37 | "icon_id": "8358855",
38 | "name": "增加4",
39 | "font_class": "zengjia2",
40 | "unicode": "e689",
41 | "unicode_decimal": 59017
42 | },
43 | {
44 | "icon_id": "5253606",
45 | "name": "移除",
46 | "font_class": "yichu",
47 | "unicode": "e656",
48 | "unicode_decimal": 58966
49 | },
50 | {
51 | "icon_id": "1301395",
52 | "name": "增加",
53 | "font_class": "zengjia",
54 | "unicode": "e668",
55 | "unicode_decimal": 58984
56 | },
57 | {
58 | "icon_id": "1272671",
59 | "name": "移除",
60 | "font_class": "delete-br",
61 | "unicode": "e63d",
62 | "unicode_decimal": 58941
63 | },
64 | {
65 | "icon_id": "4581221",
66 | "name": "发送中",
67 | "font_class": "loading-solid",
68 | "unicode": "e647",
69 | "unicode_decimal": 58951
70 | },
71 | {
72 | "icon_id": "3398713",
73 | "name": "发送失败",
74 | "font_class": "fasongshibai",
75 | "unicode": "e62c",
76 | "unicode_decimal": 58924
77 | },
78 | {
79 | "icon_id": "6659620",
80 | "name": "朋友",
81 | "font_class": "pengyou1",
82 | "unicode": "e631",
83 | "unicode_decimal": 58929
84 | },
85 | {
86 | "icon_id": "3315144",
87 | "name": "邀请好友",
88 | "font_class": "yaoqinghaoyou",
89 | "unicode": "e61c",
90 | "unicode_decimal": 58908
91 | },
92 | {
93 | "icon_id": "6683016",
94 | "name": "加好友",
95 | "font_class": "jiahaoyou",
96 | "unicode": "e603",
97 | "unicode_decimal": 58883
98 | },
99 | {
100 | "icon_id": "795513",
101 | "name": "视频",
102 | "font_class": "ai-video",
103 | "unicode": "e66b",
104 | "unicode_decimal": 58987
105 | },
106 | {
107 | "icon_id": "842937",
108 | "name": "表情",
109 | "font_class": "biaoqing",
110 | "unicode": "e666",
111 | "unicode_decimal": 58982
112 | },
113 | {
114 | "icon_id": "2078817",
115 | "name": "dkw_消息 ",
116 | "font_class": "dkw_xiaoxi",
117 | "unicode": "e606",
118 | "unicode_decimal": 58886
119 | },
120 | {
121 | "icon_id": "2967254",
122 | "name": "符号-电话",
123 | "font_class": "dianhua",
124 | "unicode": "e729",
125 | "unicode_decimal": 59177
126 | },
127 | {
128 | "icon_id": "4166140",
129 | "name": "朋友",
130 | "font_class": "pengyou",
131 | "unicode": "e61a",
132 | "unicode_decimal": 58906
133 | },
134 | {
135 | "icon_id": "5582330",
136 | "name": "搜索",
137 | "font_class": "sousuo",
138 | "unicode": "e659",
139 | "unicode_decimal": 58969
140 | },
141 | {
142 | "icon_id": "5893499",
143 | "name": "退出",
144 | "font_class": "tuichu",
145 | "unicode": "e61f",
146 | "unicode_decimal": 58911
147 | },
148 | {
149 | "icon_id": "7588121",
150 | "name": "图片",
151 | "font_class": "tupian",
152 | "unicode": "e623",
153 | "unicode_decimal": 58915
154 | },
155 | {
156 | "icon_id": "10099699",
157 | "name": "文件",
158 | "font_class": "wenjian",
159 | "unicode": "e61b",
160 | "unicode_decimal": 58907
161 | },
162 | {
163 | "icon_id": "10515201",
164 | "name": "挂断",
165 | "font_class": "guaduan",
166 | "unicode": "e640",
167 | "unicode_decimal": 58944
168 | }
169 | ]
170 | }
171 |
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/assets/fonts/iconfont.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/assets/fonts/iconfont.woff
--------------------------------------------------------------------------------
/src/assets/fonts/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/assets/fonts/iconfont.woff2
--------------------------------------------------------------------------------
/src/assets/img/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/assets/img/loading.gif
--------------------------------------------------------------------------------
/src/assets/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/assets/img/logo.png
--------------------------------------------------------------------------------
/src/assets/img/qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/assets/img/qrcode.png
--------------------------------------------------------------------------------
/src/components/friendlist/friendlist.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
{{item.initial}}
7 |
8 |
9 |
10 | {{newFriendRequestCount}}
11 |
12 |
![]()
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
42 |
43 |
98 |
--------------------------------------------------------------------------------
/src/components/info/man.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/components/info/man.png
--------------------------------------------------------------------------------
/src/components/info/woman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/components/info/woman.png
--------------------------------------------------------------------------------
/src/components/menu/addtip.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/menu/rightMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/components/mycard/mycard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{unreadTotalCount}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
30 |
31 |
32 |
33 |
99 |
100 |
173 |
--------------------------------------------------------------------------------
/src/components/search/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/components/search/delete.png
--------------------------------------------------------------------------------
/src/components/search/search.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
44 |
45 |
92 |
--------------------------------------------------------------------------------
/src/constant/index.js:
--------------------------------------------------------------------------------
1 | export const WS_PROTOCOL = 'wss';
2 | export const WS_IP = 'backend-websocket.fsharechat.cn/ws';
3 | export const HTTP_IP = 'backend-http.fsharechat.cn';
4 | //websocket端口,请不要更改
5 | export const WS_PORT = 9326;
6 | export const HEART_BEAT_INTERVAL = 25 * 1000;
7 | export const RECONNECT_INTERVAL = 30 * 1000;
8 | export const BINTRAY_TYPE = 'blob';
9 |
10 | //signal
11 | export const CONNECT = 'CONNECT';
12 | export const DISCONNECT = 'DISCONNECT';
13 | export const CONNECT_ACK = 'CONNECT_ACK';
14 | export const PUBLISH = 'PUBLISH';
15 | export const PUB_ACK = 'PUB_ACK';
16 | //subsignal
17 | export const FRP = 'FRP';
18 | export const FP = 'FP';
19 | export const FALS = 'FALS';
20 | export const UPUI = 'UPUI';
21 | export const GPGI = 'GPGI';
22 | export const GPGM = 'GPGM';
23 | export const GAM = 'GAM';
24 | export const GC = 'GC';
25 | export const GMI = 'GMI';
26 | export const GKM = 'GKM';
27 | export const GQ = 'GQ';
28 | export const GD = 'GD';
29 | export const MP = 'MP';
30 | export const MS = "MS";
31 | export const MN = "MN";
32 | export const MR = "MR";
33 | export const RMN = "RMN";
34 | export const GQNUT = "GQNUT";
35 | export const GMURL = "GMURL";
36 | export const US = "US";
37 | export const FAR = "FAR";
38 | export const FRN = "FRN";
39 | export const FHR = "FHR";
40 | export const FN = "FN";
41 | export const MMI = "MMI";
42 | export const LRM = "LRM";
43 |
44 | export const HTTP_HOST = "https://"+HTTP_IP + "/"
45 | export const LOGIN_API = HTTP_HOST + "login";
46 | export const SNED_VERIFY_CODE_API = HTTP_HOST + "send_code";;
47 |
48 | export const KEY_VUE_DEVICE_ID = 'vue-device-id';
49 | export const KEY_VUE_USER_ID = 'vue-user-id';
50 | export const KEY_VUE_TOKEN = 'vue-token';
51 |
52 | //userId 这里为了演示静态登录,由于还没有登录界面所以暂时使用静态userid
53 | export const USER_ID = 'TYTzTz33';
54 | export const CLINET_ID = 'bccdb58cfdb34d861576810441000';
55 | //token
56 | export const TOKEN = '6Yz2rQDrtRPRc3j9PesLy0De17uX2RlVcvkxU/UmGEaMamd/kaagwWNThIWSGMd6SPVHxLeynho03sJWdbm7wFMRO8VTKf5Wogv7l7gKLsq81mswRha3j6FMdDVHVJ+MLJrVjrThkqXrK1rHwsZvGxpqSGcekHIggI1UEEJSXyQ=';
57 |
58 | //是否使用七牛上传文件
59 | export const UPLOAD_BY_QINIU = false;
60 |
61 | export const ERROR_CODE = 400;
62 | export const SUCCESS_CODE = 200;
63 |
64 | //conversation
65 | export const CONVERSATION_MAX_MESSAGE_SIZE = 50;
66 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 | import store from './store'
7 | import './permission' // permission control
8 | Vue.config.productionTip = false
9 |
10 | import ElementUI from 'element-ui';
11 | import 'element-ui/lib/theme-chalk/index.css';
12 | Vue.use(ElementUI);
13 | /* eslint-disable no-new */
14 | const vm = new Vue({
15 | el: '#app',
16 | router,
17 | store,
18 | template: '',
19 | components: { App }
20 | })
21 |
--------------------------------------------------------------------------------
/src/page/chat/chat.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
34 |
35 |
46 |
--------------------------------------------------------------------------------
/src/page/friend/friend.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
25 |
26 |
--------------------------------------------------------------------------------
/src/page/friend/searchfriend.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
16 |
20 |
24 |
25 |
26 |
27 |
34 |
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
{{scope.row.displayName}}
50 |
{{scope.row.mobile}}
51 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
61 |
62 |
66 |
67 |
68 |
69 |
70 |
123 |
124 |
--------------------------------------------------------------------------------
/src/page/group/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/src/page/group/delete.png
--------------------------------------------------------------------------------
/src/page/login/login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |

10 |
输入账号进行安全登录
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{sendVerifyBtnText}}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
135 |
136 |
--------------------------------------------------------------------------------
/src/page/main.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
55 |
56 |
76 |
--------------------------------------------------------------------------------
/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from './router'
2 | import store from './store'
3 | const whiteList = ['/login'] // 不重定向白名单
4 | router.beforeEach((to, from, next) => {
5 | var token = localStorage.getItem('vue-token');
6 | console.log('match route token '+token+" to "+to.path +" from "+from.path +" next "+next.path);
7 | if (token) {
8 | if (to.path === '/login') {
9 | next({ path: '/conversation' })
10 | } else {
11 | next();
12 | }
13 | } else {
14 | if (whiteList.indexOf(to.path) !== -1) {
15 | next()
16 | } else {
17 | next('/login')
18 | }
19 | }
20 | })
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | const router = new Router({
7 | mode: 'history',
8 | // 共三个页面: 聊天页面,好友页面,个人简历分别对应一下路由
9 | routes: [
10 | {
11 | path: '/login',
12 | component: require('@/page/login/login.vue')
13 | },
14 | {
15 | path: '/',
16 | component: require('@/page/main.vue'),
17 | redirect: 'conversation',
18 | children: [{
19 | path: 'conversation',
20 | name: 'conversation',
21 | component: require('@/page/chat/chat.vue'),
22 | hidden:true,
23 | },
24 | {
25 | path: 'friend',
26 | name: 'friend',
27 | component: require('@/page/friend/friend.vue'),
28 | hidden:true,
29 | }],
30 | },
31 | ],
32 | linkActiveClass: 'active'
33 | })
34 | // router.push({ path: '/chat' });
35 | export default router
--------------------------------------------------------------------------------
/src/webrtc/callEndReason.js:
--------------------------------------------------------------------------------
1 | export default class CallEndReason {
2 | static REASON_Unknown = 'unknown';
3 | static REASON_Busy = 'busy';
4 | static REASON_SignalError = 'signalError';
5 | static REASON_Hangup = 'hangup';
6 | static REASON_MediaError = 'mediaError';
7 | static REASON_RemoteHangup = 'remoteHangup';
8 | static REASON_OpenCameraFailure = 'openCameraError';
9 | static REASON_Timeout = 'timeout';
10 | static REASON_AcceptByOtherClient = 'acceptByOtherClient';
11 | static REASON_AllLeft = 'allLeft';
12 | }
--------------------------------------------------------------------------------
/src/webrtc/callSession.js:
--------------------------------------------------------------------------------
1 | import CallState from "./callState";
2 | export default class CallSession{
3 | callId;
4 | clientId;
5 | audioOnly;
6 | startTime;
7 | sessionCallback;
8 | callState;
9 | voipClient;
10 | tos;
11 |
12 | constructor(voipClient){
13 | this.startTime = new Date().getTime();
14 | this.voipClient = voipClient;
15 | }
16 |
17 | setState(state){
18 | if(this.callState != state){
19 | var previousState = this.callState;
20 | this.callState = state;
21 | console.log("set current call state "+this.callState);
22 | switch(state){
23 | case CallState.STATUS_INCOMING:
24 | case CallState.STATUS_OUTGOING:
25 | this.voipClient.currentEngineCallback.shouldStartRing(state === CallState.STATUS_INCOMING)
26 | break;
27 | case CallState.STATUS_CONNECTING:
28 | this.voipClient.currentEngineCallback.shouldSopRing()
29 | break;
30 | case CallState.STATUS_IDLE:
31 | case CallState.STATUS_CONNECTED:
32 | if (previousState == CallState.STATUS_INCOMING || previousState == CallState.STATUS_OUTGOING) {
33 | this.voipClient.currentEngineCallback.shouldSopRing();
34 | }
35 | break;
36 |
37 | }
38 | if(this.sessionCallback){
39 | this.sessionCallback.didChangeState(this.callState);
40 | }
41 | }
42 |
43 | }
44 |
45 | endCall(endCallReason,sender=''){
46 | this.setState(CallState.STATUS_IDLE);
47 | this.voipClient.closeCall();
48 | this.voipClient.currentSession = null;
49 | if(this.sessionCallback){
50 | this.sessionCallback.didCallEndWithReason(endCallReason,sender);
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/src/webrtc/callState.js:
--------------------------------------------------------------------------------
1 | export default class CallState {
2 | static STATUS_IDLE = 0;
3 | static STATUS_OUTGOING = 1;
4 | static STATUS_INCOMING = 2;
5 | static STATUS_CONNECTING = 3;
6 | static STATUS_CONNECTED = 4;
7 | }
--------------------------------------------------------------------------------
/src/webrtc/engineCallback.js:
--------------------------------------------------------------------------------
1 | export default class EngineCallback{
2 | onReceiveCall(callSession){}
3 |
4 | shouldStartRing(isIncomming){}
5 |
6 | shouldSopRing(){}
7 | }
--------------------------------------------------------------------------------
/src/webrtc/message/callAnswerMessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContent from '../../websocket/message/messageContent';
2 | import MessageContentType from '../../websocket/message/messageContentType';
3 | import StringUtils from "../../websocket/utils/StringUtil"
4 |
5 | export default class CallAnswerMessageContent extends MessageContent {
6 | callId;
7 | audioOnly;
8 |
9 | constructor(mentionedType = 0, mentionedTargets = []) {
10 | super(MessageContentType.VOIP_CONTENT_TYPE_ACCEPT, mentionedType, mentionedTargets);
11 | }
12 |
13 | digest() {
14 | return '';
15 | }
16 |
17 | encode() {
18 | let payload = super.encode();
19 | payload.content = this.callId;
20 |
21 | var obj;
22 | if (this.audioOnly) {
23 | obj = '1';
24 | } else {
25 | obj = '0';
26 | }
27 | payload.binaryContent = StringUtils.utf8_to_b64(obj);
28 | return payload;
29 | };
30 |
31 | decode(payload) {
32 | super.decode(payload);
33 | this.callId = payload.content;
34 | let str = StringUtils.b64_to_utf8(payload.binaryContent);
35 |
36 | this.audioOnly = (str === '1');
37 | }
38 | }
--------------------------------------------------------------------------------
/src/webrtc/message/callAnswerTMessageContent.js:
--------------------------------------------------------------------------------
1 |
2 | import MessageContent from '../../websocket/message/messageContent';
3 | import MessageContentType from '../../websocket/message/messageContentType';
4 | import StringUtils from "../../websocket/utils/StringUtil"
5 |
6 | export default class CallAnswerTMessageContent extends MessageContent {
7 | callId;
8 | audioOnly;
9 |
10 | constructor(mentionedType = 0, mentionedTargets = []) {
11 | super(MessageContentType.VOIP_CONTENT_TYPE_ACCEPT_T, mentionedType, mentionedTargets);
12 | }
13 |
14 | digest() {
15 | return '';
16 | }
17 |
18 | encode() {
19 | let payload = super.encode();
20 | payload.content = this.callId;
21 |
22 | var obj;
23 | if (this.audioOnly) {
24 | obj = '1';
25 | } else {
26 | obj = '0';
27 | }
28 | payload.binaryContent = StringUtils.utf8_to_b64(obj);
29 | return payload;
30 | };
31 |
32 | decode(payload) {
33 | super.decode(payload);
34 | this.callId = payload.content;
35 | let str = StringUtils.b64_to_utf8(payload.binaryContent);
36 |
37 | this.audioOnly = (str === '1');
38 | }
39 | }
--------------------------------------------------------------------------------
/src/webrtc/message/callByeMessageContent.js:
--------------------------------------------------------------------------------
1 |
2 | import MessageContent from '../../websocket/message/messageContent';
3 | import MessageContentType from '../../websocket/message/messageContentType';
4 | export default class CallByeMessageContent extends MessageContent {
5 | callId;
6 |
7 | constructor(mentionedType = 0, mentionedTargets = []) {
8 | super(MessageContentType.VOIP_CONTENT_TYPE_END, mentionedType, mentionedTargets);
9 | }
10 |
11 | digest() {
12 | return '';
13 | }
14 |
15 | encode() {
16 | let payload = super.encode();
17 | payload.content = this.callId;
18 | return payload;
19 | };
20 |
21 | decode(payload) {
22 | super.decode(payload);
23 | this.callId = payload.content;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/webrtc/message/callModifyMessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContent from '../../websocket/message/messageContent';
2 | import MessageContentType from '../../websocket/message/messageContentType';
3 | import StringUtils from "../../websocket/utils/StringUtil"
4 | export default class CallModifyMessageContent extends MessageContent {
5 | callId;
6 | audioOnly;
7 |
8 | constructor(mentionedType = 0, mentionedTargets = []) {
9 | super(MessageContentType.VOIP_CONTENT_TYPE_MODIFY, mentionedType, mentionedTargets);
10 | }
11 |
12 | digest() {
13 | return '';
14 | }
15 |
16 | encode() {
17 | let payload = super.encode();
18 | payload.content = this.callId;
19 |
20 | var obj;
21 | if (this.audioOnly) {
22 | obj = '1';
23 | } else {
24 | obj = '0';
25 | }
26 | payload.binaryContent = StringUtils.utf8_to_b64(obj);
27 | return payload;
28 | };
29 |
30 | decode(payload) {
31 | super.decode(payload);
32 | this.callId = payload.content;
33 | let str = StringUtils.b64_to_utf8(payload.binaryContent);
34 |
35 | this.audioOnly = (str === '1');
36 | }
37 | }
--------------------------------------------------------------------------------
/src/webrtc/message/callSignalMessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContent from '../../websocket/message/messageContent';
2 | import MessageContentType from '../../websocket/message/messageContentType';
3 | import StringUtils from "../../websocket/utils/StringUtil"
4 | export default class CallSignalMessageContent extends MessageContent {
5 | callId;
6 | payload;
7 |
8 | constructor(mentionedType = 0, mentionedTargets = []) {
9 | super(MessageContentType.VOIP_CONTENT_TYPE_SIGNAL, mentionedType, mentionedTargets);
10 | }
11 |
12 | digest() {
13 | return '';
14 | }
15 |
16 | encode() {
17 | let payload = super.encode();
18 | payload.content = this.callId;
19 | payload.binaryContent = StringUtils.utf8_to_b64(this.payload);
20 | return payload;
21 | };
22 |
23 | decode(payload) {
24 | super.decode(payload);
25 | this.callId = payload.content;
26 | this.payload = StringUtils.b64_to_utf8(payload.binaryContent);
27 | }
28 | }
--------------------------------------------------------------------------------
/src/webrtc/message/callStartMessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContent from '../../websocket/message/messageContent';
2 | import MessageContentType from '../../websocket/message/messageContentType';
3 | import StringUtils from "../../websocket/utils/StringUtil"
4 | export default class CallStartMessageContent extends MessageContent {
5 | callId;
6 | targetId;
7 | connectTime;
8 | endTime;
9 | status;
10 | audioOnly;
11 |
12 | constructor(callId, targetId, audioOnly){
13 | super(MessageContentType.VOIP_CONTENT_TYPE_START);
14 | this.callId = callId;
15 | this.targetId = targetId;
16 | this.audioOnly = audioOnly;
17 | }
18 |
19 | digest() {
20 | if (this.audioOnly) {
21 | return '[语音通话]';
22 | } else {
23 | return '[视频通话]';
24 | }
25 | }
26 |
27 | encode() {
28 | let payload = super.encode();
29 | payload.content = this.callId;
30 |
31 | let obj = {
32 | c: this.connectTime,
33 | e: this.endTime,
34 | s: this.status,
35 | a: this.audioOnly ? 1 : 0,
36 | t:this.targetId
37 | };
38 | payload.binaryContent = StringUtils.utf8_to_b64(JSON.stringify(obj));
39 | return payload;
40 | };
41 |
42 | decode(payload) {
43 | super.decode(payload);
44 | this.callId = payload.content;
45 | let json = StringUtils.b64_to_utf8(payload.binaryContent);
46 | let obj = JSON.parse(json);
47 |
48 | this.connectTime = obj.c;
49 | this.endTime = obj.e;
50 | this.status = obj.s;
51 | this.audioOnly = (obj.a === 1);
52 | this.targetId = obj.t;
53 | }
54 | }
--------------------------------------------------------------------------------
/src/webrtc/participant.js:
--------------------------------------------------------------------------------
1 | import CallSignalMessageContent from "./message/callSignalMessageContent";
2 |
3 | export default class Participant {
4 | target;
5 | sender;
6 | groupCallClient;
7 | rtcPeer;
8 |
9 | constructor(target,sender,groupCallClient){
10 | this.target = target;
11 | this.sender = sender;
12 | this.groupCallClient = groupCallClient;
13 | }
14 |
15 |
16 | getVideoElement(){
17 | return document.getElementById(this.sender);
18 | }
19 |
20 | onIceCandidate(candidate, wp) {
21 | console.log("Local candidate" + JSON.stringify(candidate));
22 |
23 | var message = {
24 | type: 'onIceCandidate',
25 | candidate: candidate,
26 | name: name
27 | };
28 | this.groupCallClient.sendSignalMessage(message);
29 | }
30 |
31 | offerToReceiveVideo(error, offerSdp, wp){
32 | if (error) return console.error ("sdp offer error"+error)
33 | console.log(this.sender + ' Invoking SDP offer callback function');
34 | var msg = {
35 | type : "receiveVideoFrom",
36 | sender : this.sender,
37 | sdpOffer : offerSdp
38 | };
39 | this.groupCallClient.sendSignalMessage(msg);
40 | }
41 |
42 | dispose() {
43 | console.log('Disposing participant ' + this.sender);
44 | this.rtcPeer.dispose();
45 | }
46 |
47 | // sendSignalMessage(msg){
48 | // var callSignalMessageContent = new CallSignalMessageContent();
49 | // //callSignalMessageContent.callId = this.currentSession.callId;
50 | // callSignalMessageContent.payload = JSON.stringify(msg);
51 | // this.groupCallClient.sendMessage(this.target,callSignalMessageContent);
52 | // }
53 | }
--------------------------------------------------------------------------------
/src/webrtc/sessionCallback.js:
--------------------------------------------------------------------------------
1 | export default class SessionCallback{
2 | didCallEndWithReason(callEndReason,sender = ''){}
3 |
4 | didChangeState(callState){}
5 |
6 | didChangeMode(mode){}
7 |
8 | didCreateLocalVideoTrack(stream){}
9 |
10 | didReceiveRemoteVideoTrack(stream,sender = ''){}
11 |
12 | didReceiveRemoteAudioTrack(stream){}
13 |
14 | didError(error){}
15 |
16 | didGetStats(stats){}
17 | }
--------------------------------------------------------------------------------
/src/websocket/chatManager.js:
--------------------------------------------------------------------------------
1 | export default class ChatManager {
2 | static onReceiveMessageListeners = [];
3 |
4 | static addReceiveMessageListener(listener){
5 | this.onReceiveMessageListeners.push(listener);
6 | }
7 |
8 | static onReceiveMessage(protoMessage){
9 | for(var messageListener of this.onReceiveMessageListeners){
10 | messageListener.onReceiveMessage(protoMessage);
11 | }
12 | }
13 |
14 | static removeOnReceiveMessageListener(){
15 | ChatManager.onReceiveMessageListeners = [];
16 | }
17 | }
--------------------------------------------------------------------------------
/src/websocket/future/futureResult.js:
--------------------------------------------------------------------------------
1 | export default class FutureResult {
2 | code;
3 | result;
4 |
5 | constructor(code, result){
6 | this.code = code
7 | this.result = result
8 | }
9 | }
--------------------------------------------------------------------------------
/src/websocket/future/promiseResolve.js:
--------------------------------------------------------------------------------
1 | export default class PromiseResolve {
2 | resolve;
3 | timeoutId;
4 | protoMessageId;
5 |
6 | constructor(resolve,timeoutId){
7 | this.resolve = resolve;
8 | this.timeoutId = timeoutId;
9 | }
10 | }
--------------------------------------------------------------------------------
/src/websocket/handler/abstractmessagehandler.js:
--------------------------------------------------------------------------------
1 | import MessageHandler from "./messageHandler";
2 | import Logger from "../utils/logger";
3 | import FutureResult from '../future/futureResult';
4 | import { SUCCESS_CODE, ERROR_CODE } from "../../constant";
5 | /**
6 | * 关于promise返回说明
7 | * 1.对于操作型消息,一般只需要返回成功与失败即可
8 | * 2.对于结果型消息,一般判断是否为空
9 | */
10 | export default class AbstractMessageHandler extends MessageHandler{
11 |
12 | constructor(vueWebsocket){
13 | super();
14 | this.vueWebsocket = vueWebsocket;
15 | }
16 |
17 | get vueWebsocketClient(){
18 | return this.vueWebsocket;
19 | }
20 |
21 | processMessage(proto){
22 | Logger.log("AbstractMessageHandler messageId "+proto.messageId +" proto content "+proto.content);
23 | var promiseReslove = this.vueWebsocket.resolvePromiseMap.get(proto.messageId);
24 | if(promiseReslove){
25 | clearTimeout(promiseReslove.timeoutId);
26 | if(proto.content == ''){
27 | promiseReslove.resolve(new FutureResult(ERROR_CODE,proto.content));
28 | } else {
29 | promiseReslove.resolve(new FutureResult(SUCCESS_CODE,this.notifyContent(proto.content)));
30 | }
31 | this.vueWebsocket.resolvePromiseMap.delete(proto.messageId);
32 | }
33 | }
34 |
35 | notifyContent(content){
36 | return content
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/src/websocket/handler/addGroupMemberHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, GAM } from "../../constant";
3 |
4 | export default class AddGroupMemberHandler extends AbstractMessageHandler {
5 | match(proto){
6 | return proto.signal == PUB_ACK && proto.subSignal == GAM;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/websocket/handler/connectackhandler.js:
--------------------------------------------------------------------------------
1 | import { CONNECT_ACK } from "../../constant";
2 | import AbstractMessageHandler from "./abstractmessagehandler";
3 | import LocalStore from "../store/localstore";
4 |
5 | export default class ConnectAckHandler extends AbstractMessageHandler{
6 | constructor(vueWebsocket){
7 | super(vueWebsocket);
8 | }
9 | match(protoObj){
10 | return protoObj.signal == CONNECT_ACK;
11 | }
12 |
13 | processMessage(data){
14 | console.log("ConnectAckHandler process message");
15 | var connectAcceptedMessage = JSON.parse(data.content);
16 | console.log("connectAcceptedMessage friendHead "+connectAcceptedMessage.friendHead+" messageHead "+connectAcceptedMessage.messageHead);
17 | //拉取朋友列表
18 | this.vueWebsocket.getFriend();
19 | let lastMessageSeq = LocalStore.getLastMessageSeq();
20 | if(!lastMessageSeq){
21 | lastMessageSeq = '0';
22 | this.vueWebsocket.sendAction('changetFirstLogin',true);
23 | } else {
24 | this.vueWebsocket.sendAction('changetFirstLogin',false);
25 | }
26 | //好友请求信息
27 | this.vueWebsocket.getFriendRequest(LocalStore.getFriendRequestVersion());
28 | //初始拉取消息列表
29 | this.vueWebsocket.pullMessage(lastMessageSeq,0,0,LocalStore.getSendMessageCount());
30 | LocalStore.setLastMessageSeq(connectAcceptedMessage.messageHead);
31 | }
32 | }
--------------------------------------------------------------------------------
/src/websocket/handler/createGroupHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, GC, ERROR_CODE, SUCCESS_CODE } from "../../constant";
3 |
4 | export default class CreateGroupHandler extends AbstractMessageHandler {
5 | match(proto){
6 | return proto.signal == PUB_ACK && proto.subSignal == GC;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/websocket/handler/dismissGroupHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, GD } from "../../constant";
3 |
4 | export default class DismissGroupHandler extends AbstractMessageHandler{
5 | match(proto){
6 | return proto.signal == PUB_ACK && proto.subSignal == GD;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/websocket/handler/friendAddRequestHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { FAR,PUB_ACK } from "../../constant";
3 | import Logger from "../utils/logger";
4 |
5 | export default class FriendAddRequestHandler extends AbstractMessageHandler{
6 | match(proto){
7 | return proto.signal == PUB_ACK && proto.subSignal == FAR;
8 | }
9 |
10 | processMessage(proto){
11 | var result = JSON.parse(proto.content);
12 | Logger.log("friend add request result "+result);
13 | }
14 | }
--------------------------------------------------------------------------------
/src/websocket/handler/friendRequestHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, FRP } from "../../constant";
3 | import LocalStore from "../store/localstore";
4 |
5 | export default class FriendRequestHandler extends AbstractMessageHandler {
6 | match(proto){
7 | return proto.signal == PUB_ACK && proto.subSignal == FRP;
8 | }
9 |
10 |
11 | processMessage(proto){
12 | if(proto.content != null && proto.content != ''){
13 | var friendRequests = JSON.parse(proto.content);
14 | var validRequest = [];
15 | var targeIds = [];
16 | for(var friendRequest of friendRequests){
17 | if(friendRequest.from != LocalStore.getUserId()){
18 | validRequest.push(friendRequest);
19 | targeIds.push(friendRequest.from);
20 | }
21 | }
22 | this.vueWebsocket.getUserInfos(targeIds);
23 | this.vueWebsocket.sendAction("updateFriendRequest",validRequest);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/websocket/handler/getGroupInfoHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { GPGI, PUB_ACK } from "../../constant";
3 | import GroupInfo from "../model/groupInfo";
4 |
5 | export default class GetGroupInfoHandler extends AbstractMessageHandler{
6 |
7 | match(proto){
8 | return proto.signal == PUB_ACK && proto.subSignal == GPGI;
9 | }
10 |
11 | processMessage(proto){
12 | if(proto.content != null){
13 | var groupInfoList = JSON.parse(proto.content);
14 | var groups = [];
15 | for(var groupInfo of groupInfoList){
16 | groups.push(GroupInfo.convert2GroupInfo(groupInfo));
17 | }
18 | this.vueWebsocket.sendAction("updateGroupInfos",groups);
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/src/websocket/handler/getGroupMemberHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, GPGM } from "../../constant";
3 | import GroupMember from "../model/groupMember";
4 |
5 | export default class GetGroupMemberHandler extends AbstractMessageHandler {
6 | match(proto){
7 | return proto.signal == PUB_ACK && proto.subSignal == GPGM;
8 | }
9 |
10 | notifyContent(content){
11 | var groupMemberList = JSON.parse(content);
12 | var groupMembers = [];
13 | for(var groupMember of groupMemberList){
14 | groupMembers.push(GroupMember.convert2GroupMember(groupMember));
15 | }
16 | return groupMembers;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/websocket/handler/getMinioUploadUrlHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, GMURL } from "../../constant";
3 |
4 | export default class GetMinioUploadUrlHandler extends AbstractMessageHandler {
5 | match(proto){
6 | return proto.signal == PUB_ACK && proto.subSignal == GMURL;
7 | }
8 | notifyContent(content){
9 | var result = JSON.parse(content);
10 | return result;
11 | }
12 | }
--------------------------------------------------------------------------------
/src/websocket/handler/getUploadtokenHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, GQNUT } from "../../constant";
3 | import LocalStore from "../store/localstore";
4 |
5 | export default class UploadTokenHandler extends AbstractMessageHandler{
6 | match(proto){
7 | return proto.signal == PUB_ACK && proto.subSignal == GQNUT;
8 | }
9 |
10 | processMessage(proto){
11 | if(proto.content){
12 | var uploadTokenResponse = JSON.parse(proto.content);
13 | var domain = uploadTokenResponse.domain;
14 | var token = uploadTokenResponse.token;
15 | console.log("domain "+domain+" token "+token);
16 | LocalStore.setUploadToken(domain,token);
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/websocket/handler/getfriendresultHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, FP } from "../../constant";
3 | import LocalStore from "../store/localstore";
4 |
5 | export default class GetFriendResultHandler extends AbstractMessageHandler{
6 | match(proto){
7 | return proto.signal == PUB_ACK && proto.subSignal == FP;
8 | }
9 |
10 | processMessage(proto){
11 | var friendList = JSON.parse(proto.content);
12 | var userIds = [];
13 | for(var i in friendList){
14 | userIds[i] = friendList[i].friendUid;
15 | }
16 | this.vueWebsocket.sendAction("updateFriendIds",friendList);
17 | //获取当前登录用户信息
18 | userIds.push(LocalStore.getUserId());
19 | this.vueWebsocket.getUserInfos(userIds);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/websocket/handler/getuserinfoHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, UPUI, ERROR_CODE, SUCCESS_CODE } from "../../constant";
3 | import UserInfo from "../model/userInfo";
4 | import py from "pinyin"
5 | import { isBuffer } from "util";
6 | import FutureResult from "../future/futureResult";
7 |
8 | export default class GetUserInfoHandler extends AbstractMessageHandler{
9 | match(proto){
10 | return proto.signal == PUB_ACK && proto.subSignal == UPUI;
11 | }
12 |
13 | processMessage(proto){
14 | if(proto.content != null && proto.content != ''){
15 | var userInfoList = JSON.parse(proto.content);
16 | var stateFriendList = [];
17 | var userInfos = [];
18 | for(var i in userInfoList){
19 | var displayName = userInfoList[i].displayName === '' ? userInfoList[i].mobile : userInfoList[i].displayName;
20 | var pinyinInitial = py(displayName,{
21 | style: py.STYLE_FIRST_LETTER
22 | });
23 | var initial = pinyinInitial[0][0];
24 |
25 | if(initial.length > 1){
26 | var initial = initial.substr(0,1);
27 | var reg= /^[A-Za-z]/;
28 | if(reg.test(initial)){
29 | initial = initial.toUpperCase();
30 | } else {
31 | initial = "#";
32 | }
33 | } else {
34 | initial = initial.toUpperCase();
35 | }
36 | stateFriendList.push({
37 | id: parseInt(i) + 1,
38 | wxid: userInfoList[i].uid, //微信号
39 | initial: initial, //姓名首字母
40 | img: userInfoList[i].portrait, //头像
41 | signature: "", //个性签名
42 | nickname: displayName, //昵称
43 | sex: 0, //性别 1为男,0为女
44 | remark: displayName, //备注
45 | area: userInfoList[i].address, //地区
46 | });
47 | userInfos.push(UserInfo.convert2UserInfo(userInfoList[i]));
48 | }
49 | if(this.vueWebsocket.resolvePromiseMap.has(proto.messageId)){
50 | console.log("messageId "+proto.messageId);
51 | var promiseReslove = this.vueWebsocket.resolvePromiseMap.get(proto.messageId);
52 | clearTimeout(promiseReslove.timeoutId);
53 | var displayName = userInfoList[0].displayName === '' ? userInfoList[0].mobile : userInfoList[0].displayName;
54 | promiseReslove.resolve(new FutureResult(SUCCESS_CODE,displayName));
55 | this.vueWebsocket.resolvePromiseMap.delete(proto.messageId);
56 | }
57 |
58 | this.vueWebsocket.sendAction("updateUserInfos",userInfos);
59 | this.vueWebsocket.sendAction("updateFriendList",stateFriendList);
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/src/websocket/handler/handleFriendRequestHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, FHR } from "../../constant";
3 | import Logger from "../utils/logger";
4 |
5 | export default class HandleFriendRequestHandler extends AbstractMessageHandler {
6 | match(proto){
7 | return proto.signal == PUB_ACK && proto.subSignal == FHR;
8 | }
9 |
10 | processMessage(proto){
11 | if(proto.content && proto.content != ''){
12 | var message = JSON.parse(proto.content);
13 | Logger.log("handle friend request "+message);
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/websocket/handler/kickGroupmemberHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, GKM } from "../../constant";
3 |
4 | export default class KickGroupMemberHandler extends AbstractMessageHandler{
5 | match(proto){
6 | return proto.signal == PUB_ACK && proto.subSignal == GKM;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/websocket/handler/loadRemoteMessageHander.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, LRM } from "../../constant";
3 |
4 | export default class LoadRemoteMessageHandler extends AbstractMessageHandler {
5 | match(proto){
6 | return proto.signal == PUB_ACK && proto.subSignal == LRM;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/websocket/handler/messageHandler.js:
--------------------------------------------------------------------------------
1 | export default class MessageHandler{
2 | match(signal){
3 | return false;
4 | }
5 |
6 | processMessage(data){
7 |
8 | }
9 | }
--------------------------------------------------------------------------------
/src/websocket/handler/modifyMyInfoHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { MMI, PUB_ACK } from "../../constant";
3 | import Logger from "../utils/logger";
4 |
5 | export default class ModifyInfoHandler extends AbstractMessageHandler{
6 | match(proto){
7 | return proto.signal == PUB_ACK && proto.subSignal == MMI;
8 | }
9 |
10 | processMessage(proto){
11 | if(proto && proto.content != ''){
12 | Logger.log("modify myInfo "+proto.content);
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/websocket/handler/notifyFriendHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { FN, PUBLISH } from "../../constant";
3 | import Logger from "../utils/logger";
4 |
5 | export default class NotifyFriendHandler extends AbstractMessageHandler {
6 | match(proto){
7 | return proto.signal == PUBLISH && proto.subSignal == FN;
8 | }
9 |
10 | processMessage(proto){
11 | if(proto.content && proto.content != ''){
12 | var friendNotify = JSON.parse(proto.content);
13 | Logger.log("friend head "+friendNotify.head);
14 | this.vueWebsocket.getFriend();
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/websocket/handler/notifyFriendRequestHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUBLISH, FRN } from "../../constant";
3 | import Logger from "../utils/logger";
4 | import LocalStore from "../store/localstore";
5 |
6 | export default class NotifyFriendRequestHandler extends AbstractMessageHandler {
7 | match(proto){
8 | return proto.signal == PUBLISH && proto.subSignal == FRN;
9 | }
10 |
11 |
12 | processMessage(proto){
13 | if(proto.content != null){
14 | var friendRequestNotification = JSON.parse(proto.content);
15 | Logger.log("friend request version "+friendRequestNotification.version);
16 | LocalStore.setFriendRequestVersion(friendRequestNotification.version);
17 | this.vueWebsocket.getFriendRequest(friendRequestNotification.version);
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/websocket/handler/notifyMessageHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUBLISH, MN } from "../../constant";
3 | import LocalStore from "../store/localstore";
4 |
5 | export default class NotifyMessageHandler extends AbstractMessageHandler{
6 | match(proto){
7 | return proto.signal == PUBLISH && proto.subSignal == MN;
8 | }
9 |
10 | processMessage(proto){
11 | console.log("notifymessage "+proto.content);
12 | let notify = JSON.parse(proto.content);
13 | console.log("notify messageHead "+notify.messageHead+" notify type "+notify.type);
14 | //更新messageHead
15 | LocalStore.resetSendMessageCount();
16 | LocalStore.setLastMessageSeq(notify.messageHead);
17 | this.vueWebsocket.pullMessage(notify.messageHead,notify.type,1);
18 | }
19 | }
--------------------------------------------------------------------------------
/src/websocket/handler/notifyRecallMessageHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUBLISH, RMN } from "../../constant";
3 |
4 | export default class NotifyRecallMessageHandler extends AbstractMessageHandler {
5 | match(proto){
6 | return proto.signal == PUBLISH && proto.subSignal == RMN;
7 | }
8 |
9 | processMessage(proto){
10 | if(proto.content){
11 | var notifyRecalMessage = JSON.parse(proto.content);
12 | console.log("from user "+notifyRecalMessage.fromUser + " messageUid "+notifyRecalMessage.messageUid);
13 | this.vueWebsocket.sendAction('updateMessageContent',notifyRecalMessage);
14 | }
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/src/websocket/handler/quitGroupHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, GQ } from "../../constant";
3 |
4 | export default class QuitGroupHandler extends AbstractMessageHandler {
5 | match(proto){
6 | return proto.signal == PUB_ACK && proto.subSignal == GQ;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/websocket/handler/recallMessageHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, MR } from "../../constant";
3 |
4 | export default class RecallMessageHandler extends AbstractMessageHandler {
5 | match(proto){
6 | return proto.signal == PUB_ACK && proto.subSignal == MR;
7 | }
8 |
9 | notifyContent(content){
10 | var content = JSON.parse(content);
11 | return content.code;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/websocket/handler/receiveMessageHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { MP, PUB_ACK } from "../../constant";
3 | import Message from "../message/message";
4 | import ProtoMessage from "../message/protomessage";
5 | import ProtoConversationInfo from '../model/protoConversationInfo'
6 | import LocalStore from "../store/localstore";
7 | import UnreadCount from "../model/unReadCount";
8 | import MessageConfig from "../message/messageConfig";
9 | import PersistFlag from "../message/persistFlag";
10 | import ChatManager from "../chatManager";
11 |
12 | export default class ReceiveMessageHandler extends AbstractMessageHandler {
13 | conversations = [];
14 |
15 | match(proto){
16 | return proto.signal == PUB_ACK && proto.subSignal == MP;
17 | }
18 |
19 | processMessage(proto){
20 | LocalStore.resetSendMessageCount();
21 | var content = JSON.parse(proto.content);
22 | console.log("current "+content.current+" head "+content.head+" messageCount "+content.messageCount);
23 | if(content.messageCount == 0){
24 | this.vueWebsocket.sendAction('changeEmptyMessageState',true);
25 | } else {
26 | this.vueWebsocket.sendAction('changeEmptyMessageState',false);
27 | }
28 | for(var protoMessage of content.messageResponseList){
29 | var protoMessage = ProtoMessage.toProtoMessage(protoMessage);
30 | if(MessageConfig.isDisplayableMessage(protoMessage)){
31 | this.addProtoMessage(protoMessage);
32 | }
33 | ChatManager.onReceiveMessage(protoMessage);
34 | }
35 | this.vueWebsocket.sendAction('changetFirstLogin',false);
36 | }
37 |
38 | addProtoMessage(protoMessage){
39 | this.vueWebsocket.sendAction('addProtoMessage',protoMessage);
40 | }
41 | }
--------------------------------------------------------------------------------
/src/websocket/handler/searchUserResultHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { US,PUB_ACK } from "../../constant";
3 | import Logger from "../utils/logger";
4 | import UserInfo from "../model/userInfo";
5 |
6 | export default class SearchUserResultHandler extends AbstractMessageHandler{
7 | match(proto){
8 | return proto.signal == PUB_ACK && proto.subSignal == US;
9 | }
10 |
11 | processMessage(proto){
12 | var searchUserList = JSON.parse(proto.content);
13 | this.vueWebsocket.sendAction("updateSearchUser",searchUserList);
14 | }
15 | }
--------------------------------------------------------------------------------
/src/websocket/handler/sendMessageHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { MS, PUB_ACK } from "../../constant";
3 | import LocalStore from "../store/localstore";
4 | import Logger from "../utils/logger";
5 | import MessageStatus from "../message/messageStatus";
6 |
7 | export default class SendMessageHandler extends AbstractMessageHandler{
8 |
9 | match(proto){
10 | return proto.signal == PUB_ACK && proto.subSignal == MS;
11 | }
12 |
13 | processMessage(proto){
14 | Logger.log("sendMessage Handler messageId "+proto.messageId)
15 | if(this.vueWebsocket.resolvePromiseMap.has(proto.messageId)){
16 | var promiseReslove = this.vueWebsocket.resolvePromiseMap.get(proto.messageId);
17 | if(proto.content != ''){
18 | var content = JSON.parse(proto.content);
19 | console.log("SendMessageHandler messageId "+content.messageId+" timestamp protoMessageId "+promiseReslove.protoMessageId);
20 | this.vueWebsocket.sendAction("updateProtoMessageUid",{
21 | messageId: promiseReslove.protoMessageId,
22 | messageUid: content.messageId
23 | });
24 | //update messageStatus
25 | this.vueWebsocket.sendAction("updateMessageStatus",{
26 | messageId: promiseReslove.protoMessageId,
27 | status: MessageStatus.Sent
28 | });
29 | LocalStore.updateSendMessageCount();
30 | promiseReslove.resolve('success');
31 | } else {
32 | this.vueWebsocket.sendAction("updateMessageStatus",{
33 | messageId: promiseReslove.protoMessageId,
34 | status: MessageStatus.SendFailure
35 | });
36 | promiseReslove.resolve('fail');
37 | }
38 | clearTimeout(promiseReslove.timeoutId);
39 | this.vueWebsocket.resolvePromiseMap.delete(proto.messageId);
40 | }
41 |
42 | }
43 | }
--------------------------------------------------------------------------------
/src/websocket/handler/setFriendAliasRequestHandler.js:
--------------------------------------------------------------------------------
1 | import AbstractMessageHandler from "./abstractmessagehandler";
2 | import { PUB_ACK, FALS } from "../../constant";
3 |
4 | export default class SetFriendAliasRequestHandler extends AbstractMessageHandler{
5 | match(proto){
6 | return proto.signal == PUB_ACK && proto.subSignal == FALS;
7 | }
8 |
9 | }
--------------------------------------------------------------------------------
/src/websocket/listener/onReceiverMessageListener.js:
--------------------------------------------------------------------------------
1 | export default class OnReceiverMessageListener {
2 | onReceiveMessage(protoMessage){
3 |
4 | }
5 | }
--------------------------------------------------------------------------------
/src/websocket/message/fileMessageContent.js:
--------------------------------------------------------------------------------
1 | import MediaMessageContent from './mediaMessageContent';
2 | import MessageContentMediaType from './messageContentMediaType';
3 | import MessageContentType from './messageContentType';
4 |
5 | export default class FileMessageContent extends MediaMessageContent {
6 | name = '';
7 | size = 0;
8 | static FILE_NAME_PREFIX = '[文件] ';
9 |
10 | constructor(fileOrLocalPath, remotePath) {
11 | super(MessageContentType.File, MessageContentMediaType.File, fileOrLocalPath, remotePath);
12 | if (typeof File !== 'undefined' && fileOrLocalPath instanceof File) {
13 | this.name = fileOrLocalPath.name;
14 | this.size = fileOrLocalPath.size;
15 | }
16 | }
17 |
18 | digest() {
19 | return '[文件]';
20 | }
21 |
22 | encode() {
23 | let payload = super.encode();
24 | payload.searchableContent = FileMessageContent.FILE_NAME_PREFIX + this.name;
25 | payload.content = this.size + '';
26 | return payload;
27 | };
28 |
29 | decode(payload) {
30 | super.decode(payload);
31 | if(payload.searchableContent){
32 | if(payload.searchableContent.indexOf(FileMessageContent.FILE_NAME_PREFIX) === 0){
33 | this.name = payload.searchableContent.substring(payload.searchableContent.indexOf(FileMessageContent.FILE_NAME_PREFIX) + FileMessageContent.FILE_NAME_PREFIX.length);
34 | }else {
35 | this.name = payload.searchableContent;
36 | }
37 | this.size = this.formateSize(payload.content);
38 | }
39 | }
40 |
41 | formateSize(value) { if (null == value || value == '') { return "0 Bytes"; }
42 | var unitArr = new Array("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"); var index = 0;
43 | var srcsize = parseFloat(value); index = Math.floor(Math.log(srcsize) / Math.log(1024));
44 | var size = srcsize / Math.pow(1024, index); size = size.toFixed(2); //保留的小数位数
45 | return size + unitArr[index];
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/src/websocket/message/imageMessageContent.js:
--------------------------------------------------------------------------------
1 | import MediaMessageContent from './mediaMessageContent';
2 | import MessageContentMediaType from './messageContentMediaType';
3 | import MessageContentType from './messageContentType';
4 |
5 | export default class ImageMessageContent extends MediaMessageContent {
6 | // base64 encoded, 不包含头部:data:image/png;base64,
7 | thumbnail;
8 |
9 | constructor(fileOrLocalPath, remotePath, thumbnail) {
10 | super(MessageContentType.Image, MessageContentMediaType.Image, fileOrLocalPath, remotePath);
11 | this.thumbnail = thumbnail;
12 | }
13 |
14 | digest() {
15 | return '[图片]';
16 | }
17 |
18 | encode() {
19 | let payload = super.encode();
20 | payload.mediaType = MessageContentMediaType.Image;
21 | payload.binaryContent = this.thumbnail;
22 | return payload;
23 | };
24 |
25 | decode(payload) {
26 | super.decode(payload);
27 | this.thumbnail = payload.binaryContent;
28 | }
29 | }
--------------------------------------------------------------------------------
/src/websocket/message/mediaMessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContent from './messageContent'
2 | export default class MediaMessageContent extends MessageContent {
3 | file;
4 | remotePath = '';
5 | localPath = '';
6 | mediaType = 0;
7 |
8 | constructor(messageType, mediaType = 0, fileOrLocalPath, remotePath) {
9 | super(messageType);
10 | this.mediaType = mediaType;
11 | this.remotePath = remotePath;
12 | if(typeof fileOrLocalPath === "string"){
13 | this.localPath = fileOrLocalPath;
14 | }else {
15 | this.file = fileOrLocalPath;
16 | if (fileOrLocalPath && fileOrLocalPath.path !== undefined) {
17 | this.localPath = fileOrLocalPath.path;
18 | // attention: 粘贴的时候,path是空字符串,故采用了这个trick
19 | if (this.localPath.indexOf(fileOrLocalPath.name) < 0) {
20 | this.localPath += fileOrLocalPath.name;
21 | }
22 | }
23 | }
24 | }
25 |
26 | encode() {
27 | let payload = super.encode();
28 | payload.localMediaPath = this.localPath;
29 | payload.remoteMediaUrl = this.remotePath;
30 | payload.mediaType = this.mediaType;
31 | payload.searchableContent = this.digest();
32 | return payload;
33 | };
34 |
35 | decode(payload) {
36 | super.decode(payload);
37 | this.localPath = payload.localMediaPath;
38 | this.remotePath = payload.remoteMediaUrl;
39 | this.mediaType = payload.mediaType;
40 | }
41 | }
--------------------------------------------------------------------------------
/src/websocket/message/message.js:
--------------------------------------------------------------------------------
1 | import Conversation from '../model/conversation'
2 | import MessageStatus from './messageStatus'
3 | import store from '../../store'
4 | import LocalStore from '../store/localstore';
5 | /***
6 | * message in json format
7 | {
8 | "conversation":{
9 | "conversationType": 0,
10 | "target": "UZUWUWuu",
11 | "line": 0,
12 | }
13 | "from": "UZUWUWuu",
14 | "content": {
15 | "type": 1,
16 | "searchableContent": "1234",
17 | "pushContent": "",
18 | "content": "",
19 | "binaryContent": "",
20 | "localContent": "",
21 | "mediaType": 0,
22 | "remoteMediaUrl": "",
23 | "localMediaPath": "",
24 | "mentionedType": 0,
25 | "mentionedTargets": [ ]
26 | },
27 | "messageId": 52,
28 | "direction": 1,
29 | "status": 5,
30 | "messageUid": 75735276990792720,
31 | "timestamp": 1550849394256,
32 | "to": ""
33 | }
34 | */
35 | export default class Message {
36 | conversation = {};
37 | from = '';
38 | content = {};
39 | messageId = 0;
40 | direction = 0;
41 | status = 0;
42 | messageUid = 0;
43 | timestamp = 0;
44 | tos = '';
45 |
46 | // static toMessage(obj){
47 | // let msg = new Message();
48 | // msg.conversation = new Conversation(obj.conversationType,obj.target,obj.line);
49 | // msg.from = obj.from;
50 | // msg.content = obj.content;
51 | // msg.messageId = obj.messageId;
52 | // if(obj.from == USER_ID){
53 | // msg.direction = 0;
54 | // } else {
55 | // msg.direction = 1;
56 | // }
57 | // msg.status = obj.status;
58 | // msg.messageUid = obj.messageId;
59 | // msg.timestamp = obj.timestamp;
60 | // msg.to = obj.target;
61 | // return msg;
62 | // }
63 |
64 | static toMessage(state,sendMessage){
65 | var message = new Message();
66 | var target = sendMessage.target;
67 | if(!target){
68 | target = state.selectTarget;
69 | }
70 | if(sendMessage.tos != ''){
71 | message.tos = sendMessage.tos;
72 | }
73 | console.log("to message target "+target);
74 | let stateConversationInfo = state.conversations.find(conversation => conversation.conversationInfo.target === target);
75 | console.log("conversationtype "+stateConversationInfo.conversationInfo.conversationType +" target "+stateConversationInfo.conversationInfo.target);
76 | message.conversation = new Conversation(stateConversationInfo.conversationInfo.conversationType,
77 | stateConversationInfo.conversationInfo.target,
78 | stateConversationInfo.conversationInfo.line);
79 | console.log("send message content "+sendMessage.messageContent)
80 | message.content = sendMessage.messageContent;
81 | message.from = state.userId;
82 | message.status = MessageStatus.Sending;
83 | message.timestamp = new Date().getTime();
84 | message.direction = 0;
85 | message.messageId = new Date().getTime();
86 | return message;
87 | }
88 |
89 | //方法不支持重载
90 | static conert2Message(sendMessage){
91 | var message = new Message();
92 | var target = sendMessage.target;
93 | if(!target){
94 | target = store.state.selectTarget;
95 | }
96 | console.log("to message target "+target);
97 | let stateConversationInfo = store.state.conversations.find(conversation => conversation.conversationInfo.target === target);
98 | console.log("conversationtype "+stateConversationInfo.conversationInfo.conversationType +" target "+stateConversationInfo.conversationInfo.target);
99 | message.conversation = new Conversation(stateConversationInfo.conversationInfo.conversationType,
100 | stateConversationInfo.conversationInfo.target,
101 | stateConversationInfo.conversationInfo.line);
102 | message.content = sendMessage.messageContent;
103 | message.from = LocalStore.getUserId();
104 | message.status = MessageStatus.Sending;
105 | message.timestamp = new Date().getTime();
106 | message.direction = 0;
107 | message.messageId = new Date().getTime();
108 | return message;
109 | }
110 | }
--------------------------------------------------------------------------------
/src/websocket/message/messageContent.js:
--------------------------------------------------------------------------------
1 | import MessagePayload from "./messagePayload";
2 |
3 | /**
4 | * 消息类型基类,一般包括文本消息,文件类消息,媒体类消息
5 | */
6 | export default class MessageContent {
7 | type;
8 | //0 普通消息, 1 部分提醒, 2 提醒全部
9 | mentionedType = 0;
10 | //提醒对象,mentionedType 1时有效
11 | mentionedTargets = [];
12 | extra;
13 |
14 | constructor(type, mentionedType = 0, mentionedTargets = []) {
15 | this.type = type;
16 | this.mentionedType = mentionedType;
17 | this.mentionedTargets = mentionedTargets;
18 | }
19 |
20 | digest() {
21 | return '...digest...';
22 | }
23 |
24 | /**
25 | * return MessagePayload in json format
26 | */
27 | encode() {
28 | let payload = new MessagePayload();
29 | payload.type = this.type;
30 | payload.mentionedType = this.mentionedType;
31 | payload.mentionedTargets = this.mentionedTargets;
32 | return payload;
33 | }
34 |
35 | /**
36 | *
37 | * @param {object} payload object json.parse from message#content
38 | */
39 | decode(payload) {
40 | this.type = payload.type;
41 | this.mentionedType = payload.mentionedType;
42 | this.mentionedTargets = payload.mentionedTargets;
43 | }
44 | }
--------------------------------------------------------------------------------
/src/websocket/message/messageContentMediaType.js:
--------------------------------------------------------------------------------
1 | export default class MessageContentMediaType {
2 | static General = 0;
3 | static Image = 1;
4 | static Voice = 2;
5 | static Video = 3;
6 | static File = 4;
7 | static Portrait = 5;
8 | static Fvaorite = 6;
9 | }
--------------------------------------------------------------------------------
/src/websocket/message/messageContentType.js:
--------------------------------------------------------------------------------
1 | export default class MessageContentType {
2 | // 基本消息类型
3 | static Unknown = 0;
4 | static Text = 1;
5 | static Voice = 2;
6 | static Image = 3;
7 | static Location = 4;
8 | static File = 5;
9 | static Video = 6;
10 | static Sticker = 7;
11 | static ImageText = 8;
12 | static P_Text = 9;
13 |
14 | // 提醒消息
15 | static RecallMessage_Notification = 80;
16 | static Tip_Notification = 90;
17 | static Typing = 91;
18 |
19 | // 群相关消息
20 | static CreateGroup_Notification = 104;
21 | static AddGroupMember_Notification = 105;
22 | static KickOffGroupMember_Notification = 106;
23 | static QuitGroup_Notification = 107;
24 | static DismissGroup_Notification = 108;
25 | static TransferGroupOwner_Notification = 109;
26 | static ChangeGroupName_Notification = 110;
27 | static ModifyGroupAlias_Notification = 111;
28 | static ChangeGroupPortrait_Notification = 112;
29 |
30 | static MuteGroupMember_Notification = 113;
31 | static ChangeJoinType_Notification = 114;
32 | static ChangePrivateChat_Notification = 115;
33 | static ChangeSearchable_Notificaiton = 116;
34 | static SetGroupManager_Notification = 117;
35 | static VOIP_CONTENT_TYPE_START = 400;
36 | static VOIP_CONTENT_TYPE_END = 402;
37 | static VOIP_CONTENT_TYPE_ACCEPT = 401;
38 | static VOIP_CONTENT_TYPE_SIGNAL = 403;
39 | static VOIP_CONTENT_TYPE_MODIFY = 404;
40 | static VOIP_CONTENT_TYPE_ACCEPT_T = 405;
41 | }
--------------------------------------------------------------------------------
/src/websocket/message/messagePayload.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | "content": {
4 | "type": 1,
5 | "searchableContent": "1234",
6 | "pushContent": "",
7 | "content": "",
8 | "binaryContent": "",
9 | "localContent": "",
10 | "mediaType": 0,
11 | "remoteMediaUrl": "",
12 | "localMediaPath": "",
13 | "mentionedType": 0,
14 | "mentionedTargets": [ ]
15 | },
16 | */
17 | export default class MessagePayload {
18 | type;
19 | searchableContent;
20 | pushContent;
21 | content;
22 | binaryContent; // base64 string, 图片时,不包含头部信息:data:image/png;base64,
23 | localContent;
24 | mediaType;
25 | remoteMediaUrl;
26 | localMediaPath;
27 | mentionedType = 0;
28 | mentionedTargets = [];
29 | extra;
30 | }
--------------------------------------------------------------------------------
/src/websocket/message/messageStatus.js:
--------------------------------------------------------------------------------
1 | export default class MessageStatus {
2 | static Sending = 0;
3 | static Sent = 1;
4 | static SendFailure = 2;
5 | static Mentioned = 3;
6 | static AllMentioned = 4;
7 | static Unread = 5;
8 | static Readed = 6;
9 | static Played = 7;
10 | }
--------------------------------------------------------------------------------
/src/websocket/message/modifyGroupInfoType.js:
--------------------------------------------------------------------------------
1 | export default class ModifyGroupInfoType {
2 | static Modify_Group_Name = 0;
3 | static Modify_Group_Portrait = 1;
4 | static Modify_Group_Extra = 2;
5 | }
--------------------------------------------------------------------------------
/src/websocket/message/myInfoType.js:
--------------------------------------------------------------------------------
1 | export default class MyInfotype {
2 | static Modify_DisplayName = 0;
3 | static Modify_Portrait = 1;
4 | static Modify_Gender = 2;
5 | static Modify_Mobile = 3;
6 | static Modify_Email = 4;
7 | static Modify_Address = 5;
8 | static Modify_Company = 6;
9 | static Modify_Social = 7;
10 | static Modify_Extra = 8;
11 | }
--------------------------------------------------------------------------------
/src/websocket/message/notification/addGroupMemberNotification.js:
--------------------------------------------------------------------------------
1 | import MessageContentType from '../messageContentType';
2 |
3 | import GroupNotificationContent from './groupNotification';
4 | import StringUtils from '../../utils/StringUtil'
5 | import webSocketCli from '../../websocketcli'
6 |
7 | export default class AddGroupMemberNotification extends GroupNotificationContent {
8 | invitor = '';
9 | invitees = [];
10 |
11 | constructor(invitor, invitees) {
12 | super(MessageContentType.AddGroupMember_Notification);
13 | this.invitor = invitor;
14 | this.invitees = invitees;
15 | }
16 |
17 | formatNotification() {
18 | let notifyStr;
19 | if (this.invitees.length === 1 && this.invitees[0] === this.invitor) {
20 | if (this.fromSelf) {
21 | return '您加入了群组';
22 | } else {
23 | return webSocketCli.getDisplayName(this.invitor) + ' 加入了群组';
24 | }
25 | }
26 |
27 | if (this.fromSelf) {
28 | notifyStr = '您邀请:';
29 | } else {
30 | notifyStr = webSocketCli.getDisplayName(this.invitor) + '邀请:';
31 | }
32 |
33 | let membersStr = '';
34 | this.invitees.forEach(m => {
35 | membersStr += ' ' + webSocketCli.getDisplayName(m);
36 | });
37 |
38 | return notifyStr + membersStr + '加入了群组';
39 | }
40 |
41 | encode() {
42 | let payload = super.encode();
43 | let obj = {
44 | g: this.groupId,
45 | o: this.invitor,
46 | ms: this.invitees,
47 | };
48 | payload.binaryContent = StringUtils.utf8_to_b64(JSON.stringify(obj));
49 | return payload;
50 | };
51 |
52 | decode(payload) {
53 | super.decode(payload);
54 | let json = StringUtils.b64_to_utf8(payload.binaryContent);
55 | let obj = JSON.parse(json);
56 | this.groupId = obj.g;
57 | this.invitor = obj.o;
58 | this.invitees = obj.ms;
59 | }
60 | }
--------------------------------------------------------------------------------
/src/websocket/message/notification/changeGroupNameNotification.js:
--------------------------------------------------------------------------------
1 | import MessageContentType from "../messageContentType";
2 |
3 | import GroupNotificationContent from "./groupNotification";
4 | import StringUtils from '../../utils/StringUtil'
5 | import webSocketCli from '../../websocketcli'
6 |
7 | export default class ChangeGroupNameNotification extends GroupNotificationContent {
8 | operator = '';
9 | name = '';
10 |
11 | constructor(operator, name) {
12 | super(MessageContentType.ChangeGroupName_Notification);
13 | this.operator = operator;
14 | this.name = name;
15 | }
16 |
17 | formatNotification() {
18 | if (this.fromSelf) {
19 | return '您修改群名称为:' + this.name;
20 | } else {
21 | return webSocketCli.getDisplayName(this.operator)+' 修改群名称为:' + this.name;
22 | }
23 | }
24 |
25 | encode() {
26 | let payload = super.encode();
27 | let obj = {
28 | g: this.groupId,
29 | n: this.name,
30 | o: this.operator,
31 | };
32 | payload.binaryContent = StringUtils.utf8_to_b64(JSON.stringify(obj));
33 | return payload;
34 | }
35 |
36 | decode(payload) {
37 | super.decode(payload);
38 | let json = StringUtils.b64_to_utf8(payload.binaryContent)
39 | let obj = JSON.parse(json);
40 | this.groupId = obj.g;
41 | this.operator = obj.o;
42 | this.name = obj.n;
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/src/websocket/message/notification/createGroupNotification.js:
--------------------------------------------------------------------------------
1 | import MessageContentType from '../messageContentType';
2 |
3 | import GroupNotificationContent from './groupNotification';
4 | import StringUtils from '../../utils/StringUtil'
5 | import webSocketCli from '../../websocketcli'
6 |
7 | export default class CreateGroupNotification extends GroupNotificationContent {
8 | creator = '';
9 | groupName = '';
10 |
11 | constructor(creator, groupName) {
12 | super(MessageContentType.CreateGroup_Notification);
13 | this.creator = creator;
14 | this.groupName = groupName;
15 | }
16 |
17 | formatNotification() {
18 | if (this.fromSelf) {
19 | return '您创建了群组 ' + this.groupName;
20 | } else {
21 | return webSocketCli.getDisplayName(this.creator)+' 创建了群组 ' + this.groupName;
22 | }
23 | }
24 |
25 | encode() {
26 | let payload = super.encode();
27 | let obj = {
28 | g: this.groupId,
29 | n: this.groupName,
30 | o: this.creator,
31 | };
32 | payload.binaryContent = StringUtils.utf8_to_b64(JSON.stringify(obj));
33 | return payload;
34 | }
35 |
36 | decode(payload) {
37 | super.decode(payload);
38 | let json = StringUtils.b64_to_utf8(payload.binaryContent)
39 | let obj = JSON.parse(json);
40 | this.groupId = obj.g;
41 | this.creator = obj.o;
42 | this.groupName = obj.n;
43 | }
44 | }
--------------------------------------------------------------------------------
/src/websocket/message/notification/dismissGroupNotification.js:
--------------------------------------------------------------------------------
1 | import MessageContentType from '../messageContentType';
2 |
3 | import GroupNotificationContent from './groupNotification';
4 | import StringUtils from '../../utils/StringUtil'
5 | import webSocketCli from '../../websocketcli'
6 |
7 | export default class DismissGroupNotification extends GroupNotificationContent {
8 | operator = '';
9 |
10 | constructor(operator) {
11 | super(MessageContentType.DismissGroup_Notification);
12 | this.operator = operator;
13 | }
14 |
15 | formatNotification() {
16 | if (this.fromSelf) {
17 | return '您解散了群组';
18 | } else {
19 | return webSocketCli.getDisplayName(this.operator) + '解散了群组';
20 | }
21 | }
22 |
23 | encode() {
24 | let payload = super.encode();
25 | let obj = {
26 | g: this.groupId,
27 | o: this.operator,
28 | };
29 | payload.binaryContent = StringUtils.utf8_to_b64(JSON.stringify(obj));
30 | return payload;
31 | }
32 |
33 | decode(payload) {
34 | super.decode(payload);
35 | let json = StringUtils.b64_to_utf8(payload.binaryContent)
36 | let obj = JSON.parse(json);
37 | this.groupId = obj.g;
38 | this.operator = obj.o;
39 | }
40 | }
--------------------------------------------------------------------------------
/src/websocket/message/notification/groupNotification.js:
--------------------------------------------------------------------------------
1 | import NotificationMessageContent from "./notificationMessageContent";
2 |
3 | export default class GroupNotificationContent extends NotificationMessageContent {
4 | groupId = '';
5 | }
--------------------------------------------------------------------------------
/src/websocket/message/notification/kickoffGroupMemberNotification.js:
--------------------------------------------------------------------------------
1 | import MessageContentType from "../messageContentType";
2 |
3 | import GroupNotificationContent from "./groupNotification";
4 | import StringUtils from '../../utils/StringUtil'
5 | import webSocketCli from '../../websocketcli'
6 |
7 | export default class KickoffGroupMemberNotification extends GroupNotificationContent {
8 | operator = '';
9 | kickedMembers = [];
10 |
11 | constructor(operator, kickedMembers) {
12 | super(MessageContentType.KickOffGroupMember_Notification);
13 | this.operator = operator;
14 | this.kickedMembers = kickedMembers;
15 | }
16 |
17 | formatNotification() {
18 | let notifyStr;
19 | if (this.fromSelf) {
20 | notifyStr = '您把 ';
21 | } else {
22 | notifyStr = webSocketCli.getDisplayName(this.operator) + '把 ';
23 | }
24 |
25 | let kickedMembersStr = '';
26 | this.kickedMembers.forEach(m => {
27 | kickedMembersStr += ' ' + webSocketCli.getDisplayName(m);
28 | });
29 |
30 | return notifyStr + kickedMembersStr + ' 移除了群组';
31 | }
32 |
33 | encode() {
34 | let payload = super.encode();
35 | let obj = {
36 | g: this.groupId,
37 | ms: this.kickedMembers,
38 | o: this.operateUser,
39 | };
40 | payload.binaryContent = StringUtils.utf8_to_b64(JSON.stringify(obj));
41 | return payload;
42 | }
43 |
44 | decode(payload) {
45 | super.decode(payload);
46 | let json = StringUtils.b64_to_utf8(payload.binaryContent)
47 | let obj = JSON.parse(json);
48 | this.groupId = obj.g;
49 | this.operator = obj.o;
50 | this.kickedMembers = obj.ms;
51 | }
52 | }
--------------------------------------------------------------------------------
/src/websocket/message/notification/notificationMessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContent from "../messageContent";
2 | export default class NotificationMessageContent extends MessageContent {
3 | // message#protoMessageToMessage时设置
4 | fromSelf = false;
5 | constructor(type) {
6 | super(type);
7 | }
8 |
9 | digest(message) {
10 | var desc = '';
11 | try {
12 | desc = this.formatNotification(message);
13 | } catch (error) {
14 | console.log('disgest', error);
15 | }
16 | return desc;
17 | };
18 |
19 | formatNotification(message) {
20 | return '..nofication..';
21 | }
22 | }
--------------------------------------------------------------------------------
/src/websocket/message/notification/quitGroupNotification.js:
--------------------------------------------------------------------------------
1 | import MessageContentType from '../messageContentType';
2 |
3 | import GroupNotificationContent from './groupNotification';
4 | import StringUtils from '../../utils/StringUtil'
5 | import webSocketCli from '../../websocketcli'
6 |
7 | export default class QuitGroupNotification extends GroupNotificationContent {
8 | operator = '';
9 |
10 | constructor(operator) {
11 | super(MessageContentType.QuitGroup_Notification);
12 | this.operator = operator;
13 | }
14 |
15 | formatNotification() {
16 | if (this.fromSelf) {
17 | return '您退出了群组';
18 | } else {
19 | return webSocketCli.getDisplayName(this.operator) + '退出了群组';
20 | }
21 | }
22 |
23 | encode() {
24 | let payload = super.encode();
25 | let obj = {
26 | g: this.groupId,
27 | o: this.operator,
28 | };
29 | payload.binaryContent = StringUtils.utf8_to_b64(JSON.stringify(obj));
30 | return payload;
31 | }
32 |
33 | decode(payload) {
34 | super.decode(payload);
35 | let json = StringUtils.b64_to_utf8(payload.binaryContent)
36 | let obj = JSON.parse(json);
37 | this.groupId = obj.g;
38 | this.operator = obj.o;
39 | }
40 | }
--------------------------------------------------------------------------------
/src/websocket/message/notification/recallMessageNotification.js:
--------------------------------------------------------------------------------
1 | import NotificationMessageContent from './notificationMessageContent'
2 | import MessageContentType from '../messageContentType';
3 | import StringUtils from '../../utils/StringUtil'
4 | import webSocketCli from '../../websocketcli'
5 |
6 | export default class RecallMessageNotification extends NotificationMessageContent {
7 | operatorId = '';
8 | messageUid = '';
9 |
10 | constructor(operatorId, messageUid) {
11 | super(MessageContentType.RecallMessage_Notification);
12 | this.operatorId = operatorId;
13 | this.messageUid = messageUid;
14 | }
15 |
16 | formatNotification() {
17 | if(this.fromSelf){
18 | return "您撤回了一条消息";
19 | }else {
20 | return webSocketCli.getDisplayName(this.operatorId) + "撤回了一条消息";
21 | }
22 | }
23 |
24 | encode() {
25 | let payload = super.encode();
26 | payload.content = this.operatorId;
27 | payload.binaryContent = StringUtils.utf8_to_b64(this.messageUid.toString());
28 | return payload;
29 | };
30 |
31 | decode(payload) {
32 | super.decode(payload);
33 | this.operatorId = payload.content;
34 | this.messageUid = StringUtils.b64_to_utf8(payload.binaryContent);
35 | }
36 | }
--------------------------------------------------------------------------------
/src/websocket/message/persistFlag.js:
--------------------------------------------------------------------------------
1 | export default class PersistFlag {
2 | static No_Persist = 0;
3 | static Persist = 1;
4 | static Persist_And_Count = 3;
5 | static Transparent = 4;
6 | }
--------------------------------------------------------------------------------
/src/websocket/message/protomessage.js:
--------------------------------------------------------------------------------
1 | import MessageStatus from "./messageStatus";
2 | import ConversationType from "../model/conversationType";
3 | import ProtoMessageContent from "./protomessageContent";
4 | import LocalStore from "../store/localstore";
5 |
6 | /**
7 | * 聊天信息content,在发送的时候转换为json消息体发送
8 | */
9 | export default class ProtoMessage {
10 | conversationType;
11 | target;
12 | line;
13 | from = '';
14 | content = {};
15 | messageId = '0';
16 | direction = 0;
17 | status = 0;
18 | messageUid = '0';
19 | timestamp = 0;
20 | tos = '';
21 |
22 |
23 | static toProtoMessage(obj){
24 | let currentUserId = LocalStore.getUserId();
25 | let protoMessage = new ProtoMessage();
26 | if(obj.from == currentUserId){
27 | protoMessage.direction = 0;
28 | protoMessage.status = MessageStatus.Sent;
29 | } else {
30 | protoMessage.direction = 1;
31 | protoMessage.status = MessageStatus.Unread;
32 | }
33 | protoMessage.tos = obj.tos;
34 | protoMessage.messageId = obj.messageId;
35 | protoMessage.messageUid = obj.messageId;
36 | protoMessage.timestamp = obj.timestamp;
37 | protoMessage.conversationType = obj.conversationType;
38 | protoMessage.target = obj.target;
39 | protoMessage.from = obj.from;
40 | if(protoMessage.conversationType == ConversationType.Single){
41 | if(obj.from != currentUserId){
42 | protoMessage.target = obj.from;
43 | protoMessage.from = obj.from;
44 | } else {
45 | protoMessage.target = obj.target;
46 | protoMessage.from = obj.from;
47 | }
48 | }
49 | protoMessage.line = obj.line;
50 | protoMessage.content = ProtoMessageContent.toProtoMessageContent(obj.content);
51 | return protoMessage;
52 | }
53 |
54 | /***
55 | * 转为即将发送的protomessage
56 | */
57 | static convertToProtoMessage(message){
58 | var protoMessage = new ProtoMessage();
59 | protoMessage.conversationType = message.conversation.type;
60 | protoMessage.target = message.conversation.target;
61 | protoMessage.line = message.conversation.line;
62 | protoMessage.from = message.from;
63 | protoMessage.tos = message.tos;
64 | protoMessage.direction = message.direction;
65 | protoMessage.status = message.status;
66 | protoMessage.messageId = message.messageId;
67 | protoMessage.messageUid = message.messageUid;
68 | protoMessage.timestamp = message.timestamp;
69 | console.log("protomessage content "+message.content)
70 | var payload = message.content.encode();
71 | protoMessage.content = ProtoMessageContent.toProtoMessageContent(payload);
72 | return protoMessage;
73 | }
74 | }
--------------------------------------------------------------------------------
/src/websocket/message/protomessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContentType from "./messageContentType";
2 |
3 | export default class ProtoMessageContent{
4 | type;
5 | searchableContent;
6 | pushContent;
7 | content;
8 | binaryContent; // base64 string, 图片时,不包含头部信息:data:image/png;base64,
9 | localContent;
10 | mediaType;
11 | remoteMediaUrl;
12 | localMediaPath;
13 | mentionedType = 0;
14 | mentionedTargets = [];
15 |
16 | static toProtoMessageContent(content){
17 | var protoMessageContent = new ProtoMessageContent();
18 | protoMessageContent.type = content.type;
19 | protoMessageContent.content = content.content;
20 | protoMessageContent.searchableContent = content.searchableContent;
21 | protoMessageContent.pushContent = content.pushContent;
22 | protoMessageContent.binaryContent = content.binaryContent;
23 | protoMessageContent.localContent = content.localContent;
24 | protoMessageContent.mediaType = content.mediaType;
25 | protoMessageContent.remoteMediaUrl = content.remoteMediaUrl;
26 | protoMessageContent.localMediaPath = content.localMediaPath;
27 | protoMessageContent.mentionedType = content.mentionedType;
28 | protoMessageContent.mentionedTargets = content.mentionedTargets;
29 | return protoMessageContent;
30 | }
31 |
32 | static typeToContent(messageContent){
33 | var showText = "您有新的消息";
34 | switch(messageContent.type){
35 | case MessageContentType.Image:
36 | showText = "[图片]";
37 | break;
38 | case MessageContentType.File:
39 | showText = "[文件]";
40 | break;
41 | case MessageContentType.Video:
42 | showText = "[视频]";
43 | break;
44 | case MessageContentType.VOIP_CONTENT_TYPE_START:
45 | showText = "[网络电话]";
46 | break;
47 | case MessageContentType.Tip_Notification:
48 | showText = '[通知消息]';
49 | }
50 | return showText;
51 | }
52 | }
--------------------------------------------------------------------------------
/src/websocket/message/sendMessage.js:
--------------------------------------------------------------------------------
1 | export default class SendMessage{
2 | target;
3 | messageContent;
4 | tos;
5 |
6 | constructor(target,messageContent,tos=''){
7 | this.target = target;
8 | this.messageContent = messageContent;
9 | this.tos = tos;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/websocket/message/textMessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContent from './messageContent'
2 | import MessagePayload from './messagePayload';
3 | import MessageContentType from './messageContentType';
4 |
5 | export default class TextMessageContent extends MessageContent {
6 | content;
7 |
8 | constructor(content, mentionedType = 0, mentionedTargets = []) {
9 | super(MessageContentType.Text, mentionedType, mentionedTargets);
10 | this.content = content;
11 | }
12 |
13 | digest() {
14 | return this.content;
15 | }
16 |
17 | encode() {
18 | let payload = super.encode();
19 | payload.searchableContent = this.content;
20 | return payload;
21 | };
22 |
23 | decode(payload) {
24 | super.decode(payload);
25 | this.content = payload.searchableContent;
26 | }
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/src/websocket/message/unknownMessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContent from "./messageContent";
2 | import MessageContentType from "./messageContentType";
3 |
4 | export default class UnknownMessageContent extends MessageContent {
5 | originalPayload;
6 |
7 | constructor(originalPayload) {
8 | super(MessageContentType.Unknown);
9 | this.originalPayload = originalPayload;
10 | }
11 |
12 | encode() {
13 | return this.originalPayload;
14 | }
15 |
16 | decode(paylaod) {
17 | this.originalPayload = paylaod;
18 | }
19 |
20 | digest() {
21 | return '未知类型消息';
22 | }
23 | }
--------------------------------------------------------------------------------
/src/websocket/message/unsupportMessageContent.js:
--------------------------------------------------------------------------------
1 | import MessageContent from "./messageContent";
2 |
3 | export default class UnsupportMessageContent extends MessageContent {
4 |
5 | digest() {
6 | return '尚不支持该类型消息, 请手机查看 : ' + this.type;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/websocket/message/videoMessageContent.js:
--------------------------------------------------------------------------------
1 | import MediaMessageContent from './mediaMessageContent'
2 | import MessageContentMediaType from './messageContentMediaType';
3 | import MessageContentType from './messageContentType';
4 |
5 | export default class VideoMessageContent extends MediaMessageContent {
6 | // base64 encoded
7 | thumbnail;
8 | constructor(fileOrLocalPath, remotePath, thumbnail) {
9 | super(MessageContentType.Video, MessageContentMediaType.Video, fileOrLocalPath, remotePath);
10 | this.thumbnail = thumbnail;
11 | }
12 |
13 | digest() {
14 | return '[视频]';
15 | }
16 |
17 | encode() {
18 | let payload = super.encode();
19 | payload.binaryContent = this.thumbnail;
20 | payload.mediaType = MessageContentMediaType.Video;
21 | return payload;
22 | };
23 |
24 | decode(payload) {
25 | super.decode(payload);
26 | this.thumbnail = payload.binaryContent;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/websocket/message/websocketprotomessage.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * websocket json 主协议
4 | *
5 | * {
6 | "signal": "connect",
7 | "sub_signal": "conect_ack",
8 | "message_id": 0,
9 | "content": ""
10 | }
11 | */
12 | export class WebSocketProtoMessage {
13 | signal;
14 | subSignal;
15 |
16 | constructor(){
17 | console.log('constuctor WebSocketProtoMessage');
18 | }
19 |
20 | setMessageId(messageId){
21 | this.messageId = messageId;
22 | }
23 |
24 | setSignal(signal){
25 | this.signal = signal;
26 | }
27 |
28 | setSubSignal(subSignal){
29 | this.subSignal = subSignal;
30 | }
31 |
32 | setContent(content){
33 | this.content = content;
34 | }
35 |
36 | toJson(){
37 | let message = {
38 | signal : this.signal,
39 | subSignal : this.subSignal == null ? 'NONE' : this.subSignal,
40 | messageId : this.messageId == null ? 0 : this.messageId,
41 | content : this.content
42 | }
43 |
44 | return JSON.stringify(message);
45 | }
46 | }
--------------------------------------------------------------------------------
/src/websocket/model/conversation.js:
--------------------------------------------------------------------------------
1 | import ConversationType from "./conversationType";
2 |
3 | /**
4 | *
5 | "conversation":{
6 | "conversationType": 0,
7 | "target": "UZUWUWuu",
8 | "line": 0,
9 | }
10 | */
11 | export default class Conversation {
12 | type = ConversationType.Single;
13 | conversationType = this.type; // 这行是为了做一个兼容处理
14 | target = '';
15 | line = 0;
16 |
17 | constructor(type, target, line) {
18 | this.type = type;
19 | this.conversationType = type;
20 | this.target = target;
21 | this.line = line;
22 | }
23 |
24 | equal(conversation) {
25 | return this.type === conversation.type
26 | && this.target === conversation.target
27 | && this.line === conversation.line;
28 | }
29 | }
--------------------------------------------------------------------------------
/src/websocket/model/conversationInfo.js:
--------------------------------------------------------------------------------
1 | export default class ConversationInfo{
2 | conversation = {};
3 | lastMessage = {};
4 | timestamp = 0;
5 | draft = '';
6 | unreadCount = {};
7 | isTop = false;
8 | isSilent = false;
9 |
10 | }
--------------------------------------------------------------------------------
/src/websocket/model/conversationType.js:
--------------------------------------------------------------------------------
1 | export default class ConversationType {
2 | static Single = 0;
3 | static Group = 1;
4 | static ChatRoom = 2;
5 | static Channel = 3;
6 | }
--------------------------------------------------------------------------------
/src/websocket/model/groupInfo.js:
--------------------------------------------------------------------------------
1 | import GroupType from './groupType'
2 | export default class GroupInfo {
3 | target = '';
4 | name = '';
5 | portrait = '';
6 | owner = '';
7 | type = GroupType.Normal;
8 | memberCount = 0;
9 | extra = '';
10 | updateDt = 0;
11 |
12 | static convert2GroupInfo(jsonObj){
13 | var groupInfo = new GroupInfo();
14 | groupInfo.target = jsonObj.target;
15 | groupInfo.name = jsonObj.name;
16 | groupInfo.portrait = jsonObj.portrait;
17 | groupInfo.owner = jsonObj.owner;
18 | groupInfo.type = jsonObj.type;
19 | groupInfo.memberCount = jsonObj.memberCount;
20 | groupInfo.extra = jsonObj.extra;
21 | groupInfo.updateDt = jsonObj.updateDt;
22 | return groupInfo;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/websocket/model/groupMember.js:
--------------------------------------------------------------------------------
1 | export default class GroupMember {
2 | groupId = '';
3 | memberId = '';
4 | alias = '';
5 | type = 0;
6 | updateDt = 0;
7 | diplayName = '';
8 | avatarUrl = '';
9 |
10 | static convert2GroupMember(jsonObj){
11 | var groupMember = new GroupMember();
12 | groupMember.memberId = jsonObj.memberId;
13 | groupMember.alias = jsonObj.alias;
14 | groupMember.type = jsonObj.type;
15 | groupMember.updateDt = jsonObj.updateDt;
16 | return groupMember;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/websocket/model/groupMemberType.js:
--------------------------------------------------------------------------------
1 | export default class GroupMemberType {
2 | static Normal = 0;
3 | static Manager = 1;
4 | static Owner = 2;
5 | }
--------------------------------------------------------------------------------
/src/websocket/model/groupType.js:
--------------------------------------------------------------------------------
1 | export default class GroupType {
2 | static Normal = 0;
3 | static Free = 1;
4 | static Restricted = 2;
5 | }
--------------------------------------------------------------------------------
/src/websocket/model/protoConversationInfo.js:
--------------------------------------------------------------------------------
1 | import UnreadCount from "./unReadCount";
2 |
3 | export default class ProtoConversationInfo{
4 | conversationType;
5 | target;
6 | line;
7 | lastMessage = {};
8 | timestamp = 0;
9 | draft = '';
10 | unreadCount = new UnreadCount();
11 | isTop = false;
12 | isSilent = false;
13 | }
--------------------------------------------------------------------------------
/src/websocket/model/stateConversationInfo.js:
--------------------------------------------------------------------------------
1 | export default class StateConversationInfo{
2 | name;
3 | img;
4 | //ProtoConversationInfo
5 | conversationInfo = {};
6 | }
--------------------------------------------------------------------------------
/src/websocket/model/stateSelectChatMessage.js:
--------------------------------------------------------------------------------
1 | export default class StateSelectChateMessage{
2 | name = '';
3 | target;
4 | protoMessages = [];
5 | }
--------------------------------------------------------------------------------
/src/websocket/model/unReadCount.js:
--------------------------------------------------------------------------------
1 | export default class UnreadCount {
2 | // 单聊未读数
3 | unread = 0;
4 | // 群聊@数
5 | unreadMention = 0;
6 | // 群聊@All数
7 | unreadMentionAll = 0;
8 | }
--------------------------------------------------------------------------------
/src/websocket/model/userInfo.js:
--------------------------------------------------------------------------------
1 | export default class UserInfo {
2 | uid = '';
3 | name = '';
4 | displayName = '';
5 | gender = 0;
6 | portrait = '';
7 | mobile = '';
8 | email = '';
9 | address = '';
10 | social = '';
11 | extra = '';
12 | type = 0; //0 normal; 1 robot; 2 thing;
13 | updateDt = 1550652404513;
14 |
15 | static convert2UserInfo(jsonObj){
16 | let userInfo = new UserInfo();
17 | userInfo.uid = jsonObj.uid;
18 | userInfo.name = jsonObj.name;
19 | userInfo.displayName = jsonObj.displayName;
20 | userInfo.gender = jsonObj.gender;
21 | userInfo.portrait = jsonObj.portrait;
22 | userInfo.mobile = jsonObj.mobile;
23 | userInfo.email = jsonObj.email;
24 | userInfo.address = jsonObj.address;
25 | userInfo.social = jsonObj.social;
26 | userInfo.extra = jsonObj.extra;
27 | userInfo.type = jsonObj.type;
28 | userInfo.updateDt = jsonObj.updateDt;
29 | return userInfo;
30 | }
31 | }
--------------------------------------------------------------------------------
/src/websocket/store/localstore.js:
--------------------------------------------------------------------------------
1 | import { KEY_VUE_USER_ID } from "../../constant";
2 |
3 | /**
4 | * 本地缓存,包括如下基本信息
5 | * 消息缓存
6 | * 消息当前序列号,用于消息拉取
7 | * 朋友列表信息
8 | * 群组信息
9 | */
10 | export default class LocalStore {
11 |
12 | static saveConverSations(value){
13 | localStorage.setItem("coversations",JSON.stringify(value));
14 | }
15 |
16 | static getConversations(){
17 | let vaule = localStorage.getItem("coversations");
18 | return JSON.parse(vaule);
19 | }
20 |
21 | static getLastMessageSeq(){
22 | return localStorage.getItem("last_message_seq");
23 | }
24 |
25 | //设置上传token,根据不同media类型存储
26 | static setUploadToken(key,token){
27 | localStorage.setItem(key,token);
28 | }
29 |
30 | static getImageUploadToken(){
31 | return localStorage.getItem("http://image.comsince.cn/");
32 | }
33 |
34 | /**
35 | * messageSeq为string类型
36 | */
37 | static setLastMessageSeq(messageSeq){
38 | localStorage.setItem("last_message_seq",messageSeq);
39 | }
40 |
41 | static saveMessages(value){
42 | localStorage.setItem("messages",JSON.stringify(value));
43 | }
44 |
45 | static getMessages(){
46 | let value = localStorage.getItem("messages");
47 | return JSON.parse(value);
48 | }
49 |
50 | static saveUserInfoList(value){
51 | localStorage.setItem("user_infos_list",JSON.stringify(value));
52 | }
53 |
54 | static getUserInfoList(){
55 | let value = localStorage.getItem("user_infos_list");
56 | return JSON.parse(value);
57 | }
58 |
59 | /**
60 | * 记录消息发送条数主要时为了
61 | */
62 | static updateSendMessageCount(){
63 | let value = localStorage.getItem("send_message_count");
64 | if(value){
65 | value = parseInt(value) + 1;
66 | } else {
67 | value = 1;
68 | }
69 | localStorage.setItem("send_message_count",value);
70 | }
71 |
72 | static getSendMessageCount(){
73 | let value = localStorage.getItem("send_message_count");
74 | if(!value){
75 | value = 0;
76 | }
77 | return value;
78 | }
79 |
80 | static resetSendMessageCount(){
81 | localStorage.setItem("send_message_count",0);
82 | }
83 |
84 | static getUserId(){
85 | return localStorage.getItem(KEY_VUE_USER_ID);
86 | }
87 |
88 | static setSelectTarget(value){
89 | localStorage.setItem("select_target",value);
90 | }
91 |
92 | static getSelectTarget(){
93 | return localStorage.getItem("select_target");
94 | }
95 |
96 | static setFriendRequestVersion(version){
97 | localStorage.setItem("friend_request_version",version);
98 | }
99 |
100 | static getFriendRequestVersion(){
101 | var version = localStorage.getItem("friend_request_version");
102 | if(!version){
103 | version = 0
104 | }
105 | return version;
106 | }
107 |
108 | static saveMessageId(messageId){
109 | localStorage.setItem("message_id",messageId);
110 | }
111 |
112 | static getMessageId(){
113 | var messageId = localStorage.getItem("message_id");
114 | if(!messageId){
115 | messageId = 0;
116 | }
117 | return messageId;
118 | }
119 |
120 | static clearLocalStore(){
121 | localStorage.setItem("coversations","");
122 | localStorage.setItem("last_message_seq","");
123 | localStorage.setItem("messages","");
124 | localStorage.setItem("send_message_count",0);
125 | localStorage.setItem("select_target","");
126 | localStorage.setItem("friend_request_version",0);
127 | localStorage.setItem(KEY_VUE_USER_ID,'');
128 | localStorage.setItem("message_id",0);
129 | }
130 | }
--------------------------------------------------------------------------------
/src/websocket/utils/StringUtil.js:
--------------------------------------------------------------------------------
1 | export default class StringUtils {
2 | static b64_to_utf8(str) {
3 | return decodeURIComponent(escape(atob(str)));
4 | }
5 |
6 |
7 | static utf8_to_b64(str) {
8 | return btoa(unescape(encodeURIComponent(str)));
9 | }
10 | }
--------------------------------------------------------------------------------
/src/websocket/utils/aes.js:
--------------------------------------------------------------------------------
1 |
2 | import CryptoJS from 'crypto-js'
3 | const iv = CryptoJS.enc.Base64.parse('ABEiM0RVZnd4eXp7fH1+fw==');
4 |
5 | export function decrypt (text) {
6 | let decrypted = CryptoJS.AES.decrypt(text, iv, {
7 | iv: iv,
8 | mode: CryptoJS.mode.CBC,
9 | padding: CryptoJS.pad.Pkcs7
10 | })
11 | //由于服务端加密的时候前四个字节时间参数,现在暂时忽略前4字节
12 | var resultWordArr = CryptoJS.enc.Hex.parse(decrypted.toString().slice(8));
13 | let result = resultWordArr.toString(CryptoJS.enc.Utf8);
14 | // let rmtimeResult = result.slice(4);
15 | return result;
16 | }
17 |
18 |
19 | export function encrypt(encryptCode,key) {
20 | console.log('code '+encryptCode+' key '+key);
21 | let keyArry = new Array(16);
22 | for(let i = 0; i<16; i++){
23 | //这里是至打印字符编码
24 | keyArry[i] = key.charCodeAt(i) & 0xFF;
25 | }
26 | let keyCode = String.fromCharCode.apply(String, keyArry);
27 | let secretCode = CryptoJS.enc.Utf8.parse(keyCode);
28 | console.log('keycode '+keyCode);
29 |
30 | let timeEncryptCode = convertTimeEncryptCode(encryptCode);
31 | console.log('timeEncryptCode '+timeEncryptCode);
32 |
33 | //pwd 必须base64解码
34 |
35 | let encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Base64.parse(timeEncryptCode), secretCode, {
36 | iv: secretCode,
37 | mode: CryptoJS.mode.CBC,
38 | padding: CryptoJS.pad.Pkcs7
39 | });
40 | return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
41 | }
42 |
43 | function convertTimeEncryptCode(encryptCode){
44 |
45 | // let encryptArr = string2Bin(encryptCode);
46 | let encryptArr = CryptoJS.enc.Base64.parse(encryptCode);
47 |
48 | encryptArr = wordToByteArray(encryptArr.words);
49 | // encryptArr = string2Bin(encryptArr);
50 | let result = [];
51 | let curhour = (new Date().getMilliseconds()/1000 - 1514736000)/3600;
52 | for(var i=0; i < encryptArr.length + 4; i++){
53 | if(i < 4){
54 | if(i == 0){
55 | result.push(curhour & 0xFF);
56 | } else if(i == 1){
57 | result.push((curhour & 0xFF00) >> 8);
58 | } else if(i == 2){
59 | result.push((curhour & 0xFF0000) >> 16);
60 | } else if(i == 3){
61 | result.push((curhour & 0xFF) >> 24);
62 | }
63 | } else{
64 | result.push(encryptArr[i - 4]);
65 | }
66 | }
67 | console.log('convertTimeEncryptCode result '+result);
68 |
69 | // https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string
70 | var base64wordarr = btoa(String.fromCharCode(...new Uint8Array(result)));
71 | console.log("hash word "+base64wordarr);
72 |
73 | return base64wordarr;
74 | }
75 |
76 | function bin2String(array) {
77 | var result = "";
78 | for (var i = 0; i < array.length; i++) {
79 | result += String.fromCharCode(parseInt(array[i], 2));
80 | }
81 | return result;
82 | }
83 |
84 | function string2Bin(str) {
85 | var result = [];
86 | for (var i = 0; i < str.length; i++) {
87 | result.push(str.charCodeAt(i).toString(2));
88 | }
89 | return result;
90 | }
91 |
92 | function wordToByteArray(wordArray) {
93 | var byteArray = [], word, i, j;
94 | for (i = 0; i < wordArray.length; ++i) {
95 | word = wordArray[i];
96 | for (j = 3; j >= 0; --j) {
97 | byteArray.push((word >> 8 * j) & 0xFF);
98 | }
99 | }
100 | return byteArray;
101 | }
102 |
103 | function byteArrayToString(byteArray) {
104 | var str = "", i;
105 | for (i = 0; i < byteArray.length; ++i) {
106 | str += escape(String.fromCharCode(byteArray[i]));
107 | }
108 | return str;
109 | }
110 |
--------------------------------------------------------------------------------
/src/websocket/utils/logger.js:
--------------------------------------------------------------------------------
1 | export default class Logger {
2 | static log(text){
3 | var time = new Date();
4 | console.log("[" + time.toLocaleTimeString() + "] " + text);
5 | }
6 | }
--------------------------------------------------------------------------------
/src/websocket/utils/timeUtils.js:
--------------------------------------------------------------------------------
1 | export default class TimeUtils {
2 | //参考链接: https://juejin.im/entry/5c7103f5f265da2dab17d1a6
3 | static _formatDate(date, fmt){
4 | var o = {
5 | "M+": date.getMonth() + 1, //月份
6 | "d+": date.getDate(), //日
7 | "h+": date.getHours(), //小时
8 | "m+": date.getMinutes(), //分
9 | "s+": date.getSeconds(), //秒
10 | "q+": Math.floor((date.getMonth() + 3) / 3), //季度
11 | "S": date.getMilliseconds() //毫秒
12 | };
13 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
14 | for (var k in o)
15 | if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
16 | return fmt;
17 | }
18 |
19 | static getTimeStringAutoShort2(timestamp,mustIncludeTime){
20 |
21 | // 当前时间
22 | var currentDate = new Date();
23 | // 目标判断时间
24 | var srcDate = new Date(parseInt(timestamp));
25 |
26 | var currentYear = currentDate.getFullYear();
27 | var currentMonth = (currentDate.getMonth()+1);
28 | var currentDateD = currentDate.getDate();
29 |
30 | var srcYear = srcDate.getFullYear();
31 | var srcMonth = (srcDate.getMonth()+1);
32 | var srcDateD = srcDate.getDate();
33 |
34 | var ret = "";
35 |
36 | // 要额外显示的时间分钟
37 | var timeExtraStr = (mustIncludeTime?" "+this._formatDate(srcDate, "hh:mm"):"");
38 |
39 | // 当年
40 | if(currentYear == srcYear) {
41 | var currentTimestamp = currentDate.getTime();
42 | var srcTimestamp = timestamp;
43 | // 相差时间(单位:毫秒)
44 | var deltaTime = (currentTimestamp-srcTimestamp);
45 |
46 | // 当天(月份和日期一致才是)
47 | if(currentMonth == srcMonth && currentDateD == srcDateD) {
48 | // 时间相差60秒以内
49 | if(deltaTime < 60 * 1000)
50 | ret = "刚刚";
51 | // 否则当天其它时间段的,直接显示“时:分”的形式
52 | else
53 | ret = this._formatDate(srcDate, "hh:mm");
54 | }
55 | // 当年 && 当天之外的时间(即昨天及以前的时间)
56 | else {
57 | // 昨天(以“现在”的时候为基准-1天)
58 | var yesterdayDate = new Date();
59 | yesterdayDate.setDate(yesterdayDate.getDate()-1);
60 |
61 | // 前天(以“现在”的时候为基准-2天)
62 | var beforeYesterdayDate = new Date();
63 | beforeYesterdayDate.setDate(beforeYesterdayDate.getDate()-2);
64 |
65 | // 用目标日期的“月”和“天”跟上方计算出来的“昨天”进行比较,是最为准确的(如果用时间戳差值
66 | // 的形式,是不准确的,比如:现在时刻是2019年02月22日1:00、而srcDate是2019年02月21日23:00,
67 | // 这两者间只相差2小时,直接用“deltaTime/(3600 * 1000)” > 24小时来判断是否昨天,就完全是扯蛋的逻辑了)
68 | if(srcMonth == (yesterdayDate.getMonth()+1) && srcDateD == yesterdayDate.getDate())
69 | ret = "昨天"+timeExtraStr;// -1d
70 | // “前天”判断逻辑同上
71 | else if(srcMonth == (beforeYesterdayDate.getMonth()+1) && srcDateD == beforeYesterdayDate.getDate())
72 | ret = "前天"+timeExtraStr;// -2d
73 | else{
74 | // 跟当前时间相差的小时数
75 | var deltaHour = (deltaTime/(3600 * 1000));
76 |
77 | // 如果小于或等 7*24小时就显示星期几
78 | if (deltaHour <= 7*24){
79 | var weekday=new Array(7);
80 | weekday[0]="星期日";
81 | weekday[1]="星期一";
82 | weekday[2]="星期二";
83 | weekday[3]="星期三";
84 | weekday[4]="星期四";
85 | weekday[5]="星期五";
86 | weekday[6]="星期六";
87 |
88 | // 取出当前是星期几
89 | var weedayDesc = weekday[srcDate.getDay()];
90 | ret = weedayDesc+timeExtraStr;
91 | }
92 | // 否则直接显示完整日期时间
93 | else
94 | ret = this._formatDate(srcDate, "yy/M/d")+timeExtraStr;
95 | }
96 | }
97 | }
98 | // 往年
99 | else{
100 | ret = this._formatDate(srcDate, "yy/M/d")+timeExtraStr;
101 | }
102 |
103 | return ret;
104 | }
105 | }
--------------------------------------------------------------------------------
/src/websocket/websocketcli.js:
--------------------------------------------------------------------------------
1 | import Logger from "./utils/logger";
2 | import vuexStore from '../store'
3 |
4 |
5 | export class WebSocketClient {
6 |
7 | getDisplayName(userId){
8 | var userInfolist = vuexStore.state.userInfoList;
9 | var userInfo = userInfolist.find(user => user.uid == userId);
10 | var displayName = userId;
11 | var friendData = vuexStore.state.friendDatas.find(friend => friend.friendUid == userId)
12 | if(friendData && friendData.alias && friendData.alias != ""){
13 | displayName = friendData.alias
14 | } else if(userInfo){
15 | displayName = userInfo.displayName;
16 | if(displayName == ''){
17 | displayName = userInfo.mobile;
18 | }
19 | } else {
20 | vuexStore.state.vueSocket.getUserInfo(userId);
21 | }
22 | // console.log("userId "+userId +" displayName "+displayName)
23 | return displayName;
24 | }
25 |
26 | getPortrait(userId){
27 | var userInfolist = vuexStore.state.userInfoList;
28 | var userInfo = userInfolist.find(user => user.uid == userId);
29 | var portrait = 'static/images/vue.jpg'
30 | if(userInfo){
31 | portrait = userInfo.portrait;
32 | }
33 | return portrait;
34 | }
35 |
36 |
37 | createGroup(groupName,memberIds){
38 | return vuexStore.state.vueSocket.createGroup(groupName,memberIds);
39 | }
40 |
41 | modifyGroupInfo(info){
42 | return vuexStore.state.vueSocket.modifyGroupInfo(info);
43 | }
44 |
45 | quitGroup(groupId){
46 | return vuexStore.state.vueSocket.quitGroup(groupId);
47 | }
48 |
49 | dismissGroup(groupId){
50 | return vuexStore.state.vueSocket.dismissGroup(groupId);
51 | }
52 |
53 | getGroupMember(groupId){
54 | return vuexStore.state.vueSocket.getGroupMember(groupId);
55 | }
56 |
57 | addMembers(groupId, memberIds){
58 | return vuexStore.state.vueSocket.addMembers(groupId,memberIds);
59 | }
60 |
61 | kickeMembers(groupId,memberIds){
62 | return vuexStore.state.vueSocket.kickeMembers(groupId,memberIds);
63 | }
64 |
65 | recallMessage(messageUid){
66 | return vuexStore.state.vueSocket.recallMessage(messageUid);
67 | }
68 |
69 | getMinioUploadUrl(mediaType,key){
70 | return vuexStore.state.vueSocket.getMinioUploadUrl(mediaType,key);
71 | }
72 |
73 | modifyFriendAlias(targetUid,alias){
74 | return vuexStore.state.vueSocket.modifyFriendAlias(targetUid,alias)
75 | }
76 |
77 | //注意beforeUid最好为string类型
78 | getRemoteMessages(conversation,beforeUid,count){
79 | return vuexStore.state.vueSocket.getRemoteMessages(conversation,beforeUid,count)
80 | }
81 |
82 | }
83 |
84 | const self = new WebSocketClient();
85 | export default self;
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/.gitkeep
--------------------------------------------------------------------------------
/static/audio/incoming_call_ring.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/audio/incoming_call_ring.mp3
--------------------------------------------------------------------------------
/static/audio/notify.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/audio/notify.mp3
--------------------------------------------------------------------------------
/static/audio/outgoing_call_ring.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/audio/outgoing_call_ring.mp3
--------------------------------------------------------------------------------
/static/css/reset.css:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video, input {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | font-size: 100%;
18 | font-weight: normal;
19 | vertical-align: baseline;
20 | }
21 |
22 | article, aside, details, figcaption, figure,
23 | footer, header, menu, nav, section {
24 | display: block;
25 | }
26 |
27 | body {
28 | line-height: 1;
29 | overflow: hidden;
30 | }
31 |
32 | blockquote, q {
33 | quotes: none;
34 | }
35 |
36 | blockquote:before, blockquote:after,
37 | q:before, q:after {
38 | content: none;
39 | }
40 |
41 | table {
42 | border-collapse: collapse;
43 | border-spacing: 0;
44 | }
45 |
46 | a {
47 | color: #7e8c8d;
48 | text-decoration: none;
49 | -webkit-backface-visibility: hidden;
50 | }
51 |
52 | li {
53 | list-style: none;
54 | }
55 |
56 |
57 | html, body {
58 | width: 100%;
59 | height: 100%;
60 | }
61 | body {
62 | -webkit-text-size-adjust: none;
63 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
64 | background: #466368;
65 | background: -webkit-linear-gradient(#648880, #293f50);
66 | background: -moz-linear-gradient(#648880, #293f50);
67 | background: linear-gradient(#648880, #293f50);
68 | background-size: cover;
69 | }
70 |
71 | /*在mac上,增加此设置,会导致滚动条不能自动显示与隐藏,滚动条会一直显示 */
72 | ::-webkit-scrollbar {
73 | width: 8px;
74 | }
75 |
76 | /* 滚动条滑块 */
77 | ::-webkit-scrollbar-thumb {
78 | border-radius: 6px;
79 | background: rgba(0,0,0,0.1);
80 | }
81 |
82 | /* video {
83 | background: #222;
84 | margin: 0 0 20px 0;
85 | --width: 100%;
86 | width: var(--width);
87 | height: var(--width);
88 | } */
89 |
90 |
91 | *,*:before,*:after {
92 | -moz-box-sizing: border-box;
93 | -webkit-box-sizing: border-box;
94 | box-sizing: border-box
95 | }
96 |
97 | .flexbox{display:-webkit-box; display:-webkit-flex; display:flex; display:-ms-flexbox;}
98 | .flex-alignc{align-items: center;}
99 | .flex1{-webkit-box-flex:1; -webkit-flex:1; -ms-flex:1; flex:1;}
--------------------------------------------------------------------------------
/static/emoji/100.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/100.gif
--------------------------------------------------------------------------------
/static/emoji/101.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/101.gif
--------------------------------------------------------------------------------
/static/emoji/102.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/102.gif
--------------------------------------------------------------------------------
/static/emoji/103.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/103.gif
--------------------------------------------------------------------------------
/static/emoji/104.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/104.gif
--------------------------------------------------------------------------------
/static/emoji/105.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/105.gif
--------------------------------------------------------------------------------
/static/emoji/106.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/106.gif
--------------------------------------------------------------------------------
/static/emoji/107.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/107.gif
--------------------------------------------------------------------------------
/static/emoji/108.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/108.gif
--------------------------------------------------------------------------------
/static/emoji/109.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/109.gif
--------------------------------------------------------------------------------
/static/emoji/110.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/110.gif
--------------------------------------------------------------------------------
/static/emoji/111.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/111.gif
--------------------------------------------------------------------------------
/static/emoji/112.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/112.gif
--------------------------------------------------------------------------------
/static/emoji/113.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/113.gif
--------------------------------------------------------------------------------
/static/emoji/114.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/114.gif
--------------------------------------------------------------------------------
/static/emoji/115.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/115.gif
--------------------------------------------------------------------------------
/static/emoji/116.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/116.gif
--------------------------------------------------------------------------------
/static/emoji/117.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/117.gif
--------------------------------------------------------------------------------
/static/emoji/118.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/118.gif
--------------------------------------------------------------------------------
/static/emoji/119.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/119.gif
--------------------------------------------------------------------------------
/static/emoji/120.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/120.gif
--------------------------------------------------------------------------------
/static/emoji/121.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/121.gif
--------------------------------------------------------------------------------
/static/emoji/122.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/122.gif
--------------------------------------------------------------------------------
/static/emoji/123.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/123.gif
--------------------------------------------------------------------------------
/static/emoji/124.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/124.gif
--------------------------------------------------------------------------------
/static/emoji/125.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/125.gif
--------------------------------------------------------------------------------
/static/emoji/126.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/126.gif
--------------------------------------------------------------------------------
/static/emoji/127.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/127.gif
--------------------------------------------------------------------------------
/static/emoji/128.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/128.gif
--------------------------------------------------------------------------------
/static/emoji/129.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/129.gif
--------------------------------------------------------------------------------
/static/emoji/130.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/130.gif
--------------------------------------------------------------------------------
/static/emoji/131.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/131.gif
--------------------------------------------------------------------------------
/static/emoji/132.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/132.gif
--------------------------------------------------------------------------------
/static/emoji/133.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/133.gif
--------------------------------------------------------------------------------
/static/emoji/134.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/134.gif
--------------------------------------------------------------------------------
/static/emoji/135.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/135.gif
--------------------------------------------------------------------------------
/static/emoji/136.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/136.gif
--------------------------------------------------------------------------------
/static/emoji/137.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/137.gif
--------------------------------------------------------------------------------
/static/emoji/138.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/138.gif
--------------------------------------------------------------------------------
/static/emoji/139.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/139.gif
--------------------------------------------------------------------------------
/static/emoji/140.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/140.gif
--------------------------------------------------------------------------------
/static/emoji/141.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/141.gif
--------------------------------------------------------------------------------
/static/emoji/142.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/142.gif
--------------------------------------------------------------------------------
/static/emoji/143.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/143.gif
--------------------------------------------------------------------------------
/static/emoji/144.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/144.gif
--------------------------------------------------------------------------------
/static/emoji/145.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/145.gif
--------------------------------------------------------------------------------
/static/emoji/146.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/146.gif
--------------------------------------------------------------------------------
/static/emoji/147.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/147.gif
--------------------------------------------------------------------------------
/static/emoji/148.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/148.gif
--------------------------------------------------------------------------------
/static/emoji/149.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/149.gif
--------------------------------------------------------------------------------
/static/emoji/150.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/150.gif
--------------------------------------------------------------------------------
/static/emoji/151.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/151.gif
--------------------------------------------------------------------------------
/static/emoji/152.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/152.gif
--------------------------------------------------------------------------------
/static/emoji/153.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/153.gif
--------------------------------------------------------------------------------
/static/emoji/154.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/154.gif
--------------------------------------------------------------------------------
/static/emoji/155.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/155.gif
--------------------------------------------------------------------------------
/static/emoji/156.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/156.gif
--------------------------------------------------------------------------------
/static/emoji/157.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/157.gif
--------------------------------------------------------------------------------
/static/emoji/158.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/158.gif
--------------------------------------------------------------------------------
/static/emoji/159.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/159.gif
--------------------------------------------------------------------------------
/static/emoji/160.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/160.gif
--------------------------------------------------------------------------------
/static/emoji/161.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/161.gif
--------------------------------------------------------------------------------
/static/emoji/162.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/162.gif
--------------------------------------------------------------------------------
/static/emoji/163.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/163.gif
--------------------------------------------------------------------------------
/static/emoji/164.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/164.gif
--------------------------------------------------------------------------------
/static/emoji/165.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/165.gif
--------------------------------------------------------------------------------
/static/emoji/166.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/166.gif
--------------------------------------------------------------------------------
/static/emoji/167.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/167.gif
--------------------------------------------------------------------------------
/static/emoji/168.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/168.gif
--------------------------------------------------------------------------------
/static/emoji/169.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/169.gif
--------------------------------------------------------------------------------
/static/emoji/170.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/170.gif
--------------------------------------------------------------------------------
/static/emoji/171.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/171.gif
--------------------------------------------------------------------------------
/static/emoji/172.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/172.gif
--------------------------------------------------------------------------------
/static/emoji/173.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/173.gif
--------------------------------------------------------------------------------
/static/emoji/174.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/174.gif
--------------------------------------------------------------------------------
/static/emoji/175.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/175.gif
--------------------------------------------------------------------------------
/static/emoji/176.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/176.gif
--------------------------------------------------------------------------------
/static/emoji/177.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/177.gif
--------------------------------------------------------------------------------
/static/emoji/178.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/178.gif
--------------------------------------------------------------------------------
/static/emoji/179.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/179.gif
--------------------------------------------------------------------------------
/static/emoji/180.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/180.gif
--------------------------------------------------------------------------------
/static/emoji/181.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/181.gif
--------------------------------------------------------------------------------
/static/emoji/182.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/182.gif
--------------------------------------------------------------------------------
/static/emoji/183.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/183.gif
--------------------------------------------------------------------------------
/static/emoji/184.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/184.gif
--------------------------------------------------------------------------------
/static/emoji/185.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/185.gif
--------------------------------------------------------------------------------
/static/emoji/186.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/186.gif
--------------------------------------------------------------------------------
/static/emoji/187.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/187.gif
--------------------------------------------------------------------------------
/static/emoji/188.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/188.gif
--------------------------------------------------------------------------------
/static/emoji/189.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/189.gif
--------------------------------------------------------------------------------
/static/emoji/190.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/190.gif
--------------------------------------------------------------------------------
/static/emoji/191.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/191.gif
--------------------------------------------------------------------------------
/static/emoji/192.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/192.gif
--------------------------------------------------------------------------------
/static/emoji/193.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/193.gif
--------------------------------------------------------------------------------
/static/emoji/194.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/194.gif
--------------------------------------------------------------------------------
/static/emoji/195.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/195.gif
--------------------------------------------------------------------------------
/static/emoji/196.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/196.gif
--------------------------------------------------------------------------------
/static/emoji/197.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/197.gif
--------------------------------------------------------------------------------
/static/emoji/198.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/198.gif
--------------------------------------------------------------------------------
/static/emoji/199.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/199.gif
--------------------------------------------------------------------------------
/static/emoji/meinv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/meinv.png
--------------------------------------------------------------------------------
/static/emoji/shangxin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/shangxin.png
--------------------------------------------------------------------------------
/static/emoji/weixiao.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/emoji/weixiao.png
--------------------------------------------------------------------------------
/static/images/Guai.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/Guai.jpg
--------------------------------------------------------------------------------
/static/images/UserAvatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/UserAvatar.jpg
--------------------------------------------------------------------------------
/static/images/father.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/father.jpg
--------------------------------------------------------------------------------
/static/images/icon__attachment-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/icon__attachment-white.png
--------------------------------------------------------------------------------
/static/images/icon__download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/icon__download.png
--------------------------------------------------------------------------------
/static/images/microzz.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/microzz.jpg
--------------------------------------------------------------------------------
/static/images/mother.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/mother.jpg
--------------------------------------------------------------------------------
/static/images/newfriend.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/newfriend.jpg
--------------------------------------------------------------------------------
/static/images/orange.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/orange.jpg
--------------------------------------------------------------------------------
/static/images/vue.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/vue.jpg
--------------------------------------------------------------------------------
/static/images/加菲猫.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/加菲猫.jpg
--------------------------------------------------------------------------------
/static/images/大飞哥.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/大飞哥.jpg
--------------------------------------------------------------------------------
/static/images/小姨妈.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/小姨妈.jpg
--------------------------------------------------------------------------------
/static/images/悟空.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/悟空.jpg
--------------------------------------------------------------------------------
/static/images/新之助.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/新之助.jpg
--------------------------------------------------------------------------------
/static/images/萌萌俊.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fsharechat/vue-chat/948ffd3a4082f38fc521c26f06289d6c97fdedd1/static/images/萌萌俊.jpg
--------------------------------------------------------------------------------