├── .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 | [![star](https://gitee.com/comsince/vue-chat/badge/star.svg?theme=white)](https://gitee.com/comsince/vue-chat) 3 | [![GitHub stars](https://img.shields.io/github/stars/comsince/vue-chat?style=social)](https://github.com/comsince/vue-chat) 4 | 5 | # 飞享 6 | 7 | ![image](http://image.comsince.cn/fx-chat.png) 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 | ![image](./attachment/vue-chat-unread.png) 18 | 19 | * 文字消息 20 | 21 | ![image](./attachment/vue-chat.png) 22 | 23 | * 图片消息 24 | 25 | ![image](./attachment/vue-chat-pic.png) 26 | 27 | * 视频消息 28 | 29 | ![image](./attachment/vue-chat-video.png) 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 | ![image](https://user-gold-cdn.xitu.io/2020/4/13/171719952947e62a?w=1518&h=655&f=png&s=170160) 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 | ![image](https://user-gold-cdn.xitu.io/2020/5/8/171f4c271ba2b4dd?w=2064&h=1144&f=png&s=428322) 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 | 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 | 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 | 11 | 12 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/menu/rightMenu.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/components/mycard/mycard.vue: -------------------------------------------------------------------------------- 1 | 2 | 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 | 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 | 13 | 14 | 34 | 35 | 46 | -------------------------------------------------------------------------------- /src/page/friend/friend.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 25 | 26 | -------------------------------------------------------------------------------- /src/page/friend/searchfriend.vue: -------------------------------------------------------------------------------- 1 | 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 | 23 | 24 | 135 | 136 | -------------------------------------------------------------------------------- /src/page/main.vue: -------------------------------------------------------------------------------- 1 | 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 --------------------------------------------------------------------------------