├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .postcssrc.js ├── README.md ├── _config.yml ├── 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 └── webpack.test.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── favicon.ico ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.vue ├── api │ └── api.js ├── assets │ └── logo.png ├── components │ ├── emojiSlide.vue │ ├── evaluate.vue │ └── toast.vue ├── main.js └── views │ └── main.vue ├── static ├── .gitkeep ├── audio │ └── msg.mp3 ├── emojiDB.json ├── images │ ├── addClose.svg │ ├── addOn.svg │ ├── c.jpg │ ├── female.jpg │ ├── hang-up.png │ ├── logo.jpg │ ├── new.gif │ ├── s.jpg │ ├── smileClose.svg │ ├── smileOn.svg │ ├── soundClose.svg │ ├── soundOn.svg │ ├── star.png │ └── wx.gif └── js │ ├── myVerto.js │ └── verto.js └── test ├── e2e ├── custom-assertions │ └── elementCount.js ├── nightwatch.conf.js ├── runner.js └── specs │ └── test.js └── unit ├── .eslintrc ├── index.js ├── karma.conf.js └── specs └── Hello.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=vue; 2 | *.css linguist-language=vue; 3 | *.html linguist-language=vue; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | test/unit/coverage 8 | test/e2e/reports 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /.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 | # wue-chat 2 | 3 | > 基于vue2.5的聊天界面开发 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | 20 | # run unit tests 21 | npm run unit 22 | 23 | # run e2e tests 24 | npm run e2e 25 | 26 | # run all tests 27 | npm test 28 | 29 | ``` 30 | ### test 31 | 32 | ![](https://github.com/yiluxiangbei87110/vue-chat/blob/master/static/images/wx.gif) 33 | 34 | ### 此版本源代码未更新 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /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 = process.env.NODE_ENV === 'testing' 14 | ? require('./webpack.prod.conf') 15 | : require('./webpack.dev.conf') 16 | 17 | // default port where dev server listens for incoming traffic 18 | var port = process.env.PORT || config.dev.port 19 | // automatically open browser, if not set will be false 20 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 21 | // Define HTTP proxies to your custom API backend 22 | // https://github.com/chimurai/http-proxy-middleware 23 | var proxyTable = config.dev.proxyTable 24 | 25 | var app = express() 26 | var compiler = webpack(webpackConfig) 27 | 28 | 29 | //新增axios 30 | var axios=require('axios'); 31 | var apiRoutes=express.Router(); 32 | apiRoutes.get('/customRequestService', function (req, res) { 33 | var url = 'https://kf.163.cn/WxCrm2.0_Mgw_Dev/WebMgw.aspx' 34 | axios.get(url, { 35 | headers: { 36 | // referer: 'https://kf.163.cn/service/test/ui/web/testVideo.html?m=wxcrmwebtest&p=106&v=8702a58532d695d1dd3fc2a6b53c8ebb&u=&n=&e=ios', 37 | // host: 'kf.163.cn' 38 | referer: 'https://kf.163.cn', 39 | host: 'kf.163.cn' 40 | }, 41 | params: req.query 42 | }).then((response) => { 43 | res.json(response.data) 44 | }).catch((e) => { 45 | console.log(e) 46 | }) 47 | }) 48 | 49 | 50 | app.use('/api', apiRoutes) 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 63 | publicPath: webpackConfig.output.publicPath, 64 | quiet: true 65 | }) 66 | 67 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 68 | log: () => {} 69 | }) 70 | // force page reload when html-webpack-plugin template changes 71 | compiler.plugin('compilation', function (compilation) { 72 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 73 | hotMiddleware.publish({ action: 'reload' }) 74 | cb() 75 | }) 76 | }) 77 | 78 | // proxy api requests 79 | Object.keys(proxyTable).forEach(function (context) { 80 | var options = proxyTable[context] 81 | if (typeof options === 'string') { 82 | options = { target: options } 83 | } 84 | app.use(proxyMiddleware(options.filter || context, options)) 85 | }) 86 | 87 | // handle fallback for HTML5 history API 88 | app.use(require('connect-history-api-fallback')()) 89 | 90 | // serve webpack bundle output 91 | app.use(devMiddleware) 92 | 93 | // enable hot-reload and state-preserving 94 | // compilation error display 95 | app.use(hotMiddleware) 96 | 97 | // serve pure static assets 98 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 99 | app.use(staticPath, express.static('./static')) 100 | 101 | var uri = 'http://localhost:' + port 102 | 103 | var _resolve 104 | var readyPromise = new Promise(resolve => { 105 | _resolve = resolve 106 | }) 107 | 108 | //console.log('> Starting dev server...') 109 | devMiddleware.waitUntilValid(() => { 110 | //console.log('> Listening at ' + uri + '\n') 111 | // when env is testing, don't need open it 112 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 113 | opn(uri) 114 | } 115 | _resolve() 116 | }) 117 | 118 | var server = app.listen(port) 119 | 120 | module.exports = { 121 | ready: readyPromise, 122 | close: () => { 123 | server.close() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /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 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /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: /\.(js|vue)$/, 32 | // loader: 'eslint-loader', 33 | // enforce: 'pre', 34 | // include: [resolve('src'), resolve('test')], 35 | // options: { 36 | // formatter: require('eslint-friendly-formatter') 37 | // } 38 | // }, 39 | { 40 | test: /\.vue$/, 41 | loader: 'vue-loader', 42 | options: vueLoaderConfig 43 | }, 44 | { 45 | test: /\.js$/, 46 | loader: 'babel-loader', 47 | include: [resolve('src'), resolve('test')] 48 | }, 49 | { 50 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 51 | loader: 'url-loader', 52 | options: { 53 | limit: 10000, 54 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 55 | } 56 | }, 57 | { 58 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 59 | loader: 'url-loader', 60 | options: { 61 | limit: 10000, 62 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /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 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true, 32 | favicon: './favicon.ico' 33 | }), 34 | new FriendlyErrorsPlugin() 35 | ] 36 | }) 37 | -------------------------------------------------------------------------------- /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 = process.env.NODE_ENV === 'testing' 13 | ? require('../config/test.env') 14 | : config.build.env 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | filename: utils.assetsPath('css/[name].[contenthash].css') 43 | }), 44 | // Compress extracted CSS. We are using this plugin so that possible 45 | // duplicated CSS from different components can be deduped. 46 | new OptimizeCSSPlugin({ 47 | cssProcessorOptions: { 48 | safe: true 49 | } 50 | }), 51 | // generate dist index.html with correct asset hash for caching. 52 | // you can customize output by editing /index.html 53 | // see https://github.com/ampedandwired/html-webpack-plugin 54 | new HtmlWebpackPlugin({ 55 | filename: process.env.NODE_ENV === 'testing' 56 | ? 'index.html' 57 | : config.build.index, 58 | template: 'index.html', 59 | inject: true, 60 | minify: { 61 | removeComments: true, 62 | collapseWhitespace: true, 63 | removeAttributeQuotes: true 64 | // more options: 65 | // https://github.com/kangax/html-minifier#options-quick-reference 66 | }, 67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 68 | chunksSortMode: 'dependency' 69 | }), 70 | // split vendor js into its own file 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'vendor', 73 | minChunks: function (module, count) { 74 | // any required modules inside node_modules are extracted to vendor 75 | return ( 76 | module.resource && 77 | /\.js$/.test(module.resource) && 78 | module.resource.indexOf( 79 | path.join(__dirname, '../node_modules') 80 | ) === 0 81 | ) 82 | } 83 | }), 84 | // extract webpack runtime and module manifest to its own file in order to 85 | // prevent vendor hash from being updated whenever app bundle is updated 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'manifest', 88 | chunks: ['vendor'] 89 | }), 90 | // copy custom static assets 91 | new CopyWebpackPlugin([ 92 | { 93 | from: path.resolve(__dirname, '../static'), 94 | to: config.build.assetsSubDirectory, 95 | ignore: ['.*'] 96 | } 97 | ]) 98 | ] 99 | }) 100 | 101 | if (config.build.productionGzip) { 102 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 103 | 104 | webpackConfig.plugins.push( 105 | new CompressionWebpackPlugin({ 106 | asset: '[path].gz[query]', 107 | algorithm: 'gzip', 108 | test: new RegExp( 109 | '\\.(' + 110 | config.build.productionGzipExtensions.join('|') + 111 | ')$' 112 | ), 113 | threshold: 10240, 114 | minRatio: 0.8 115 | }) 116 | ) 117 | } 118 | 119 | if (config.build.bundleAnalyzerReport) { 120 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 122 | } 123 | 124 | module.exports = webpackConfig 125 | -------------------------------------------------------------------------------- /build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | // This is the webpack config used for unit tests. 2 | 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseConfig = require('./webpack.base.conf') 7 | 8 | var webpackConfig = merge(baseConfig, { 9 | // use inline sourcemap for karma-sourcemap-loader 10 | module: { 11 | rules: utils.styleLoaders() 12 | }, 13 | devtool: '#inline-source-map', 14 | resolveLoader: { 15 | alias: { 16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 18 | 'scss-loader': 'sass-loader' 19 | } 20 | }, 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': require('../config/test.env') 24 | }) 25 | ] 26 | }) 27 | 28 | // no need for app entry during tests 29 | delete webpackConfig.entry 30 | 31 | module.exports = webpackConfig 32 | -------------------------------------------------------------------------------- /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: 8081, 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 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue chat 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-chat", 3 | "version": "1.0.0", 4 | "description": "vue-chat", 5 | "author": "Felix", 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 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 12 | "e2e": "node test/e2e/runner.js", 13 | "test": "npm run unit && npm run e2e", 14 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs" 15 | }, 16 | "dependencies": { 17 | "axios": "^0.18.0", 18 | "mint-ui": "^2.2.13", 19 | "save": "^2.3.1", 20 | "vue": "^2.5.16", 21 | "vue-awesome-swiper": "^2.6.7", 22 | "vue-axios": "^2.1.1" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer": "^6.7.2", 26 | "babel-core": "^6.22.1", 27 | "babel-eslint": "^7.1.1", 28 | "babel-loader": "^6.2.10", 29 | "babel-plugin-transform-runtime": "^6.22.0", 30 | "babel-preset-env": "^1.3.2", 31 | "babel-preset-stage-2": "^6.22.0", 32 | "babel-register": "^6.22.0", 33 | "chalk": "^1.1.3", 34 | "connect-history-api-fallback": "^1.3.0", 35 | "copy-webpack-plugin": "^4.0.1", 36 | "css-loader": "^0.28.0", 37 | "eslint": "^3.19.0", 38 | "eslint-friendly-formatter": "^2.0.7", 39 | "eslint-loader": "^1.7.1", 40 | "eslint-plugin-html": "^2.0.0", 41 | "eslint-config-standard": "^6.2.1", 42 | "eslint-plugin-promise": "^3.4.0", 43 | "eslint-plugin-standard": "^2.0.1", 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 | "cross-env": "^4.0.0", 53 | "karma": "^1.4.1", 54 | "karma-coverage": "^1.1.1", 55 | "karma-mocha": "^1.3.0", 56 | "karma-phantomjs-launcher": "^1.0.2", 57 | "karma-phantomjs-shim": "^1.4.0", 58 | "karma-sinon-chai": "^1.3.1", 59 | "karma-sourcemap-loader": "^0.3.7", 60 | "karma-spec-reporter": "0.0.30", 61 | "karma-webpack": "^2.0.2", 62 | "lolex": "^1.5.2", 63 | "mocha": "^3.2.0", 64 | "chai": "^3.5.0", 65 | "sinon": "^2.1.0", 66 | "sinon-chai": "^2.8.0", 67 | "inject-loader": "^3.0.0", 68 | "babel-plugin-istanbul": "^4.1.1", 69 | "phantomjs-prebuilt": "^2.1.14", 70 | "chromedriver": "^2.27.2", 71 | "cross-spawn": "^5.0.1", 72 | "nightwatch": "^0.9.12", 73 | "selenium-server": "^3.0.1", 74 | "semver": "^5.3.0", 75 | "shelljs": "^0.7.6", 76 | "opn": "^4.0.2", 77 | "optimize-css-assets-webpack-plugin": "^1.3.0", 78 | "ora": "^1.2.0", 79 | "rimraf": "^2.6.0", 80 | "url-loader": "^0.5.8", 81 | "vue-loader": "^12.1.0", 82 | "vue-style-loader": "^3.0.1", 83 | "vue-template-compiler": "^2.3.3", 84 | "webpack": "^2.6.1", 85 | "webpack-dev-middleware": "^1.10.0", 86 | "webpack-hot-middleware": "^2.18.0", 87 | "webpack-merge": "^4.1.0" 88 | }, 89 | "engines": { 90 | "node": ">= 4.0.0", 91 | "npm": ">= 3.0.0" 92 | }, 93 | "browserslist": [ 94 | "> 1%", 95 | "last 2 versions", 96 | "not ie <= 8" 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 668 | 669 | 670 | 671 | 672 | 673 | -------------------------------------------------------------------------------- /src/api/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | //获取emoji头像(本地json只能放在static文件夹里) 3 | export function getEmojiData() { 4 | return axios({ 5 | method: 'get', 6 | url: '/static/emojiDB.json', 7 | }) 8 | .then(function(res) { 9 | return Promise.resolve(res.data); 10 | }); 11 | } -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/emojiSlide.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 23 | 24 | 71 | 72 | -------------------------------------------------------------------------------- /src/components/evaluate.vue: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 47 | 48 | -------------------------------------------------------------------------------- /src/components/toast.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 36 | 37 | -------------------------------------------------------------------------------- /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 Mint from 'mint-ui' 6 | import 'mint-ui/lib/style.css' 7 | 8 | 9 | import axios from 'axios' 10 | import VueAxios from 'vue-axios' 11 | 12 | Vue.use(VueAxios, axios) 13 | 14 | Vue.use(Mint); 15 | Vue.config.productionTip = false 16 | 17 | /* eslint-disable no-new */ 18 | new Vue({ 19 | el: '#app', 20 | template: '', 21 | components: { App } 22 | }) 23 | -------------------------------------------------------------------------------- /src/views/main.vue: -------------------------------------------------------------------------------- 1 | 145 | 146 | 332 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/.gitkeep -------------------------------------------------------------------------------- /static/audio/msg.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/audio/msg.mp3 -------------------------------------------------------------------------------- /static/emojiDB.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "EXPS": [ 4 | { 5 | "file": "https://kf.163.cn/service/ui/image/emotion/100.gif", 6 | "code": "/::)", 7 | "title": "微笑", 8 | "reg": /\/::\)/g 9 | }, 10 | { 11 | "file": "https://kf.163.cn/service/ui/image/emotion/101.gif", 12 | "code": "/::~", 13 | "title": "伤心", 14 | "reg": /\/::~/g 15 | }, 16 | { 17 | "file": "https://kf.163.cn/service/ui/image/emotion/102.gif", 18 | "code": "/::B", 19 | "title": "美女", 20 | "reg": /\/::B/g 21 | }, 22 | { 23 | "file": "https://kf.163.cn/service/ui/image/emotion/103.gif", 24 | "code": "/::|", 25 | "title": "发呆", 26 | "reg": /\/::\|/g 27 | }, 28 | { 29 | "file": "https://kf.163.cn/service/ui/image/emotion/104.gif", 30 | "code": "/:8-)", 31 | "title": "墨镜", 32 | "reg": /\/:8-\)/g 33 | }, 34 | { 35 | "file": "https://kf.163.cn/service/ui/image/emotion/105.gif", 36 | "code": "/::<", 37 | "title": "哭", 38 | "reg": /\/:: -------------------------------------------------------------------------------- /static/images/addOn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/images/c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/images/c.jpg -------------------------------------------------------------------------------- /static/images/female.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/images/female.jpg -------------------------------------------------------------------------------- /static/images/hang-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/images/hang-up.png -------------------------------------------------------------------------------- /static/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/images/logo.jpg -------------------------------------------------------------------------------- /static/images/new.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/images/new.gif -------------------------------------------------------------------------------- /static/images/s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/images/s.jpg -------------------------------------------------------------------------------- /static/images/smileClose.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/images/smileOn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/images/soundClose.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/images/soundOn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/images/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/images/star.png -------------------------------------------------------------------------------- /static/images/wx.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiluxiangbei87110/vue-chat/e86f47ce371d478e01f7ac8225dbc38f695acadb/static/images/wx.gif -------------------------------------------------------------------------------- /static/js/myVerto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | //var siphost = '192.168.100.7'; 3 | //var siphost = '172.24.195.142'; 4 | //var sipwsurl = 'ws://' + siphost + ':8081'; 5 | var cur_call = null; 6 | var confMan = null; 7 | var verto = null; 8 | var ringing = false; 9 | var autocall = false; 10 | var chatting_with = false; 11 | 12 | // var taskPlayVoiceRinging; 13 | // function playVoiceRinging() { 14 | // var audioRinging = document.getElementById('audioRinging'); 15 | // audioRinging.play(); 16 | // if (ringing) { 17 | // taskPlayVoiceRinging = setTimeout(function () { 18 | // playVoiceRinging(); 19 | // }, 5000); 20 | // } 21 | // } 22 | // function stopVoiceRinging() { 23 | // var audioRinging = document.getElementById('audioRinging'); 24 | // if (taskPlayVoiceRinging) { 25 | // clearTimeout(taskPlayVoiceRinging); 26 | // } 27 | // audioRinging.pause(); 28 | // audioRinging.load(); 29 | // } 30 | 31 | //----------外呼----------------// 32 | function callout() { 33 | var caller = '83990044'; 34 | var called = $("#tel").val(); 35 | sip_callout(caller, called); 36 | } 37 | 38 | function transfer() { 39 | var target = $("#transfertarget").val(); 40 | sip_transfer(target); 41 | } 42 | 43 | 44 | //--------呼入回调函数-------------// 45 | function cb_callin(caller, called, guid, type) { 46 | console.log('caller:' + caller); 47 | console.log('called:' + called); 48 | console.log('guid:' + guid); 49 | console.log('type:' + type); 50 | 51 | Ext.ComponentQuery.query('dial')[0].getController().callin(caller, called, guid, type); 52 | 53 | 54 | } 55 | 56 | 57 | //--------呼叫结算回调函数-------------// 58 | function cb_callend() { 59 | console.log('-----------call end-------------'); 60 | //显示外呼按钮 61 | // Ext.ComponentQuery.query('dial')[0].getController().setBtnCalloutDisabled(false); 62 | 63 | // Ext.ComponentQuery.query('dial')[0].getViewModel().set('callinDisable', false); 64 | // Ext.ComponentQuery.query('dial')[0].getViewModel().set('calloutDisable', false); 65 | // Ext.ComponentQuery.query('dial')[0].getViewModel().set('callToolsDisable', true); 66 | // Ext.ComponentQuery.query('#tbCallCenterOperate>#toggleHold')[0].setText('静音'); 67 | 68 | // //取消屏蔽呼叫坐席 69 | // var editorComm = Ext.ComponentQuery.query('communicate_session>panel>htmleditor'); 70 | // if (editorComm.length > 0) { 71 | // for (var i = 0; i < editorComm.length; i++) { 72 | // editorComm[i].getToolbar().query('#itemIdSessionCallout')[0].setDisabled(false); 73 | // } 74 | // } 75 | // //取消屏蔽 留言处理-外呼 76 | // var mesCallout = Ext.ComponentQuery.query('#fcMesMobi>#callout'); 77 | // if (mesCallout.length > 0) { 78 | // mesCallout[0].setDisabled(false); 79 | // } 80 | 81 | // //删除对应的浏览器消息提醒 82 | // Ext.ComponentQuery.query('dial')[0].getController().removeNotif('语音呼入'); 83 | // Ext.Msg.hide(); 84 | 85 | // if (callOperateType === 1) { 86 | // showAllCallTime();//计算总时长 87 | 88 | // //浏览器消息提醒notification :已挂机 89 | // var mobile = ''; 90 | // var vModel = Ext.ComponentQuery.query('dial')[0].getViewModel(), 91 | // callType = vModel.get('callType'); 92 | // if (callType == 1) {//呼入 93 | // mobile = Ext.ComponentQuery.query('dial>#callin>#caller')[0].getValue(); 94 | // } else if (callType == 2) {//呼出 95 | // mobile = Ext.ComponentQuery.query('dial>#callout>panel>#called')[0].getValue(); 96 | // } 97 | // if (mobile != '') { 98 | // playMusicCommon('sound/onhook.wav'); 99 | // client.showBroswerNotice('信息提示', mobile + '号码已挂机', 'image/phone_off.png', '挂机提示'); 100 | // setTimeout(function () { 101 | // var notif = client.getNotifByTag('挂机提示'); 102 | // if (notif) { 103 | // client.removeNotif('挂机提示'); 104 | // notif.notif.close(); 105 | // } 106 | // }, 3000); 107 | // } 108 | 109 | 110 | 111 | // //刷新呼叫记录 112 | // // setTimeout(function () { 113 | // // server_mgw(this, '15005', { type: 1 }); 114 | // // server_mgw(this, '15005', { type: 2 }); 115 | // // }, 5000); 116 | 117 | // } else if (callOperateType === 2) {//坐席通话 118 | // var communicateChat = Ext.getCmp('idCommunicateChat'); 119 | // if (communicateChat && communicateChat.getActiveTab()) { 120 | // communicateChat.getActiveTab().down('htmleditor').down('[itemId=itemIdSessionCallout]').setDisabled(false); 121 | // //communicateChat.getActiveTab().down('htmleditor').down('[itemId=displayTip]').setValue('已挂机...'); 122 | // playMusicCommon('sound/onhook.wav'); 123 | // client.showBroswerNotice('信息提示', '工号 ' + communicateChat.getActiveTab().oper + ' 已挂机', 'image/phone_off.png', '挂机提示'); 124 | // setTimeout(function () { 125 | // var notif = client.getNotifByTag('挂机提示'); 126 | // if (notif) { 127 | // client.removeNotif('挂机提示'); 128 | // notif.notif.close(); 129 | // } 130 | // }, 3000); 131 | // } 132 | // } else if (callOperateType === 3) {//监听 133 | // playMusicCommon('sound/onhook.wav'); 134 | // client.showBroswerNotice('信息提示', '已取消监听', 'image/phone_off.png', '挂机提示'); 135 | // setTimeout(function () { 136 | // var notif = client.getNotifByTag('挂机提示'); 137 | // if (notif) { 138 | // client.removeNotif('挂机提示'); 139 | // notif.notif.close(); 140 | // } 141 | // }, 3000); 142 | // } 143 | 144 | // //显示主动营销外呼按钮 145 | // // var myworkmain = Ext.ComponentQuery.query('telsalemyworkmain')[0]; 146 | // // if (myworkmain) { 147 | // // myworkmain.getController().lookupReference('btnCallout').setDisabled(false); 148 | // // } 149 | 150 | } 151 | 152 | 153 | //---------登录回调函数--------------// 154 | function cb_login(v, success) { 155 | console.log('---------------------登陆事件-----------------------'); 156 | console.log(v); 157 | console.log('login:' + success); 158 | if (success === false) { 159 | console.log('登陆语音平台失败,请联系管理员。'); 160 | } else { 161 | //语音注册 162 | //server_mgw('', '15001', { svrip: 'wss.163.cn' }, ''); 163 | 164 | } 165 | } 166 | 167 | 168 | //---------退出回调函数--------------// 169 | function cb_logout(v, success) { 170 | console.log('---------------------退出事件-----------------------'); 171 | console.log(v); 172 | console.log("success:" + success); 173 | 174 | 175 | } 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | //-------------登陆---------------------// 187 | function sip_login(user, pwd) { 188 | console.log(user); 189 | console.log(pwd); 190 | if (verto) { 191 | verto.loginData({ 192 | login: user,// + "@" + 'wss.163.cn', 193 | passwd: pwd 194 | }); 195 | verto.login(); 196 | } 197 | else { 198 | verto = new $.verto({ 199 | login: user + "@" + 'wss.163.cn', 200 | passwd: pwd, 201 | socketUrl: 'wss://' + 'wss.163.cn' + ':8082', 202 | tag: "webcam", 203 | localTag: 'local_webcam', 204 | //ringFile: "sounds/bell_ring2.wav", 205 | videoParams: { 206 | "minWidth": 320, 207 | "minHeight": 240, 208 | "maxWidth": 320, 209 | "maxHeight": 240, 210 | "minFrameRate": 15, 211 | "vertoBestFrameRate": 30 212 | }, 213 | audioParams: { 214 | googAutoGainControl: false, 215 | googNoiseSuppression: false, 216 | googHighpassFilter: false 217 | } 218 | //iceServers: Ext.isIE ? true : false 219 | }, callbacks); 220 | } 221 | } 222 | 223 | 224 | //-------------退出-----------------// 225 | function sip_logout() { 226 | if (verto) { 227 | if (cur_call) { 228 | verto.hangup(); 229 | cur_call = null; 230 | } 231 | verto.logout(); 232 | verto = null; 233 | } 234 | } 235 | 236 | //-------------外呼-------------------------// 237 | function sip_callout(caller, called) { 238 | console.log(caller); 239 | console.log(called); 240 | if (!verto) { 241 | return; 242 | } 243 | 244 | if (cur_call) { 245 | return; 246 | } 247 | 248 | cur_call = verto.newCall({ 249 | destination_number: called, 250 | caller_id_name: caller, 251 | caller_id_number: caller, 252 | useVideo: true, 253 | useStereo: false 254 | }); 255 | 256 | ////屏蔽外呼按钮 257 | //Ext.ComponentQuery.query('dial')[0].getController().setBtnCalloutDisabled(true); 258 | 259 | 260 | //Ext.ComponentQuery.query('dial')[0].getViewModel().set('callinDisable', true); 261 | //Ext.ComponentQuery.query('dial')[0].getViewModel().set('calloutDisable', false); 262 | //Ext.ComponentQuery.query('dial')[0].getViewModel().set('callToolsDisable', false); 263 | 264 | } 265 | 266 | 267 | //---------------挂机---------------------------------// 268 | function sip_callend() { 269 | if (cur_call) { 270 | verto.hangup(); 271 | cur_call = null; 272 | } 273 | 274 | //用户挂机时删除对应的浏览器消息提醒notification 275 | //Ext.ComponentQuery.query('dial')[0].getController().removeNotif('语音呼入'); 276 | 277 | 278 | } 279 | 280 | 281 | //--------静音、呼叫保持/取消静音、取消呼叫保持-------// 282 | function sip_hold() { 283 | if (cur_call) { 284 | cur_call.toggleHold(); 285 | } 286 | } 287 | 288 | 289 | //-----------按键播放dtmf音--------------------------// 290 | function sip_dtmf(key) { 291 | var i = parseInt(key); 292 | if (key === "#" || key === "*" || key === "0" || (i > 0 && i <= 9)) { 293 | if (cur_call) { 294 | cur_call.dtmf(key); 295 | } 296 | } 297 | } 298 | 299 | 300 | //-----------应答来电--------------------------// 301 | function sip_answer(cname, cid) { 302 | if (cur_call) { 303 | cur_call.answer({ 304 | useStereo: false, 305 | callee_id_name: cname, 306 | callee_id_number: cid, 307 | }); 308 | 309 | 310 | 311 | } 312 | } 313 | 314 | 315 | //-----------转接电话--------------------------// 316 | function sip_transfer(target) { 317 | if (cur_call) { 318 | if (target) { 319 | cur_call.transfer(target); 320 | } 321 | } 322 | } 323 | 324 | 325 | var callbacks = { 326 | 327 | onMessage: function (verto, dialog, msg, data) { 328 | 329 | switch (msg) { 330 | case $.verto.enum.message.pvtEvent: 331 | // console.error("pvtEvent", data.pvtData); 332 | if (data.pvtData) { 333 | switch (data.pvtData.action) { 334 | 335 | case "conference-liveArray-part": 336 | break; 337 | case "conference-liveArray-join": 338 | break; 339 | } 340 | } 341 | break; 342 | case $.verto.enum.message.info: 343 | var body = data ? data.body : ''; 344 | break; 345 | case $.verto.enum.message.display: 346 | break; 347 | default: 348 | break; 349 | } 350 | }, 351 | 352 | onDialogState: function (d) { 353 | cur_call = d; 354 | 355 | if (d.state == $.verto.enum.state.ringing) { 356 | ringing = true; 357 | //playVoiceRinging(); 358 | } else { 359 | ringing = false; 360 | //stopVoiceRinging(); 361 | } 362 | 363 | switch (d.state) { 364 | case $.verto.enum.state.ringing: 365 | var callerStr = d.params.caller_id_number.split('#'); 366 | console.log(callerStr); 367 | var caller = callerStr[0]; 368 | var called = callerStr[1]; 369 | var guid = callerStr[2]; 370 | var type = callerStr[3]; 371 | cb_callin(caller, called, guid, type); 372 | 373 | // if (callOperateType === 1) { 374 | // startTaskCallWaitingTimer();//等待时长计时 375 | // } 376 | break; 377 | 378 | case $.verto.enum.state.trying: 379 | // if (callOperateType === 1) { 380 | // startTaskCallWaitingTimer();//等待时长计时 381 | // } else if (callOperateType === 2) {//坐席通话 382 | // var communicateChat = Ext.getCmp('idCommunicateChat'); 383 | // if (communicateChat && communicateChat.getActiveTab()) { 384 | // //communicateChat.getActiveTab().down('htmleditor').down('[itemId=displayTip]').setValue('接通中...'); 385 | // communicateChat.getActiveTab().down('htmleditor').down('[itemId=itemIdSessionCallout]').setDisabled(true); 386 | // } 387 | // } 388 | break; 389 | case $.verto.enum.state.early: 390 | break; 391 | case $.verto.enum.state.active: 392 | // if (callOperateType === 1) { 393 | // showCallTimerWindow();//计时器 394 | // stopTaskCallWaitingTimer();//等待时长计时-关闭 395 | 396 | // //浏览器消息提醒notification :已接通 397 | // var mobile = ''; 398 | // var vModel = Ext.ComponentQuery.query('dial')[0].getViewModel(), 399 | // callType = vModel.get('callType'); 400 | // if (callType == 1) {//呼入 401 | // mobile = Ext.ComponentQuery.query('dial>#callin>#caller')[0].getValue(); 402 | // } else if (callType == 2) {//呼出 403 | // mobile = Ext.ComponentQuery.query('dial>#callout>panel>#called')[0].getValue(); 404 | // } 405 | // if (mobile != '') { 406 | // playMusicCommon('sound/answer.wav'); 407 | // client.showBroswerNotice('信息提示', mobile + '号码已接通', 'image/phone_on.png', '接通提示'); 408 | // setTimeout(function () { 409 | // var notif = client.getNotifByTag('接通提示'); 410 | // if (notif) { 411 | // client.removeNotif('接通提示'); 412 | // notif.notif.close(); 413 | // } 414 | // }, 3000); 415 | // } 416 | 417 | // //主动营销 418 | // var myworkmain = Ext.ComponentQuery.query('telsalemyworkmain')[0]; 419 | // if (myworkmain) { 420 | // myworkmain.getController().getViewModel().set('answeredFlag', 1); 421 | // } 422 | // } else if (callOperateType === 2) {//坐席通话 423 | // var communicateChat = Ext.getCmp('idCommunicateChat'); 424 | // if (communicateChat && communicateChat.getActiveTab()) { 425 | // //communicateChat.getActiveTab().down('htmleditor').down('[itemId=displayTip]').setValue('已接通'); 426 | // playMusicCommon('sound/answer.wav'); 427 | // client.showBroswerNotice('信息提示', '工号 ' + communicateChat.getActiveTab().oper + ' 已接通', 'image/phone_on.png', '接通提示'); 428 | // setTimeout(function () { 429 | // var notif = client.getNotifByTag('接通提示'); 430 | // if (notif) { 431 | // client.removeNotif('接通提示'); 432 | // notif.notif.close(); 433 | // } 434 | // }, 3000); 435 | // } 436 | // } else if (callOperateType === 3) {//监听 437 | // playMusicCommon('sound/answer.wav'); 438 | // client.showBroswerNotice('信息提示', '已监听工号:' + callCenterMonitorOper, 'image/phone_on.png', '接通提示'); 439 | // setTimeout(function () { 440 | // var notif = client.getNotifByTag('接通提示'); 441 | // if (notif) { 442 | // client.removeNotif('接通提示'); 443 | // notif.notif.close(); 444 | // } 445 | // }, 3000); 446 | // } 447 | break; 448 | case $.verto.enum.state.hangup: 449 | break; 450 | case $.verto.enum.state.destroy: 451 | cb_callend(); 452 | cur_call = null; 453 | //stopTaskCallWaitingTimer();//等待时长计时-关闭 454 | break; 455 | case $.verto.enum.state.held: 456 | break; 457 | default: 458 | break; 459 | } 460 | }, 461 | onWSLogin: function (v, success) { 462 | cur_call = null; 463 | ringing = false; 464 | cb_login(v, success); 465 | }, 466 | 467 | onWSClose: function (v, success) { 468 | cb_logout(v, success); 469 | }, 470 | 471 | onEvent: function (v, e) { 472 | console.debug("GOT EVENT", e); 473 | }, 474 | }; -------------------------------------------------------------------------------- /static/js/verto.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | function findLine(sdpLines, prefix, substr) { return findLineInRange(sdpLines, 0, -1, prefix, substr); } 3 | function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { 4 | var realEndLine = (endLine != -1) ? endLine : sdpLines.length; for (var i = startLine; i < realEndLine; ++i) { if (sdpLines[i].indexOf(prefix) === 0) { if (!substr || sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { return i; } } } 5 | return null; 6 | } 7 | function getCodecPayloadType(sdpLine) { var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); var result = sdpLine.match(pattern); return (result && result.length == 2) ? result[1] : null; } 8 | function setDefaultCodec(mLine, payload) { 9 | var elements = mLine.split(' '); var newLine = []; var index = 0; for (var i = 0; i < elements.length; i++) { 10 | if (index === 3) { newLine[index++] = payload; } 11 | if (elements[i] !== payload) newLine[index++] = elements[i]; 12 | } 13 | return newLine.join(' '); 14 | } 15 | $.FSRTC = function (options) { 16 | this.options = $.extend({ useVideo: null, useStereo: false, userData: null, localVideo: null, screenShare: false, useCamera: "any", iceServers: false, videoParams: {}, audioParams: {}, callbacks: { onICEComplete: function () { }, onICE: function () { }, onOfferSDP: function () { } }, }, options); this.audioEnabled = true; this.videoEnabled = true; this.mediaData = { SDP: null, profile: {}, candidateList: [] }; if (moz) { this.constraints = { offerToReceiveAudio: this.options.useSpeak === "none" ? false : true, offerToReceiveVideo: this.options.useVideo ? true : false, }; } else { this.constraints = { optional: [{ 'DtlsSrtpKeyAgreement': 'true' }], mandatory: { OfferToReceiveAudio: this.options.useSpeak === "none" ? false : true, OfferToReceiveVideo: this.options.useVideo ? true : false, } }; } 17 | if (self.options.useVideo) { self.options.useVideo.style.display = 'none'; } 18 | setCompat(); checkCompat(); 19 | }; $.FSRTC.validRes = []; $.FSRTC.prototype.useVideo = function (obj, local) { 20 | var self = this; if (obj) { self.options.useVideo = obj; self.options.localVideo = local; if (moz) { self.constraints.offerToReceiveVideo = true; } else { self.constraints.mandatory.OfferToReceiveVideo = true; } } else { self.options.useVideo = null; self.options.localVideo = null; if (moz) { self.constraints.offerToReceiveVideo = false; } else { self.constraints.mandatory.OfferToReceiveVideo = false; } } 21 | if (self.options.useVideo) { self.options.useVideo.style.display = 'none'; } 22 | }; $.FSRTC.prototype.useStereo = function (on) { var self = this; self.options.useStereo = on; }; $.FSRTC.prototype.stereoHack = function (sdp) { 23 | var self = this; if (!self.options.useStereo) { return sdp; } 24 | var sdpLines = sdp.split('\r\n'); var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'), opusPayload; if (!opusIndex) { return sdp; } else { opusPayload = getCodecPayloadType(sdpLines[opusIndex]); } 25 | var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); if (fmtpLineIndex === null) { sdpLines[opusIndex] = sdpLines[opusIndex] + '\r\na=fmtp:' + opusPayload.toString() + " stereo=1; sprop-stereo=1" } else { sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1'); } 26 | sdp = sdpLines.join('\r\n'); return sdp; 27 | }; function setCompat() { $.FSRTC.moz = !!navigator.mozGetUserMedia; if (!navigator.getUserMedia) { navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia; } } 28 | function checkCompat() { 29 | if (!navigator.getUserMedia) { alert('This application cannot function in this browser.'); return false; } 30 | return true; 31 | } 32 | function onStreamError(self, e) { console.log('There has been a problem retrieving the streams - did you allow access? Check Device Resolution', e); doCallback(self, "onError", e); } 33 | function onStreamSuccess(self, stream) { console.log("Stream Success"); doCallback(self, "onStream", stream); } 34 | function onICE(self, candidate) { self.mediaData.candidate = candidate; self.mediaData.candidateList.push(self.mediaData.candidate); doCallback(self, "onICE"); } 35 | function doCallback(self, func, arg) { if (func in self.options.callbacks) { self.options.callbacks[func](self, arg); } } 36 | function onICEComplete(self, candidate) { console.log("ICE Complete"); doCallback(self, "onICEComplete"); } 37 | function onChannelError(self, e) { console.error("Channel Error", e); doCallback(self, "onError", e); } 38 | function onICESDP(self, sdp) { self.mediaData.SDP = self.stereoHack(sdp.sdp); console.log("ICE SDP"); doCallback(self, "onICESDP"); } 39 | function onAnswerSDP(self, sdp) { self.answer.SDP = self.stereoHack(sdp.sdp); console.log("ICE ANSWER SDP"); doCallback(self, "onAnswerSDP", self.answer.SDP); } 40 | function onMessage(self, msg) { console.log("Message"); doCallback(self, "onICESDP", msg); } 41 | function onRemoteStream(self, stream) { 42 | if (self.options.useVideo) { self.options.useVideo.style.display = 'block'; } 43 | var element = self.options.useAudio; console.log("REMOTE STREAM", stream, element); if (typeof element.srcObject !== 'undefined') { element.srcObject = stream; } else if (typeof element.mozSrcObject !== 'undefined') { element.mozSrcObject = stream; } else if (typeof element.src !== 'undefined') { element.src = URL.createObjectURL(stream); } else { console.error('Error attaching stream to element.'); } 44 | self.options.useAudio.play(); self.remoteStream = stream; 45 | } 46 | function onOfferSDP(self, sdp) { self.mediaData.SDP = self.stereoHack(sdp.sdp); console.log("Offer SDP"); doCallback(self, "onOfferSDP"); } 47 | $.FSRTC.prototype.answer = function (sdp, onSuccess, onError) { this.peer.addAnswerSDP({ type: "answer", sdp: sdp }, onSuccess, onError); }; $.FSRTC.prototype.stopPeer = function () { if (self.peer) { console.log("stopping peer"); self.peer.stop(); } } 48 | $.FSRTC.prototype.stop = function () { 49 | var self = this; if (self.options.useVideo) { self.options.useVideo.style.display = 'none'; if (moz) { self.options.useVideo['mozSrcObject'] = null; } else { self.options.useVideo['src'] = ''; } } 50 | if (self.localStream) { 51 | if (typeof self.localStream.stop == 'function') { self.localStream.stop(); } else { if (self.localStream.active) { var tracks = self.localStream.getTracks(); console.error(tracks); tracks.forEach(function (track, index) { console.log(track); track.stop(); }) } } 52 | self.localStream = null; 53 | } 54 | if (self.options.localVideo) { self.options.localVideo.style.display = 'none'; if (moz) { self.options.localVideo['mozSrcObject'] = null; } else { self.options.localVideo['src'] = ''; } } 55 | if (self.options.localVideoStream) { if (typeof self.options.localVideoStream.stop == 'function') { self.options.localVideoStream.stop(); } else { if (self.options.localVideoStream.active) { var tracks = self.options.localVideoStream.getTracks(); console.error(tracks); tracks.forEach(function (track, index) { console.log(track); track.stop(); }) } } } 56 | if (self.peer) { console.log("stopping peer"); self.peer.stop(); } 57 | }; $.FSRTC.prototype.getMute = function () { var self = this; return self.audioEnabled; } 58 | $.FSRTC.prototype.setMute = function (what) { 59 | var self = this; var audioTracks = self.localStream.getAudioTracks(); for (var i = 0, len = audioTracks.length; i < len; i++) { 60 | switch (what) { case "on": audioTracks[i].enabled = true; break; case "off": audioTracks[i].enabled = false; break; case "toggle": audioTracks[i].enabled = !audioTracks[i].enabled; default: break; } 61 | self.audioEnabled = audioTracks[i].enabled; 62 | } 63 | return !self.audioEnabled; 64 | } 65 | $.FSRTC.prototype.getVideoMute = function () { var self = this; return self.videoEnabled; } 66 | $.FSRTC.prototype.setVideoMute = function (what) { 67 | var self = this; var videoTracks = self.localStream.getVideoTracks(); for (var i = 0, len = videoTracks.length; i < len; i++) { 68 | switch (what) { case "on": videoTracks[i].enabled = true; break; case "off": videoTracks[i].enabled = false; break; case "toggle": videoTracks[i].enabled = !videoTracks[i].enabled; default: break; } 69 | self.videoEnabled = videoTracks[i].enabled; 70 | } 71 | return !self.videoEnabled; 72 | } 73 | $.FSRTC.prototype.createAnswer = function (params) { 74 | var self = this; self.type = "answer"; self.remoteSDP = params.sdp; console.debug("inbound sdp: ", params.sdp); function onSuccess(stream) { self.localStream = stream; self.peer = RTCPeerConnection({ type: self.type, attachStream: self.localStream, onICE: function (candidate) { return onICE(self, candidate); }, onICEComplete: function () { return onICEComplete(self); }, onRemoteStream: function (stream) { return onRemoteStream(self, stream); }, onICESDP: function (sdp) { return onICESDP(self, sdp); }, onChannelError: function (e) { return onChannelError(self, e); }, constraints: self.constraints, iceServers: self.options.iceServers, offerSDP: { type: "offer", sdp: self.remoteSDP } }); onStreamSuccess(self); } 75 | function onError(e) { onStreamError(self, e); } 76 | var mediaParams = getMediaParams(self); console.log("Audio constraints", mediaParams.audio); console.log("Video constraints", mediaParams.video); if (self.options.useVideo && self.options.localVideo) { getUserMedia({ constraints: { audio: false, video: { mandatory: self.options.videoParams, optional: [] }, }, localVideo: self.options.localVideo, onsuccess: function (e) { self.options.localVideoStream = e; console.log("local video ready"); }, onerror: function (e) { console.error("local video error!"); } }); } 77 | getUserMedia({ constraints: { audio: mediaParams.audio, video: mediaParams.video }, video: mediaParams.useVideo, onsuccess: onSuccess, onerror: onError }); 78 | }; function getMediaParams(obj) { 79 | var audio; if (obj.options.useMic && obj.options.useMic === "none") { console.log("Microphone Disabled"); audio = false; } else if (obj.options.videoParams && obj.options.screenShare) { console.error("SCREEN SHARE"); audio = false; } else { 80 | audio = { mandatory: {}, optional: [] }; if (obj.options.useMic !== "any") { audio.optional = [{ sourceId: obj.options.useMic }] } 81 | if (obj.options.audioParams) { for (var key in obj.options.audioParams) { var con = {}; con[key] = obj.options.audioParams[key]; audio.optional.push(con); } } 82 | } 83 | if (obj.options.useVideo && obj.options.localVideo) { getUserMedia({ constraints: { audio: false, video: { mandatory: obj.options.videoParams, optional: [] }, }, localVideo: obj.options.localVideo, onsuccess: function (e) { self.options.localVideoStream = e; console.log("local video ready"); }, onerror: function (e) { console.error("local video error!"); } }); } 84 | var video = {}; var bestFrameRate = obj.options.videoParams.vertoBestFrameRate; delete obj.options.videoParams.vertoBestFrameRate; video = { mandatory: obj.options.videoParams, optional: [] } 85 | var useVideo = obj.options.useVideo; if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") { 86 | if (!video.optional) { video.optional = []; } 87 | if (obj.options.useCamera !== "any") { video.optional.push({ sourceId: obj.options.useCamera }); } 88 | if (bestFrameRate) { video.optional.push({ minFrameRate: bestFrameRate }); video.optional.push({ maxFrameRate: bestFrameRate }); } 89 | } else { console.log("Camera Disabled"); video = false; useVideo = false; } 90 | return { audio: audio, video: video, useVideo: useVideo }; 91 | } 92 | $.FSRTC.prototype.call = function (profile) { 93 | checkCompat(); var self = this; var screen = false; self.type = "offer"; if (self.options.videoParams && self.options.screenShare) { screen = true; } 94 | function onSuccess(stream) { 95 | self.localStream = stream; if (screen) { if (moz) { self.constraints.OfferToReceiveVideo = false; } else { self.constraints.mandatory.OfferToReceiveVideo = false; } } 96 | self.peer = RTCPeerConnection({ type: self.type, attachStream: self.localStream, onICE: function (candidate) { return onICE(self, candidate); }, onICEComplete: function () { return onICEComplete(self); }, onRemoteStream: screen ? function (stream) { } : function (stream) { return onRemoteStream(self, stream); }, onOfferSDP: function (sdp) { return onOfferSDP(self, sdp); }, onICESDP: function (sdp) { return onICESDP(self, sdp); }, onChannelError: function (e) { return onChannelError(self, e); }, constraints: self.constraints, iceServers: self.options.iceServers, }); onStreamSuccess(self, stream); 97 | } 98 | function onError(e) { onStreamError(self, e); } 99 | var mediaParams = getMediaParams(self); console.log("Audio constraints", mediaParams.audio); console.log("Video constraints", mediaParams.video); if (mediaParams.audio || mediaParams.video) { getUserMedia({ constraints: { audio: mediaParams.audio, video: mediaParams.video }, video: mediaParams.useVideo, onsuccess: onSuccess, onerror: onError }); } else { onSuccess(null); } 100 | }; window.moz = !!navigator.mozGetUserMedia; function RTCPeerConnection(options) { 101 | var gathering = false, done = false; var w = window, PeerConnection = w.mozRTCPeerConnection || w.webkitRTCPeerConnection, SessionDescription = w.mozRTCSessionDescription || w.RTCSessionDescription, IceCandidate = w.mozRTCIceCandidate || w.RTCIceCandidate; var STUN = { url: !moz ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121' }; var iceServers = null; if (options.iceServers) { 102 | var tmp = options.iceServers; if (typeof (tmp) === "boolean") { tmp = null; } 103 | if (tmp && !(typeof (tmp) == "object" && tmp.constructor === Array)) { console.warn("iceServers must be an array, reverting to default ice servers"); tmp = null; } 104 | iceServers = { iceServers: tmp || [STUN] }; if (!moz && !tmp) { iceServers.iceServers = [STUN]; } 105 | } 106 | var optional = { optional: [] }; if (!moz) { optional.optional = [{ DtlsSrtpKeyAgreement: true }, { RtpDataChannels: options.onChannelMessage ? true : false }]; } 107 | var peer = new PeerConnection(iceServers, optional); openOffererChannel(); var x = 0; function ice_handler() { 108 | done = true; gathering = null; if (options.onICEComplete) { options.onICEComplete(); } 109 | if (options.type == "offer") { if ((!moz || (!options.sentICESDP && peer.localDescription.sdp.match(/a=candidate/)) && !x && options.onICESDP)) { options.onICESDP(peer.localDescription); } } else { if (!x && options.onICESDP) { options.onICESDP(peer.localDescription); } } 110 | } 111 | peer.onicecandidate = function (event) { 112 | if (done) { return; } 113 | if (!gathering) { gathering = setTimeout(ice_handler, 1000); } 114 | if (event) { if (event.candidate) { options.onICE(event.candidate); } } else { 115 | done = true; if (gathering) { clearTimeout(gathering); gathering = null; } 116 | ice_handler(); 117 | } 118 | }; if (options.attachStream) peer.addStream(options.attachStream); if (options.attachStreams && options.attachStream.length) { var streams = options.attachStreams; for (var i = 0; i < streams.length; i++) { peer.addStream(streams[i]); } } 119 | peer.onaddstream = function (event) { var remoteMediaStream = event.stream; remoteMediaStream.onended = function () { if (options.onRemoteStreamEnded) options.onRemoteStreamEnded(remoteMediaStream); }; if (options.onRemoteStream) options.onRemoteStream(remoteMediaStream); }; var constraints = options.constraints || { offerToReceiveAudio: true, offerToReceiveVideo: true }; function createOffer() { if (!options.onOfferSDP) return; peer.createOffer(function (sessionDescription) { sessionDescription.sdp = serializeSdp(sessionDescription.sdp); peer.setLocalDescription(sessionDescription); options.onOfferSDP(sessionDescription); if (moz && options.onICESDP && sessionDescription.sdp.match(/a=candidate/)) { options.onICESDP(sessionDescription); options.sentICESDP = 1; } }, onSdpError, constraints); } 120 | function createAnswer() { if (options.type != "answer") return; peer.setRemoteDescription(new SessionDescription(options.offerSDP), onSdpSuccess, onSdpError); peer.createAnswer(function (sessionDescription) { sessionDescription.sdp = serializeSdp(sessionDescription.sdp); peer.setLocalDescription(sessionDescription); if (options.onAnswerSDP) { options.onAnswerSDP(sessionDescription); } }, onSdpError, constraints); } 121 | if ((options.onChannelMessage && !moz) || !options.onChannelMessage) { createOffer(); createAnswer(); } 122 | function setBandwidth(sdp) { sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); sdp = sdp.replace(/a=mid:data\r\n/g, 'a=mid:data\r\nb=AS:1638400\r\n'); return sdp; } 123 | function getInteropSDP(sdp) { 124 | var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''), extractedChars = ''; function getChars() { extractedChars += chars[parseInt(Math.random() * 40)] || ''; if (extractedChars.length < 40) getChars(); return extractedChars; } 125 | if (options.onAnswerSDP) sdp = sdp.replace(/(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g, ''); var inline = getChars() + '\r\n' + (extractedChars = ''); sdp = sdp.indexOf('a=crypto') == -1 ? sdp.replace(/c=IN/g, 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:' + inline + 'c=IN') : sdp; return sdp; 126 | } 127 | function serializeSdp(sdp) { return sdp; } 128 | var channel; function openOffererChannel() { if (!options.onChannelMessage || (moz && !options.onOfferSDP)) return; _openOffererChannel(); if (!moz) return; navigator.mozGetUserMedia({ audio: true, fake: true }, function (stream) { peer.addStream(stream); createOffer(); }, useless); } 129 | function _openOffererChannel() { channel = peer.createDataChannel(options.channel || 'RTCDataChannel', moz ? {} : { reliable: false }); if (moz) channel.binaryType = 'blob'; setChannelEvents(); } 130 | function setChannelEvents() { channel.onmessage = function (event) { if (options.onChannelMessage) options.onChannelMessage(event); }; channel.onopen = function () { if (options.onChannelOpened) options.onChannelOpened(channel); }; channel.onclose = function (event) { if (options.onChannelClosed) options.onChannelClosed(event); console.warn('WebRTC DataChannel closed', event); }; channel.onerror = function (event) { if (options.onChannelError) options.onChannelError(event); console.error('WebRTC DataChannel error', event); }; } 131 | if (options.onAnswerSDP && moz && options.onChannelMessage) openAnswererChannel(); function openAnswererChannel() { peer.ondatachannel = function (event) { channel = event.channel; channel.binaryType = 'blob'; setChannelEvents(); }; if (!moz) return; navigator.mozGetUserMedia({ audio: true, fake: true }, function (stream) { peer.addStream(stream); createAnswer(); }, useless); } 132 | function useless() { log('Error in fake:true'); } 133 | function onSdpSuccess() { } 134 | function onSdpError(e) { 135 | if (options.onChannelError) { options.onChannelError(e); } 136 | console.error('sdp error:', e); 137 | } 138 | return { addAnswerSDP: function (sdp, cbSuccess, cbError) { peer.setRemoteDescription(new SessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError); }, addICE: function (candidate) { peer.addIceCandidate(new IceCandidate({ sdpMLineIndex: candidate.sdpMLineIndex, candidate: candidate.candidate })); }, peer: peer, channel: channel, sendData: function (message) { if (channel) { channel.send(message); } }, stop: function () { peer.close(); if (options.attachStream) { if (typeof options.attachStream.stop == 'function') { options.attachStream.stop(); } else { options.attachStream.active = false; } } } }; 139 | } 140 | var video_constraints = { mandatory: {}, optional: [] }; function getUserMedia(options) { 141 | var n = navigator, media; n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia; n.getMedia(options.constraints || { audio: true, video: video_constraints }, streaming, options.onerror || function (e) { console.error(e); }); function streaming(stream) { 142 | if (options.localVideo) { options.localVideo[moz ? 'mozSrcObject' : 'src'] = moz ? stream : window.webkitURL.createObjectURL(stream); options.localVideo.style.display = 'block'; } 143 | if (options.onsuccess) { options.onsuccess(stream); } 144 | media = stream; 145 | } 146 | return media; 147 | } 148 | $.FSRTC.resSupported = function (w, h) { 149 | for (var i in $.FSRTC.validRes) { if ($.FSRTC.validRes[i][0] == w && $.FSRTC.validRes[i][1] == h) { return true; } } 150 | return false; 151 | } 152 | $.FSRTC.bestResSupported = function () { 153 | var w = 0, h = 0; for (var i in $.FSRTC.validRes) { if ($.FSRTC.validRes[i][0] > w && $.FSRTC.validRes[i][1] > h) { w = $.FSRTC.validRes[i][0]; h = $.FSRTC.validRes[i][1]; } } 154 | return [w, h]; 155 | } 156 | var resList = [[320, 180], [320, 240], [640, 360], [640, 480], [1280, 720], [1920, 1080]]; var resI = 0; var ttl = 0; var checkRes = function (cam, func) { 157 | if (resI >= resList.length) { var res = { 'validRes': $.FSRTC.validRes, 'bestResSupported': $.FSRTC.bestResSupported() }; localStorage.setItem("res_" + cam, $.toJSON(res)); if (func) return func(res); return; } 158 | var video = { mandatory: {}, optional: [] } 159 | if (cam) { video.optional = [{ sourceId: cam }]; } 160 | w = resList[resI][0]; h = resList[resI][1]; resI++; video.mandatory = { "minWidth": w, "minHeight": h, "maxWidth": w, "maxHeight": h }; getUserMedia({ constraints: { audio: ttl++ == 0, video: video }, onsuccess: function (e) { e.getTracks().forEach(function (track) { track.stop(); }); console.info(w + "x" + h + " supported."); $.FSRTC.validRes.push([w, h]); checkRes(cam, func); }, onerror: function (e) { console.error(w + "x" + h + " not supported."); checkRes(cam, func); } }); 161 | } 162 | $.FSRTC.getValidRes = function (cam, func) { 163 | var used = []; var cached = localStorage.getItem("res_" + cam); if (cached) { 164 | var cache = $.parseJSON(cached); if (cache) { $.FSRTC.validRes = cache.validRes; console.log("CACHED RES FOR CAM " + cam, cache); } else { console.error("INVALID CACHE"); } 165 | return func ? func(cache) : null; 166 | } 167 | $.FSRTC.validRes = []; resI = 0; checkRes(cam, func); 168 | } 169 | $.FSRTC.checkPerms = function (runtime, check_audio, check_video) { 170 | getUserMedia({ 171 | constraints: { audio: check_audio, video: check_video, }, onsuccess: function (e) { e.getTracks().forEach(function (track) { track.stop(); }); console.info("media perm init complete"); if (runtime) { setTimeout(runtime, 100, true); } }, onerror: function (e) { 172 | if (check_video && check_audio) { console.error("error, retesting with audio params only"); return $.FSRTC.checkPerms(runtime, check_audio, false); } 173 | console.error("media perm init error"); if (runtime) { runtime(false) } 174 | } 175 | }); 176 | } 177 | })(jQuery); (function ($) { 178 | $.JsonRpcClient = function (options) { var self = this; this.options = $.extend({ ajaxUrl: null, socketUrl: null, onmessage: null, login: null, passwd: null, sessid: null, loginParams: null, userVariables: null, getSocket: function (onmessage_cb) { return self._getSocket(onmessage_cb); } }, options); self.ws_cnt = 0; this.wsOnMessage = function (event) { self._wsOnMessage(event); }; }; $.JsonRpcClient.prototype._ws_socket = null; $.JsonRpcClient.prototype._ws_callbacks = {}; $.JsonRpcClient.prototype._current_id = 1; $.JsonRpcClient.prototype.speedTest = function (bytes, cb) { 179 | var socket = this.options.getSocket(this.wsOnMessage); if (socket !== null) { 180 | this.speedCB = cb; this.speedBytes = bytes; socket.send("#SPU " + bytes); var loops = bytes / 1024; var rem = bytes % 1024; var i; var data = new Array(1024).join("."); for (i = 0; i < loops; i++) { socket.send("#SPB " + data); } 181 | if (rem) { socket.send("#SPB " + data); } 182 | socket.send("#SPE"); 183 | } 184 | }; $.JsonRpcClient.prototype.call = function (method, params, success_cb, error_cb) { 185 | if (!params) { params = {}; } 186 | if (this.options.sessid) { params.sessid = this.options.sessid; } 187 | var request = { jsonrpc: '2.0', method: method, params: params, id: this._current_id++ }; if (!success_cb) { success_cb = function (e) { console.log("Success: ", e); }; } 188 | if (!error_cb) { error_cb = function (e) { console.log("Error: ", e); }; } 189 | var socket = this.options.getSocket(this.wsOnMessage); if (socket !== null) { this._wsCall(socket, request, success_cb, error_cb); return; } 190 | if (this.options.ajaxUrl === null) { throw "$.JsonRpcClient.call used with no websocket and no http endpoint."; } 191 | $.ajax({ type: 'POST', url: this.options.ajaxUrl, data: $.toJSON(request), dataType: 'json', cache: false, success: function (data) { if ('error' in data) error_cb(data.error, this); success_cb(data.result, this); }, error: function (jqXHR, textStatus, errorThrown) { try { var response = $.parseJSON(jqXHR.responseText); if ('console' in window) console.log(response); error_cb(response.error, this); } catch (err) { error_cb({ error: jqXHR.responseText }, this); } } }); 192 | }; $.JsonRpcClient.prototype.notify = function (method, params) { 193 | if (this.options.sessid) { params.sessid = this.options.sessid; } 194 | var request = { jsonrpc: '2.0', method: method, params: params }; var socket = this.options.getSocket(this.wsOnMessage); if (socket !== null) { this._wsCall(socket, request); return; } 195 | if (this.options.ajaxUrl === null) { throw "$.JsonRpcClient.notify used with no websocket and no http endpoint."; } 196 | $.ajax({ type: 'POST', url: this.options.ajaxUrl, data: $.toJSON(request), dataType: 'json', cache: false }); 197 | }; $.JsonRpcClient.prototype.batch = function (callback, all_done_cb, error_cb) { var batch = new $.JsonRpcClient._batchObject(this, all_done_cb, error_cb); callback(batch); batch._execute(); }; $.JsonRpcClient.prototype.socketReady = function () { 198 | if (this._ws_socket === null || this._ws_socket.readyState > 1) { return false; } 199 | return true; 200 | }; $.JsonRpcClient.prototype.closeSocket = function () { var self = this; if (self.socketReady()) { self._ws_socket.onclose = function (w) { console.log("Closing Socket"); }; self._ws_socket.close(); } }; $.JsonRpcClient.prototype.loginData = function (params) { var self = this; self.options.login = params.login; self.options.passwd = params.passwd; self.options.loginParams = params.loginParams; self.options.userVariables = params.userVariables; }; $.JsonRpcClient.prototype.connectSocket = function (onmessage_cb) { 201 | var self = this; if (self.to) { clearTimeout(self.to); } 202 | if (!self.socketReady()) { 203 | self.authing = false; if (self._ws_socket) { delete self._ws_socket; } 204 | self._ws_socket = new WebSocket(self.options.socketUrl); if (self._ws_socket) { 205 | self._ws_socket.onmessage = onmessage_cb; self._ws_socket.onclose = function (w) { 206 | if (!self.ws_sleep) { self.ws_sleep = 1000; } 207 | if (self.options.onWSClose) { self.options.onWSClose(self); } 208 | console.error("Websocket Lost " + self.ws_cnt + " sleep: " + self.ws_sleep + "msec"); self.to = setTimeout(function () { console.log("Attempting Reconnection...."); self.connectSocket(onmessage_cb); }, self.ws_sleep); self.ws_cnt++; if (self.ws_sleep < 3000 && (self.ws_cnt % 10) === 0) { self.ws_sleep += 1000; } 209 | }; self._ws_socket.onopen = function () { 210 | if (self.to) { clearTimeout(self.to); } 211 | self.ws_sleep = 1000; self.ws_cnt = 0; if (self.options.onWSConnect) { self.options.onWSConnect(self); } 212 | var req; while ((req = $.JsonRpcClient.q.pop())) { self._ws_socket.send(req); } 213 | }; 214 | } 215 | } 216 | return self._ws_socket ? true : false; 217 | }; $.JsonRpcClient.prototype._getSocket = function (onmessage_cb) { if (this.options.socketUrl === null || !("WebSocket" in window)) return null; this.connectSocket(onmessage_cb); return this._ws_socket; }; $.JsonRpcClient.q = []; $.JsonRpcClient.prototype._wsCall = function (socket, request, success_cb, error_cb) { 218 | var request_json = $.toJSON(request); if (socket.readyState < 1) { self = this; $.JsonRpcClient.q.push(request_json); } else { socket.send(request_json); } 219 | if ('id' in request && typeof success_cb !== 'undefined') { this._ws_callbacks[request.id] = { request: request_json, request_obj: request, success_cb: success_cb, error_cb: error_cb }; } 220 | }; $.JsonRpcClient.prototype._wsOnMessage = function (event) { 221 | var response; if (event.data[0] == "#" && event.data[1] == "S" && event.data[2] == "P") { 222 | if (event.data[3] == "U") { this.up_dur = parseInt(event.data.substring(4)); } else if (this.speedCB && event.data[3] == "D") { this.down_dur = parseInt(event.data.substring(4)); var up_kps = (((this.speedBytes * 8) / (this.up_dur / 1000)) / 1024).toFixed(0); var down_kps = (((this.speedBytes * 8) / (this.down_dur / 1000)) / 1024).toFixed(0); console.info("Speed Test: Up: " + up_kps + " Down: " + down_kps); this.speedCB(event, { upDur: this.up_dur, downDur: this.down_dur, upKPS: up_kps, downKPS: down_kps }); this.speedCB = null; } 223 | return; 224 | } 225 | try { 226 | response = $.parseJSON(event.data); if (typeof response === 'object' && 'jsonrpc' in response && response.jsonrpc === '2.0') { 227 | if ('result' in response && this._ws_callbacks[response.id]) { var success_cb = this._ws_callbacks[response.id].success_cb; delete this._ws_callbacks[response.id]; success_cb(response.result, this); return; } else if ('error' in response && this._ws_callbacks[response.id]) { 228 | var error_cb = this._ws_callbacks[response.id].error_cb; var orig_req = this._ws_callbacks[response.id].request; if (!self.authing && response.error.code == -32000 && self.options.login && self.options.passwd) { 229 | self.authing = true; this.call("login", { login: self.options.login, passwd: self.options.passwd, loginParams: self.options.loginParams, userVariables: self.options.userVariables }, this._ws_callbacks[response.id].request_obj.method == "login" ? function (e) { self.authing = false; console.log("logged in"); delete self._ws_callbacks[response.id]; if (self.options.onWSLogin) { self.options.onWSLogin(true, self); } } : function (e) { 230 | self.authing = false; console.log("logged in, resending request id: " + response.id); var socket = self.options.getSocket(self.wsOnMessage); if (socket !== null) { socket.send(orig_req); } 231 | if (self.options.onWSLogin) { self.options.onWSLogin(true, self); } 232 | }, function (e) { console.log("error logging in, request id:", response.id); delete self._ws_callbacks[response.id]; error_cb(response.error, this); if (self.options.onWSLogin) { self.options.onWSLogin(false, self); } }); return; 233 | } 234 | delete this._ws_callbacks[response.id]; error_cb(response.error, this); return; 235 | } 236 | } 237 | } catch (err) { console.log("ERROR: " + err); return; } 238 | if (typeof this.options.onmessage === 'function') { 239 | event.eventData = response; if (!event.eventData) { event.eventData = {}; } 240 | var reply = this.options.onmessage(event); if (reply && typeof reply === "object" && event.eventData.id) { var msg = { jsonrpc: "2.0", id: event.eventData.id, result: reply }; var socket = self.options.getSocket(self.wsOnMessage); if (socket !== null) { socket.send($.toJSON(msg)); } } 241 | } 242 | }; $.JsonRpcClient._batchObject = function (jsonrpcclient, all_done_cb, error_cb) { this._requests = []; this.jsonrpcclient = jsonrpcclient; this.all_done_cb = all_done_cb; this.error_cb = typeof error_cb === 'function' ? error_cb : function () { }; }; $.JsonRpcClient._batchObject.prototype.call = function (method, params, success_cb, error_cb) { 243 | if (!params) { params = {}; } 244 | if (this.options.sessid) { params.sessid = this.options.sessid; } 245 | if (!success_cb) { success_cb = function (e) { console.log("Success: ", e); }; } 246 | if (!error_cb) { error_cb = function (e) { console.log("Error: ", e); }; } 247 | this._requests.push({ request: { jsonrpc: '2.0', method: method, params: params, id: this.jsonrpcclient._current_id++ }, success_cb: success_cb, error_cb: error_cb }); 248 | }; $.JsonRpcClient._batchObject.prototype.notify = function (method, params) { 249 | if (this.options.sessid) { params.sessid = this.options.sessid; } 250 | this._requests.push({ request: { jsonrpc: '2.0', method: method, params: params } }); 251 | }; $.JsonRpcClient._batchObject.prototype._execute = function () { 252 | var self = this; if (this._requests.length === 0) return; var batch_request = []; var handlers = {}; var i = 0; var call; var success_cb; var error_cb; var socket = self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage); if (socket !== null) { 253 | for (i = 0; i < this._requests.length; i++) { call = this._requests[i]; success_cb = ('success_cb' in call) ? call.success_cb : undefined; error_cb = ('error_cb' in call) ? call.error_cb : undefined; self.jsonrpcclient._wsCall(socket, call.request, success_cb, error_cb); } 254 | if (typeof all_done_cb === 'function') all_done_cb(result); return; 255 | } 256 | for (i = 0; i < this._requests.length; i++) { call = this._requests[i]; batch_request.push(call.request); if ('id' in call.request) { handlers[call.request.id] = { success_cb: call.success_cb, error_cb: call.error_cb }; } } 257 | success_cb = function (data) { self._batchCb(data, handlers, self.all_done_cb); }; if (self.jsonrpcclient.options.ajaxUrl === null) { throw "$.JsonRpcClient.batch used with no websocket and no http endpoint."; } 258 | $.ajax({ url: self.jsonrpcclient.options.ajaxUrl, data: $.toJSON(batch_request), dataType: 'json', cache: false, type: 'POST', error: function (jqXHR, textStatus, errorThrown) { self.error_cb(jqXHR, textStatus, errorThrown); }, success: success_cb }); 259 | }; $.JsonRpcClient._batchObject.prototype._batchCb = function (result, handlers, all_done_cb) { 260 | for (var i = 0; i < result.length; i++) { var response = result[i]; if ('error' in response) { if (response.id === null || !(response.id in handlers)) { if ('console' in window) console.log(response); } else { handlers[response.id].error_cb(response.error, this); } } else { if (!(response.id in handlers) && 'console' in window) { console.log(response); } else { handlers[response.id].success_cb(response.result, this); } } } 261 | if (typeof all_done_cb === 'function') all_done_cb(result); 262 | }; 263 | })(jQuery); (function ($) { 264 | var sources = []; var generateGUID = (typeof (window.crypto) !== 'undefined' && typeof (window.crypto.getRandomValues) !== 'undefined') ? function () { 265 | var buf = new Uint16Array(8); window.crypto.getRandomValues(buf); var S4 = function (num) { 266 | var ret = num.toString(16); while (ret.length < 4) { ret = "0" + ret; } 267 | return ret; 268 | }; return (S4(buf[0]) + S4(buf[1]) + "-" + S4(buf[2]) + "-" + S4(buf[3]) + "-" + S4(buf[4]) + "-" + S4(buf[5]) + S4(buf[6]) + S4(buf[7])); 269 | } : function () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; $.verto = function (options, callbacks) { 270 | var verto = this; $.verto.saved.push(verto); verto.options = $.extend({ login: null, passwd: null, socketUrl: null, tag: null, localTag: null, videoParams: {}, audioParams: {}, loginParams: {}, deviceParams: { onResCheck: null }, userVariables: {}, iceServers: false, ringSleep: 6000, sessid: null }, options); if (verto.options.deviceParams.useCamera) { $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, verto.options.deviceParams.onResCheck); } 271 | if (!verto.options.deviceParams.useMic) { verto.options.deviceParams.useMic = "any"; } 272 | if (!verto.options.deviceParams.useSpeak) { verto.options.deviceParams.useSpeak = "any"; } 273 | if (verto.options.sessid) { verto.sessid = verto.options.sessid; } else { verto.sessid = localStorage.getItem("verto_session_uuid") || generateGUID(); localStorage.setItem("verto_session_uuid", verto.sessid); } 274 | verto.dialogs = {}; verto.callbacks = callbacks || {}; verto.eventSUBS = {}; verto.rpcClient = new $.JsonRpcClient({ 275 | login: verto.options.login, passwd: verto.options.passwd, socketUrl: verto.options.socketUrl, loginParams: verto.options.loginParams, userVariables: verto.options.userVariables, sessid: verto.sessid, onmessage: function (e) { return verto.handleMessage(e.eventData); }, onWSConnect: function (o) { o.call('login', {}); }, onWSLogin: function (success) { if (verto.callbacks.onWSLogin) { verto.callbacks.onWSLogin(verto, success); } }, onWSClose: function (success) { 276 | if (verto.callbacks.onWSClose) { verto.callbacks.onWSClose(verto, success); } 277 | verto.purge(); 278 | } 279 | }); if (verto.options.ringFile && verto.options.tag) { verto.ringer = $("#" + verto.options.tag); } 280 | verto.rpcClient.call('login', {}); 281 | }; $.verto.prototype.deviceParams = function (obj) { 282 | var verto = this; for (var i in obj) { verto.options.deviceParams[i] = obj[i]; } 283 | if (obj.useCamera) { $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, obj ? obj.onResCheck : undefined); } 284 | }; $.verto.prototype.videoParams = function (obj) { var verto = this; for (var i in obj) { verto.options.videoParams[i] = obj[i]; } }; $.verto.prototype.iceServers = function (obj) { var verto = this; verto.options.iceServers = obj; }; $.verto.prototype.loginData = function (params) { var verto = this; verto.options.login = params.login; verto.options.passwd = params.passwd; verto.rpcClient.loginData(params); }; $.verto.prototype.logout = function (msg) { 285 | var verto = this; verto.rpcClient.closeSocket(); if (verto.callbacks.onWSClose) { verto.callbacks.onWSClose(verto, false); } 286 | verto.purge(); 287 | }; $.verto.prototype.login = function (msg) { var verto = this; verto.logout(); verto.rpcClient.call('login', {}); }; $.verto.prototype.message = function (msg) { 288 | var verto = this; var err = 0; if (!msg.to) { console.error("Missing To"); err++; } 289 | if (!msg.body) { console.error("Missing Body"); err++; } 290 | if (err) { return false; } 291 | verto.sendMethod("verto.info", { msg: msg }); return true; 292 | }; $.verto.prototype.processReply = function (method, success, e) { 293 | var verto = this; var i; switch (method) { 294 | case "verto.subscribe": for (i in e.unauthorizedChannels) { drop_bad(verto, e.unauthorizedChannels[i]); } 295 | for (i in e.subscribedChannels) { mark_ready(verto, e.subscribedChannels[i]); } 296 | break; case "verto.unsubscribe": break; 297 | } 298 | }; $.verto.prototype.sendMethod = function (method, params) { var verto = this; verto.rpcClient.call(method, params, function (e) { verto.processReply(method, true, e); }, function (e) { verto.processReply(method, false, e); }); }; function do_sub(verto, channel, obj) { } 299 | function drop_bad(verto, channel) { console.error("drop unauthorized channel: " + channel); delete verto.eventSUBS[channel]; } 300 | function mark_ready(verto, channel) { for (var j in verto.eventSUBS[channel]) { verto.eventSUBS[channel][j].ready = true; console.log("subscribed to channel: " + channel); if (verto.eventSUBS[channel][j].readyHandler) { verto.eventSUBS[channel][j].readyHandler(verto, channel); } } } 301 | var SERNO = 1; function do_subscribe(verto, channel, subChannels, sparams) { 302 | var params = sparams || {}; var local = params.local; var obj = { eventChannel: channel, userData: params.userData, handler: params.handler, ready: false, readyHandler: params.readyHandler, serno: SERNO++ }; var isnew = false; if (!verto.eventSUBS[channel]) { verto.eventSUBS[channel] = []; subChannels.push(channel); isnew = true; } 303 | verto.eventSUBS[channel].push(obj); if (local) { obj.ready = true; obj.local = true; } 304 | if (!isnew && verto.eventSUBS[channel][0].ready) { obj.ready = true; if (obj.readyHandler) { obj.readyHandler(verto, channel); } } 305 | return { serno: obj.serno, eventChannel: channel }; 306 | } 307 | $.verto.prototype.subscribe = function (channel, sparams) { 308 | var verto = this; var r = []; var subChannels = []; var params = sparams || {}; if (typeof (channel) === "string") { r.push(do_subscribe(verto, channel, subChannels, params)); } else { for (var i in channel) { r.push(do_subscribe(verto, channel, subChannels, params)); } } 309 | if (subChannels.length) { verto.sendMethod("verto.subscribe", { eventChannel: subChannels.length == 1 ? subChannels[0] : subChannels, subParams: params.subParams }); } 310 | return r; 311 | }; $.verto.prototype.unsubscribe = function (handle) { 312 | var verto = this; var i; if (!handle) { for (i in verto.eventSUBS) { if (verto.eventSUBS[i]) { verto.unsubscribe(verto.eventSUBS[i]); } } } else { 313 | var unsubChannels = {}; var sendChannels = []; var channel; if (typeof (handle) == "string") { delete verto.eventSUBS[handle]; unsubChannels[handle]++; } else { 314 | for (i in handle) { 315 | if (typeof (handle[i]) == "string") { channel = handle[i]; delete verto.eventSUBS[channel]; unsubChannels[channel]++; } else { 316 | var repl = []; channel = handle[i].eventChannel; for (var j in verto.eventSUBS[channel]) { if (verto.eventSUBS[channel][j].serno == handle[i].serno) { } else { repl.push(verto.eventSUBS[channel][j]); } } 317 | verto.eventSUBS[channel] = repl; if (verto.eventSUBS[channel].length === 0) { delete verto.eventSUBS[channel]; unsubChannels[channel]++; } 318 | } 319 | } 320 | } 321 | for (var u in unsubChannels) { console.log("Sending Unsubscribe for: ", u); sendChannels.push(u); } 322 | if (sendChannels.length) { verto.sendMethod("verto.unsubscribe", { eventChannel: sendChannels.length == 1 ? sendChannels[0] : sendChannels }); } 323 | } 324 | }; $.verto.prototype.broadcast = function (channel, params) { 325 | var verto = this; var msg = { eventChannel: channel, data: {} }; for (var i in params) { msg.data[i] = params[i]; } 326 | verto.sendMethod("verto.broadcast", msg); 327 | }; $.verto.prototype.purge = function (callID) { 328 | var verto = this; var x = 0; var i; for (i in verto.dialogs) { 329 | if (!x) { console.log("purging dialogs"); } 330 | x++; verto.dialogs[i].setState($.verto.enum.state.purge); 331 | } 332 | for (i in verto.eventSUBS) { if (verto.eventSUBS[i]) { console.log("purging subscription: " + i); delete verto.eventSUBS[i]; } } 333 | }; $.verto.prototype.hangup = function (callID) { var verto = this; if (callID) { var dialog = verto.dialogs[callID]; if (dialog) { dialog.hangup(); } } else { for (var i in verto.dialogs) { verto.dialogs[i].hangup(); } } }; $.verto.prototype.newCall = function (args, callbacks) { 334 | var verto = this; if (!verto.rpcClient.socketReady()) { console.error("Not Connected..."); return; } 335 | var dialog = new $.verto.dialog($.verto.enum.direction.outbound, this, args); dialog.invite(); if (callbacks) { dialog.callbacks = callbacks; } 336 | return dialog; 337 | }; $.verto.prototype.handleMessage = function (data) { 338 | var verto = this; if (!(data && data.method)) { console.error("Invalid Data", data); return; } 339 | if (data.params.callID) { 340 | var dialog = verto.dialogs[data.params.callID]; if (data.method === "verto.attach" && dialog) { delete dialog.verto.dialogs[dialog.callID]; dialog.rtc.stop(); dialog = null; } 341 | if (dialog) { switch (data.method) { case 'verto.bye': dialog.hangup(data.params); break; case 'verto.answer': dialog.handleAnswer(data.params); break; case 'verto.media': dialog.handleMedia(data.params); break; case 'verto.display': dialog.handleDisplay(data.params); break; case 'verto.info': dialog.handleInfo(data.params); break; default: console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED", dialog, data.method); break; } } else { 342 | switch (data.method) { 343 | case 'verto.attach': data.params.attach = true; if (data.params.sdp && data.params.sdp.indexOf("m=video") > 0) { data.params.useVideo = true; } 344 | if (data.params.sdp && data.params.sdp.indexOf("stereo=1") > 0) { data.params.useStereo = true; } 345 | dialog = new $.verto.dialog($.verto.enum.direction.inbound, verto, data.params); dialog.setState($.verto.enum.state.recovering); break; case 'verto.invite': if (data.params.sdp && data.params.sdp.indexOf("m=video") > 0) { data.params.wantVideo = true; } 346 | if (data.params.sdp && data.params.sdp.indexOf("stereo=1") > 0) { data.params.useStereo = true; } 347 | dialog = new $.verto.dialog($.verto.enum.direction.inbound, verto, data.params); break; default: console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED"); break; 348 | } 349 | } 350 | return { method: data.method }; 351 | } else { 352 | switch (data.method) { 353 | case 'verto.punt': verto.purge(); verto.logout(); break; case 'verto.event': var list = null; var key = null; if (data.params) { key = data.params.eventChannel; } 354 | if (key) { list = verto.eventSUBS[key]; if (!list) { list = verto.eventSUBS[key.split(".")[0]]; } } 355 | if (!list && key && key === verto.sessid) { if (verto.callbacks.onMessage) { verto.callbacks.onMessage(verto, null, $.verto.enum.message.pvtEvent, data.params); } } else if (!list && key && verto.dialogs[key]) { verto.dialogs[key].sendMessage($.verto.enum.message.pvtEvent, data.params); } else if (!list) { 356 | if (!key) { key = "UNDEFINED"; } 357 | console.error("UNSUBBED or invalid EVENT " + key + " IGNORED"); 358 | } else { for (var i in list) { var sub = list[i]; if (!sub || !sub.ready) { console.error("invalid EVENT for " + key + " IGNORED"); } else if (sub.handler) { sub.handler(verto, data.params, sub.userData); } else if (verto.callbacks.onEvent) { verto.callbacks.onEvent(verto, data.params, sub.userData); } else { console.log("EVENT:", data.params); } } } 359 | break; case "verto.info": if (verto.callbacks.onMessage) { verto.callbacks.onMessage(verto, null, $.verto.enum.message.info, data.params.msg); } 360 | console.debug("MESSAGE from: " + data.params.msg.from, data.params.msg.body); break; default: console.error("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED", data.method); break; 361 | } 362 | } 363 | }; var del_array = function (array, name) { 364 | var r = []; var len = array.length; for (var i = 0; i < len; i++) { if (array[i] != name) { r.push(array[i]); } } 365 | return r; 366 | }; var hashArray = function () { 367 | var vha = this; var hash = {}; var array = []; vha.reorder = function (a) { 368 | array = a; var h = hash; hash = {}; var len = array.length; for (var i = 0; i < len; i++) { var key = array[i]; if (h[key]) { hash[key] = h[key]; delete h[key]; } } 369 | h = undefined; 370 | }; vha.clear = function () { hash = undefined; array = undefined; hash = {}; array = []; }; vha.add = function (name, val, insertAt) { 371 | var redraw = false; if (!hash[name]) { 372 | if (insertAt === undefined || insertAt < 0 || insertAt >= array.length) { array.push(name); } else { 373 | var x = 0; var n = []; var len = array.length; for (var i = 0; i < len; i++) { 374 | if (x++ == insertAt) { n.push(name); } 375 | n.push(array[i]); 376 | } 377 | array = undefined; array = n; n = undefined; redraw = true; 378 | } 379 | } 380 | hash[name] = val; return redraw; 381 | }; vha.del = function (name) { 382 | var r = false; if (hash[name]) { array = del_array(array, name); delete hash[name]; r = true; } else { console.error("can't del nonexistant key " + name); } 383 | return r; 384 | }; vha.get = function (name) { return hash[name]; }; vha.order = function () { return array; }; vha.hash = function () { return hash; }; vha.indexOf = function (name) { var len = array.length; for (var i = 0; i < len; i++) { if (array[i] == name) { return i; } } }; vha.arrayLen = function () { return array.length; }; vha.asArray = function () { 385 | var r = []; var len = array.length; for (var i = 0; i < len; i++) { var key = array[i]; r.push(hash[key]); } 386 | return r; 387 | }; vha.each = function (cb) { var len = array.length; for (var i = 0; i < len; i++) { cb(array[i], hash[array[i]]); } }; vha.dump = function (html) { var str = ""; vha.each(function (name, val) { str += "name: " + name + " val: " + JSON.stringify(val) + (html ? "
" : "\n"); }); return str; }; 388 | }; $.verto.liveArray = function (verto, context, name, config) { 389 | var la = this; var lastSerno = 0; var binding = null; var user_obj = config.userObj; var local = false; hashArray.call(la); la._add = la.add; la._del = la.del; la._reorder = la.reorder; la._clear = la.clear; la.context = context; la.name = name; la.user_obj = user_obj; la.verto = verto; la.broadcast = function (channel, obj) { verto.broadcast(channel, obj); }; la.errs = 0; la.clear = function () { la._clear(); lastSerno = 0; if (la.onChange) { la.onChange(la, { action: "clear" }); } }; la.checkSerno = function (serno) { 390 | if (serno < 0) { return true; } 391 | if (lastSerno > 0 && serno != (lastSerno + 1)) { 392 | if (la.onErr) { la.onErr(la, { lastSerno: lastSerno, serno: serno }); } 393 | la.errs++; console.debug(la.errs); if (la.errs < 3) { la.bootstrap(la.user_obj); } 394 | return false; 395 | } else { lastSerno = serno; return true; } 396 | }; la.reorder = function (serno, a) { if (la.checkSerno(serno)) { la._reorder(a); if (la.onChange) { la.onChange(la, { serno: serno, action: "reorder" }); } } }; la.init = function (serno, val, key, index) { 397 | if (key === null || key === undefined) { key = serno; } 398 | if (la.checkSerno(serno)) { if (la.onChange) { la.onChange(la, { serno: serno, action: "init", index: index, key: key, data: val }); } } 399 | }; la.bootObj = function (serno, val) { 400 | if (la.checkSerno(serno)) { 401 | for (var i in val) { la._add(val[i][0], val[i][1]); } 402 | if (la.onChange) { la.onChange(la, { serno: serno, action: "bootObj", data: val, redraw: true }); } 403 | } 404 | }; la.add = function (serno, val, key, index) { 405 | if (key === null || key === undefined) { key = serno; } 406 | if (la.checkSerno(serno)) { var redraw = la._add(key, val, index); if (la.onChange) { la.onChange(la, { serno: serno, action: "add", index: index, key: key, data: val, redraw: redraw }); } } 407 | }; la.modify = function (serno, val, key, index) { 408 | if (key === null || key === undefined) { key = serno; } 409 | if (la.checkSerno(serno)) { la._add(key, val, index); if (la.onChange) { la.onChange(la, { serno: serno, action: "modify", key: key, data: val, index: index }); } } 410 | }; la.del = function (serno, key, index) { 411 | if (key === null || key === undefined) { key = serno; } 412 | if (la.checkSerno(serno)) { 413 | if (index === null || index < 0 || index === undefined) { index = la.indexOf(key); } 414 | var ok = la._del(key); if (ok && la.onChange) { la.onChange(la, { serno: serno, action: "del", key: key, index: index }); } 415 | } 416 | }; var eventHandler = function (v, e, la) { 417 | var packet = e.data; if (packet.name != la.name) { return; } 418 | switch (packet.action) { 419 | case "init": la.init(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex); break; case "bootObj": la.bootObj(packet.wireSerno, packet.data); break; case "add": la.add(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex); break; case "modify": if (!(packet.arrIndex || packet.hashKey)) { console.error("Invalid Packet", packet); } else { la.modify(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex); } 420 | break; case "del": if (!(packet.arrIndex || packet.hashKey)) { console.error("Invalid Packet", packet); } else { la.del(packet.wireSerno, packet.hashKey, packet.arrIndex); } 421 | break; case "clear": la.clear(); break; case "reorder": la.reorder(packet.wireSerno, packet.order); break; default: if (la.checkSerno(packet.wireSerno)) { if (la.onChange) { la.onChange(la, { serno: packet.wireSerno, action: packet.action, data: packet.data }); } } 422 | break; 423 | } 424 | }; if (la.context) { binding = la.verto.subscribe(la.context, { handler: eventHandler, userData: la, subParams: config.subParams }); } 425 | la.destroy = function () { la._clear(); la.verto.unsubscribe(binding); }; la.sendCommand = function (cmd, obj) { var self = la; self.broadcast(self.context, { liveArray: { command: cmd, context: self.context, name: self.name, obj: obj } }); }; la.bootstrap = function (obj) { var self = la; la.sendCommand("bootstrap", obj); }; la.changepage = function (obj) { var self = la; self.clear(); self.broadcast(self.context, { liveArray: { command: "changepage", context: la.context, name: la.name, obj: obj } }); }; la.heartbeat = function (obj) { var self = la; var callback = function () { self.heartbeat.call(self, obj); }; self.broadcast(self.context, { liveArray: { command: "heartbeat", context: self.context, name: self.name, obj: obj } }); self.hb_pid = setTimeout(callback, 30000); }; la.bootstrap(la.user_obj); 426 | }; $.verto.liveTable = function (verto, context, name, jq, config) { 427 | var dt; var la = new $.verto.liveArray(verto, context, name, { subParams: config.subParams }); var lt = this; lt.liveArray = la; lt.dataTable = dt; lt.verto = verto; lt.destroy = function () { 428 | if (dt) { dt.fnDestroy(); } 429 | if (la) { la.destroy(); } 430 | dt = null; la = null; 431 | }; la.onErr = function (obj, args) { console.error("Error: ", obj, args); }; function genRow(data) { 432 | if (typeof (data[4]) === "string" && data[4].indexOf("{") > -1) { var tmp = $.parseJSON(data[4]); data[4] = tmp.oldStatus; data[5] = null; } 433 | return data; 434 | } 435 | function genArray(obj) { 436 | var data = obj.asArray(); for (var i in data) { data[i] = genRow(data[i]); } 437 | return data; 438 | } 439 | la.onChange = function (obj, args) { 440 | var index = 0; var iserr = 0; if (!dt) { 441 | if (!config.aoColumns) { 442 | if (args.action != "init") { return; } 443 | config.aoColumns = []; for (var i in args.data) { config.aoColumns.push({ "sTitle": args.data[i] }); } 444 | } 445 | dt = jq.dataTable(config); 446 | } 447 | if (dt && (args.action == "del" || args.action == "modify")) { 448 | index = args.index; if (index === undefined && args.key) { index = la.indexOf(args.key); } 449 | if (index === undefined) { console.error("INVALID PACKET Missing INDEX\n", args); return; } 450 | } 451 | if (config.onChange) { config.onChange(obj, args); } 452 | try { 453 | switch (args.action) { 454 | case "bootObj": if (!args.data) { console.error("missing data"); return; } 455 | dt.fnClearTable(); dt.fnAddData(genArray(obj)); dt.fnAdjustColumnSizing(); break; case "add": if (!args.data) { console.error("missing data"); return; } 456 | if (args.redraw > -1) { dt.fnClearTable(); dt.fnAddData(genArray(obj)); } else { dt.fnAddData(genRow(args.data)); } 457 | dt.fnAdjustColumnSizing(); break; case "modify": if (!args.data) { return; } 458 | dt.fnUpdate(genRow(args.data), index); dt.fnAdjustColumnSizing(); break; case "del": dt.fnDeleteRow(index); dt.fnAdjustColumnSizing(); break; case "clear": dt.fnClearTable(); break; case "reorder": dt.fnClearTable(); dt.fnAddData(genArray(obj)); break; case "hide": jq.hide(); break; case "show": jq.show(); break; 459 | } 460 | } catch (err) { console.error("ERROR: " + err); iserr++; } 461 | if (iserr) { obj.errs++; if (obj.errs < 3) { obj.bootstrap(obj.user_obj); } } else { obj.errs = 0; } 462 | }; la.onChange(la, { action: "init" }); 463 | }; var CONFMAN_SERNO = 1; $.verto.conf = function (verto, params) { var conf = this; conf.params = $.extend({ dialog: null, hasVid: false, laData: null, onBroadcast: null, onLaChange: null, onLaRow: null }, params); conf.verto = verto; conf.serno = CONFMAN_SERNO++; createMainModeratorMethods(); verto.subscribe(conf.params.laData.modChannel, { handler: function (v, e) { if (conf.params.onBroadcast) { conf.params.onBroadcast(verto, conf, e.data); } } }); verto.subscribe(conf.params.laData.chatChannel, { handler: function (v, e) { if (typeof (conf.params.chatCallback) === "function") { conf.params.chatCallback(v, e); } } }); }; $.verto.conf.prototype.modCommand = function (cmd, id, value) { var conf = this; conf.verto.rpcClient.call("verto.broadcast", { "eventChannel": conf.params.laData.modChannel, "data": { "application": "conf-control", "command": cmd, "id": id, "value": value } }); }; $.verto.conf.prototype.destroy = function () { 464 | var conf = this; conf.destroyed = true; conf.params.onBroadcast(conf.verto, conf, 'destroy'); if (conf.params.laData.modChannel) { conf.verto.unsubscribe(conf.params.laData.modChannel); } 465 | if (conf.params.laData.chatChannel) { conf.verto.unsubscribe(conf.params.laData.chatChannel); } 466 | }; function createMainModeratorMethods() { 467 | $.verto.conf.prototype.listVideoLayouts = function () { this.modCommand("list-videoLayouts", null, null); }; $.verto.conf.prototype.play = function (file) { this.modCommand("play", null, file); }; $.verto.conf.prototype.stop = function () { this.modCommand("stop", null, "all"); }; $.verto.conf.prototype.record = function (file) { this.modCommand("recording", null, ["start", file]); }; $.verto.conf.prototype.stopRecord = function () { this.modCommand("recording", null, ["stop", "all"]); }; $.verto.conf.prototype.snapshot = function (file) { 468 | if (!this.params.hasVid) { throw 'Conference has no video'; } 469 | this.modCommand("vid-write-png", null, file); 470 | }; $.verto.conf.prototype.setVideoLayout = function (layout, canvasID) { 471 | if (!this.params.hasVid) { throw 'Conference has no video'; } 472 | if (canvasID) { this.modCommand("vid-layout", null, [layout, canvasID]); } else { this.modCommand("vid-layout", null, layout); } 473 | }; $.verto.conf.prototype.kick = function (memberID) { this.modCommand("kick", parseInt(memberID)); }; $.verto.conf.prototype.muteMic = function (memberID) { this.modCommand("tmute", parseInt(memberID)); }; $.verto.conf.prototype.muteVideo = function (memberID) { 474 | if (!this.params.hasVid) { throw 'Conference has no video'; } 475 | this.modCommand("tvmute", parseInt(memberID)); 476 | }; $.verto.conf.prototype.presenter = function (memberID) { 477 | if (!this.params.hasVid) { throw 'Conference has no video'; } 478 | this.modCommand("vid-res-id", parseInt(memberID), "presenter"); 479 | }; $.verto.conf.prototype.videoFloor = function (memberID) { 480 | if (!this.params.hasVid) { throw 'Conference has no video'; } 481 | this.modCommand("vid-floor", parseInt(memberID), "force"); 482 | }; $.verto.conf.prototype.banner = function (memberID, text) { 483 | if (!this.params.hasVid) { throw 'Conference has no video'; } 484 | this.modCommand("vid-banner", parseInt(memberID), escape(text)); 485 | }; $.verto.conf.prototype.volumeDown = function (memberID) { this.modCommand("volume_out", parseInt(memberID), "down"); }; $.verto.conf.prototype.volumeUp = function (memberID) { this.modCommand("volume_out", parseInt(memberID), "up"); }; $.verto.conf.prototype.gainDown = function (memberID) { this.modCommand("volume_in", parseInt(memberID), "down"); }; $.verto.conf.prototype.gainUp = function (memberID) { this.modCommand("volume_in", parseInt(memberID), "up"); }; $.verto.conf.prototype.transfer = function (memberID, exten) { this.modCommand("transfer", parseInt(memberID), exten); }; $.verto.conf.prototype.sendChat = function (message, type) { var conf = this; conf.verto.rpcClient.call("verto.broadcast", { "eventChannel": conf.params.laData.chatChannel, "data": { "action": "send", "message": message, "type": type } }); }; 486 | } 487 | $.verto.modfuncs = {}; $.verto.confMan = function (verto, params) { 488 | var confMan = this; confMan.params = $.extend({ tableID: null, statusID: null, mainModID: null, dialog: null, hasVid: false, laData: null, onBroadcast: null, onLaChange: null, onLaRow: null }, params); confMan.verto = verto; confMan.serno = CONFMAN_SERNO++; confMan.canvasCount = confMan.params.laData.canvasCount; function genMainMod(jq) { 489 | var play_id = "play_" + confMan.serno; var stop_id = "stop_" + confMan.serno; var recording_id = "recording_" + confMan.serno; var snapshot_id = "snapshot_" + confMan.serno; var rec_stop_id = "recording_stop" + confMan.serno; var div_id = "confman_" + confMan.serno; var html = "

" + "" + "" + "" + "" + 490 | (confMan.params.hasVid ? "" : "") + "

"; jq.html(html); $.verto.modfuncs.change_video_layout = function (id, canvas_id) { var val = $("#" + id + " option:selected").text(); if (val !== "none") { confMan.modCommand("vid-layout", null, [val, canvas_id]); } }; if (confMan.params.hasVid) { 491 | for (var j = 0; j < confMan.canvasCount; j++) { var vlayout_id = "confman_vid_layout_" + j + "_" + confMan.serno; var vlselect_id = "confman_vl_select_" + j + "_" + confMan.serno; var vlhtml = "

" + "Video Layout Canvas " + (j + 1) + " " + "

"; jq.append(vlhtml); } 492 | $("#" + snapshot_id).click(function () { var file = prompt("Please enter file name", ""); if (file) { confMan.modCommand("vid-write-png", null, file); } }); 493 | } 494 | $("#" + play_id).click(function () { var file = prompt("Please enter file name", ""); if (file) { confMan.modCommand("play", null, file); } }); $("#" + stop_id).click(function () { confMan.modCommand("stop", null, "all"); }); $("#" + recording_id).click(function () { var file = prompt("Please enter file name", ""); if (file) { confMan.modCommand("recording", null, ["start", file]); } }); $("#" + rec_stop_id).click(function () { confMan.modCommand("recording", null, ["stop", "all"]); }); 495 | } 496 | function genControls(jq, rowid) { 497 | var x = parseInt(rowid); var kick_id = "kick_" + x; var canvas_in_next_id = "canvas_in_next_" + x; var canvas_in_prev_id = "canvas_in_prev_" + x; var canvas_out_next_id = "canvas_out_next_" + x; var canvas_out_prev_id = "canvas_out_prev_" + x; var canvas_in_set_id = "canvas_in_set_" + x; var canvas_out_set_id = "canvas_out_set_" + x; var layer_set_id = "layer_set_" + x; var layer_next_id = "layer_next_" + x; var layer_prev_id = "layer_prev_" + x; var tmute_id = "tmute_" + x; var tvmute_id = "tvmute_" + x; var vbanner_id = "vbanner_" + x; var tvpresenter_id = "tvpresenter_" + x; var tvfloor_id = "tvfloor_" + x; var box_id = "box_" + x; var gainup_id = "gain_in_up" + x; var gaindn_id = "gain_in_dn" + x; var volup_id = "vol_in_up" + x; var voldn_id = "vol_in_dn" + x; var transfer_id = "transfer" + x; var html = "
"; html += "General Controls
"; html += "" + "" + "" + "" + "" + "" + ""; if (confMan.params.hasVid) { 498 | html += "

Video Controls
"; html += "" + "" + "" + ""; if (confMan.canvasCount > 1) { html += "

Canvas Controls
" + "" + "" + "" + "
" + "" + "" + ""; } 499 | html += "
" + "" + "" + "" + "
"; 500 | } 501 | jq.html(html); if (!jq.data("mouse")) { $("#" + box_id).hide(); } 502 | jq.mouseover(function (e) { jq.data({ "mouse": true }); $("#" + box_id).show(); }); jq.mouseout(function (e) { jq.data({ "mouse": false }); $("#" + box_id).hide(); }); $("#" + transfer_id).click(function () { var xten = prompt("Enter Extension"); if (xten) { confMan.modCommand("transfer", x, xten); } }); $("#" + kick_id).click(function () { confMan.modCommand("kick", x); }); $("#" + layer_set_id).click(function () { var cid = prompt("Please enter layer ID", ""); if (cid) { confMan.modCommand("vid-layer", x, cid); } }); $("#" + layer_next_id).click(function () { confMan.modCommand("vid-layer", x, "next"); }); $("#" + layer_prev_id).click(function () { confMan.modCommand("vid-layer", x, "prev"); }); $("#" + canvas_in_set_id).click(function () { var cid = prompt("Please enter canvas ID", ""); if (cid) { confMan.modCommand("vid-canvas", x, cid); } }); $("#" + canvas_out_set_id).click(function () { var cid = prompt("Please enter canvas ID", ""); if (cid) { confMan.modCommand("vid-watching-canvas", x, cid); } }); $("#" + canvas_in_next_id).click(function () { confMan.modCommand("vid-canvas", x, "next"); }); $("#" + canvas_in_prev_id).click(function () { confMan.modCommand("vid-canvas", x, "prev"); }); $("#" + canvas_out_next_id).click(function () { confMan.modCommand("vid-watching-canvas", x, "next"); }); $("#" + canvas_out_prev_id).click(function () { confMan.modCommand("vid-watching-canvas", x, "prev"); }); $("#" + tmute_id).click(function () { confMan.modCommand("tmute", x); }); if (confMan.params.hasVid) { $("#" + tvmute_id).click(function () { confMan.modCommand("tvmute", x); }); $("#" + tvpresenter_id).click(function () { confMan.modCommand("vid-res-id", x, "presenter"); }); $("#" + tvfloor_id).click(function () { confMan.modCommand("vid-floor", x, "force"); }); $("#" + vbanner_id).click(function () { var text = prompt("Please enter text", ""); if (text) { confMan.modCommand("vid-banner", x, escape(text)); } }); } 503 | $("#" + gainup_id).click(function () { confMan.modCommand("volume_in", x, "up"); }); $("#" + gaindn_id).click(function () { confMan.modCommand("volume_in", x, "down"); }); $("#" + volup_id).click(function () { confMan.modCommand("volume_out", x, "up"); }); $("#" + voldn_id).click(function () { confMan.modCommand("volume_out", x, "down"); }); return html; 504 | } 505 | var atitle = ""; var awidth = 0; verto.subscribe(confMan.params.laData.chatChannel, { handler: function (v, e) { if (typeof (confMan.params.chatCallback) === "function") { confMan.params.chatCallback(v, e); } } }); if (confMan.params.laData.role === "moderator") { 506 | atitle = "Action"; awidth = 600; if (confMan.params.mainModID) { genMainMod($(confMan.params.mainModID)); $(confMan.params.displayID).html("Moderator Controls Ready

"); } else { $(confMan.params.mainModID).html(""); } 507 | verto.subscribe(confMan.params.laData.modChannel, { 508 | handler: function (v, e) { 509 | if (confMan.params.onBroadcast) { confMan.params.onBroadcast(verto, confMan, e.data); } 510 | if (e.data["conf-command"] === "list-videoLayouts") { 511 | for (var j = 0; j < confMan.canvasCount; j++) { 512 | var vlselect_id = "#confman_vl_select_" + j + "_" + confMan.serno; var vlayout_id = "#confman_vid_layout_" + j + "_" + confMan.serno; var x = 0; var options; $(vlselect_id).selectmenu({}); $(vlselect_id).selectmenu("enable"); $(vlselect_id).empty(); $(vlselect_id).append(new Option("Choose a Layout", "none")); if (e.data.responseData) { 513 | var rdata = []; for (var i in e.data.responseData) { rdata.push(e.data.responseData[i].name); } 514 | options = rdata.sort(function (a, b) { 515 | var ga = a.substring(0, 6) == "group:" ? true : false; var gb = b.substring(0, 6) == "group:" ? true : false; if ((ga || gb) && ga != gb) { return ga ? -1 : 1; } 516 | return ((a == b) ? 0 : ((a > b) ? 1 : -1)); 517 | }); for (var i in options) { $(vlselect_id).append(new Option(options[i], options[i])); x++; } 518 | } 519 | if (x) { $(vlselect_id).selectmenu('refresh', true); } else { $(vlayout_id).hide(); } 520 | } 521 | } else { 522 | if (!confMan.destroyed && confMan.params.displayID) { 523 | $(confMan.params.displayID).html(e.data.response + "

"); if (confMan.lastTimeout) { clearTimeout(confMan.lastTimeout); confMan.lastTimeout = 0; } 524 | confMan.lastTimeout = setTimeout(function () { $(confMan.params.displayID).html(confMan.destroyed ? "" : "Moderator Controls Ready

"); }, 4000); 525 | } 526 | } 527 | } 528 | }); if (confMan.params.hasVid) { confMan.modCommand("list-videoLayouts", null, null); } 529 | } 530 | var row_callback = null; if (confMan.params.laData.role === "moderator") { row_callback = function (nRow, aData, iDisplayIndex, iDisplayIndexFull) { if (!aData[5]) { var $row = $('td:eq(5)', nRow); genControls($row, aData); if (confMan.params.onLaRow) { confMan.params.onLaRow(verto, confMan, $row, aData); } } }; } 531 | confMan.lt = new $.verto.liveTable(verto, confMan.params.laData.laChannel, confMan.params.laData.laName, $(confMan.params.tableID), { subParams: { callID: confMan.params.dialog ? confMan.params.dialog.callID : null }, "onChange": function (obj, args) { $(confMan.params.statusID).text("Conference Members: " + " (" + obj.arrayLen() + " Total)"); if (confMan.params.onLaChange) { confMan.params.onLaChange(verto, confMan, $.verto.enum.confEvent.laChange, obj, args); } }, "aaData": [], "aoColumns": [{ "sTitle": "ID", "sWidth": "50" }, { "sTitle": "Number", "sWidth": "250" }, { "sTitle": "Name", "sWidth": "250" }, { "sTitle": "Codec", "sWidth": "100" }, { "sTitle": "Status", "sWidth": confMan.params.hasVid ? "200px" : "150px" }, { "sTitle": atitle, "sWidth": awidth, }], "bAutoWidth": true, "bDestroy": true, "bSort": false, "bInfo": false, "bFilter": false, "bLengthChange": false, "bPaginate": false, "iDisplayLength": 1400, "oLanguage": { "sEmptyTable": "The Conference is Empty....." }, "fnRowCallback": row_callback }); 532 | }; $.verto.confMan.prototype.modCommand = function (cmd, id, value) { var confMan = this; confMan.verto.rpcClient.call("verto.broadcast", { "eventChannel": confMan.params.laData.modChannel, "data": { "application": "conf-control", "command": cmd, "id": id, "value": value } }); }; $.verto.confMan.prototype.sendChat = function (message, type) { var confMan = this; confMan.verto.rpcClient.call("verto.broadcast", { "eventChannel": confMan.params.laData.chatChannel, "data": { "action": "send", "message": message, "type": type } }); }; $.verto.confMan.prototype.destroy = function () { 533 | var confMan = this; confMan.destroyed = true; if (confMan.lt) { confMan.lt.destroy(); } 534 | if (confMan.params.laData.chatChannel) { confMan.verto.unsubscribe(confMan.params.laData.chatChannel); } 535 | if (confMan.params.laData.modChannel) { confMan.verto.unsubscribe(confMan.params.laData.modChannel); } 536 | if (confMan.params.mainModID) { $(confMan.params.mainModID).html(""); } 537 | }; $.verto.dialog = function (direction, verto, params) { 538 | var dialog = this; dialog.params = $.extend({ useVideo: verto.options.useVideo, useStereo: verto.options.useStereo, screenShare: false, useCamera: verto.options.deviceParams.useCamera, useMic: verto.options.deviceParams.useMic, useSpeak: verto.options.deviceParams.useSpeak, tag: verto.options.tag, localTag: verto.options.localTag, login: verto.options.login, videoParams: verto.options.videoParams }, params); dialog.verto = verto; dialog.direction = direction; dialog.lastState = null; dialog.state = dialog.lastState = $.verto.enum.state.new; dialog.callbacks = verto.callbacks; dialog.answered = false; dialog.attach = params.attach || false; dialog.screenShare = params.screenShare || false; dialog.useCamera = dialog.params.useCamera; dialog.useMic = dialog.params.useMic; dialog.useSpeak = dialog.params.useSpeak; if (dialog.params.callID) { dialog.callID = dialog.params.callID; } else { dialog.callID = dialog.params.callID = generateGUID(); } 539 | if (dialog.params.tag) { dialog.audioStream = document.getElementById(dialog.params.tag); if (dialog.params.useVideo) { dialog.videoStream = dialog.audioStream; } } 540 | if (dialog.params.localTag) { dialog.localVideo = document.getElementById(dialog.params.localTag); } 541 | dialog.verto.dialogs[dialog.callID] = dialog; var RTCcallbacks = {}; if (dialog.direction == $.verto.enum.direction.inbound) { 542 | if (dialog.params.display_direction === "outbound") { dialog.params.remote_caller_id_name = dialog.params.caller_id_name; dialog.params.remote_caller_id_number = dialog.params.caller_id_number; } else { dialog.params.remote_caller_id_name = dialog.params.callee_id_name; dialog.params.remote_caller_id_number = dialog.params.callee_id_number; } 543 | if (!dialog.params.remote_caller_id_name) { dialog.params.remote_caller_id_name = "Nobody"; } 544 | if (!dialog.params.remote_caller_id_number) { dialog.params.remote_caller_id_number = "UNKNOWN"; } 545 | RTCcallbacks.onMessage = function (rtc, msg) { console.debug(msg); }; RTCcallbacks.onAnswerSDP = function (rtc, sdp) { console.error("answer sdp", sdp); }; 546 | } else { dialog.params.remote_caller_id_name = "Outbound Call"; dialog.params.remote_caller_id_number = dialog.params.destination_number; } 547 | RTCcallbacks.onICESDP = function (rtc) { 548 | console.log("RECV " + rtc.type + " SDP", rtc.mediaData.SDP); if (dialog.state == $.verto.enum.state.requesting || dialog.state == $.verto.enum.state.answering || dialog.state == $.verto.enum.state.active) { location.reload(); return; } 549 | if (rtc.type == "offer") { if (dialog.state == $.verto.enum.state.active) { dialog.setState($.verto.enum.state.requesting); dialog.sendMethod("verto.attach", { sdp: rtc.mediaData.SDP }); } else { dialog.setState($.verto.enum.state.requesting); dialog.sendMethod("verto.invite", { sdp: rtc.mediaData.SDP }); } } else { dialog.setState($.verto.enum.state.answering); dialog.sendMethod(dialog.attach ? "verto.attach" : "verto.answer", { sdp: dialog.rtc.mediaData.SDP }); } 550 | }; RTCcallbacks.onICE = function (rtc) { if (rtc.type == "offer") { console.log("offer", rtc.mediaData.candidate); return; } }; RTCcallbacks.onStream = function (rtc, stream) { console.log("stream started"); }; RTCcallbacks.onError = function (e) { console.error("ERROR:", e); dialog.hangup({ cause: "Device or Permission Error" }); }; dialog.rtc = new $.FSRTC({ callbacks: RTCcallbacks, localVideo: dialog.screenShare ? null : dialog.localVideo, useVideo: dialog.params.useVideo ? dialog.videoStream : null, useAudio: dialog.audioStream, useStereo: dialog.params.useStereo, videoParams: dialog.params.videoParams, audioParams: verto.options.audioParams, iceServers: verto.options.iceServers, screenShare: dialog.screenShare, useCamera: dialog.useCamera, useMic: dialog.useMic, useSpeak: dialog.useSpeak }); dialog.rtc.verto = dialog.verto; if (dialog.direction == $.verto.enum.direction.inbound) { if (dialog.attach) { dialog.answer(); } else { dialog.ring(); } } 551 | }; $.verto.dialog.prototype.invite = function () { var dialog = this; dialog.rtc.call(); }; $.verto.dialog.prototype.sendMethod = function (method, obj) { 552 | var dialog = this; obj.dialogParams = {}; for (var i in dialog.params) { 553 | if (i == "sdp" && method != "verto.invite" && method != "verto.attach") { continue; } 554 | obj.dialogParams[i] = dialog.params[i]; 555 | } 556 | dialog.verto.rpcClient.call(method, obj, function (e) { dialog.processReply(method, true, e); }, function (e) { dialog.processReply(method, false, e); }); 557 | }; function checkStateChange(oldS, newS) { 558 | if (newS == $.verto.enum.state.purge || $.verto.enum.states[oldS.name][newS.name]) { return true; } 559 | return false; 560 | } 561 | function find_name(id) { 562 | for (var i in $.verto.audioOutDevices) { var source = $.verto.audioOutDevices[i]; if (source.id === id) { return (source.label); } } 563 | return id; 564 | } 565 | $.verto.dialog.prototype.setAudioPlaybackDevice = function (sinkId, callback, arg) { 566 | var dialog = this; var element = dialog.audioStream; if (typeof element.sinkId !== 'undefined') { 567 | var devname = find_name(sinkId); console.info("Dialog: " + dialog.callID + " Setting speaker:", element, devname); element.setSinkId(sinkId).then(function () { console.log("Dialog: " + dialog.callID + ' Success, audio output device attached: ' + sinkId); if (callback) { callback(true, devname, arg); } }).catch(function (error) { 568 | var errorMessage = error; if (error.name === 'SecurityError') { errorMessage = "Dialog: " + dialog.callID + ' You need to use HTTPS for selecting audio output ' + 'device: ' + error; } 569 | if (callback) { callback(false, null, arg); } 570 | console.error(errorMessage); 571 | }); 572 | } else { console.warn("Dialog: " + dialog.callID + ' Browser does not support output device selection.'); if (callback) { callback(false, null, arg); } } 573 | } 574 | $.verto.dialog.prototype.setState = function (state) { 575 | var dialog = this; if (dialog.state == $.verto.enum.state.ringing) { dialog.stopRinging(); } 576 | if (dialog.state == state || !checkStateChange(dialog.state, state)) { console.error("Dialog " + dialog.callID + ": INVALID state change from " + dialog.state.name + " to " + state.name); dialog.hangup(); return false; } 577 | console.log("Dialog " + dialog.callID + ": state change from " + dialog.state.name + " to " + state.name); dialog.lastState = dialog.state; dialog.state = state; if (!dialog.causeCode) { dialog.causeCode = 16; } 578 | if (!dialog.cause) { dialog.cause = "NORMAL CLEARING"; } 579 | if (dialog.callbacks.onDialogState) { dialog.callbacks.onDialogState(this); } 580 | switch (dialog.state) { 581 | case $.verto.enum.state.early: case $.verto.enum.state.active: var speaker = dialog.useSpeak; console.info("Using Speaker: ", speaker); if (speaker && speaker !== "any" && speaker !== "none") { setTimeout(function () { dialog.setAudioPlaybackDevice(speaker); }, 500); } 582 | break; case $.verto.enum.state.trying: setTimeout(function () { if (dialog.state == $.verto.enum.state.trying) { dialog.setState($.verto.enum.state.hangup); } }, 30000); break; case $.verto.enum.state.purge: dialog.setState($.verto.enum.state.destroy); break; case $.verto.enum.state.hangup: if (dialog.lastState.val > $.verto.enum.state.requesting.val && dialog.lastState.val < $.verto.enum.state.hangup.val) { dialog.sendMethod("verto.bye", {}); } 583 | dialog.setState($.verto.enum.state.destroy); break; case $.verto.enum.state.destroy: delete dialog.verto.dialogs[dialog.callID]; if (dialog.params.screenShare) { dialog.rtc.stopPeer(); } else { dialog.rtc.stop(); } 584 | break; 585 | } 586 | return true; 587 | }; $.verto.dialog.prototype.processReply = function (method, success, e) { 588 | var dialog = this; switch (method) { 589 | case "verto.answer": case "verto.attach": if (success) { dialog.setState($.verto.enum.state.active); } else { dialog.hangup(); } 590 | break; case "verto.invite": if (success) { dialog.setState($.verto.enum.state.trying); } else { dialog.setState($.verto.enum.state.destroy); } 591 | break; case "verto.bye": dialog.hangup(); break; case "verto.modify": if (e.holdState) { if (e.holdState == "held") { if (dialog.state != $.verto.enum.state.held) { dialog.setState($.verto.enum.state.held); } } else if (e.holdState == "active") { if (dialog.state != $.verto.enum.state.active) { dialog.setState($.verto.enum.state.active); } } } 592 | if (success) { } 593 | break; default: break; 594 | } 595 | }; $.verto.dialog.prototype.hangup = function (params) { 596 | var dialog = this; if (params) { 597 | if (params.causeCode) { dialog.causeCode = params.causeCode; } 598 | if (params.cause) { dialog.cause = params.cause; } 599 | } 600 | if (dialog.state.val >= $.verto.enum.state.new.val && dialog.state.val < $.verto.enum.state.hangup.val) { dialog.setState($.verto.enum.state.hangup); } else if (dialog.state.val < $.verto.enum.state.destroy) { dialog.setState($.verto.enum.state.destroy); } 601 | }; $.verto.dialog.prototype.stopRinging = function () { var dialog = this; if (dialog.verto.ringer) { dialog.verto.ringer.stop(); } }; $.verto.dialog.prototype.indicateRing = function () { var dialog = this; if (dialog.verto.ringer) { dialog.verto.ringer.attr("src", dialog.verto.options.ringFile)[0].play(); setTimeout(function () { dialog.stopRinging(); if (dialog.state == $.verto.enum.state.ringing) { dialog.indicateRing(); } }, dialog.verto.options.ringSleep); } }; $.verto.dialog.prototype.ring = function () { var dialog = this; dialog.setState($.verto.enum.state.ringing); dialog.indicateRing(); }; $.verto.dialog.prototype.useVideo = function (on) { 602 | var dialog = this; dialog.params.useVideo = on; if (on) { dialog.videoStream = dialog.audioStream; } else { dialog.videoStream = null; } 603 | dialog.rtc.useVideo(dialog.videoStream, dialog.localVideo); 604 | }; $.verto.dialog.prototype.setMute = function (what) { var dialog = this; return dialog.rtc.setMute(what); }; $.verto.dialog.prototype.getMute = function () { var dialog = this; return dialog.rtc.getMute(); }; $.verto.dialog.prototype.setVideoMute = function (what) { var dialog = this; return dialog.rtc.setVideoMute(what); }; $.verto.dialog.prototype.getVideoMute = function () { var dialog = this; return dialog.rtc.getVideoMute(); }; $.verto.dialog.prototype.useStereo = function (on) { var dialog = this; dialog.params.useStereo = on; dialog.rtc.useStereo(on); }; $.verto.dialog.prototype.dtmf = function (digits) { var dialog = this; if (digits) { dialog.sendMethod("verto.info", { dtmf: digits }); } }; $.verto.dialog.prototype.transfer = function (dest, params) { var dialog = this; if (dest) { dialog.sendMethod("verto.modify", { action: "transfer", destination: dest, params: params }); } }; $.verto.dialog.prototype.hold = function (params) { var dialog = this; dialog.sendMethod("verto.modify", { action: "hold", params: params }); }; $.verto.dialog.prototype.unhold = function (params) { var dialog = this; dialog.sendMethod("verto.modify", { action: "unhold", params: params }); }; $.verto.dialog.prototype.toggleHold = function (params) { var dialog = this; dialog.sendMethod("verto.modify", { action: "toggleHold", params: params }); }; $.verto.dialog.prototype.message = function (msg) { 605 | var dialog = this; var err = 0; msg.from = dialog.params.login; if (!msg.to) { console.error("Missing To"); err++; } 606 | if (!msg.body) { console.error("Missing Body"); err++; } 607 | if (err) { return false; } 608 | dialog.sendMethod("verto.info", { msg: msg }); return true; 609 | }; $.verto.dialog.prototype.answer = function (params) { 610 | var dialog = this; if (!dialog.answered) { 611 | if (!params) { params = {}; } 612 | params.sdp = dialog.params.sdp; if (params) { 613 | if (params.useVideo) { dialog.useVideo(true); } 614 | dialog.params.callee_id_name = params.callee_id_name; dialog.params.callee_id_number = params.callee_id_number; if (params.useCamera) { dialog.useCamera = params.useCamera; } 615 | if (params.useMic) { dialog.useMic = params.useMic; } 616 | if (params.useSpeak) { dialog.useSpeak = params.useSpeak; } 617 | } 618 | dialog.rtc.createAnswer(params); dialog.answered = true; 619 | } 620 | }; $.verto.dialog.prototype.handleAnswer = function (params) { 621 | var dialog = this; dialog.gotAnswer = true; if (dialog.state.val >= $.verto.enum.state.active.val) { return; } 622 | if (dialog.state.val >= $.verto.enum.state.early.val) { dialog.setState($.verto.enum.state.active); } else { if (dialog.gotEarly) { console.log("Dialog " + dialog.callID + " Got answer while still establishing early media, delaying..."); } else { console.log("Dialog " + dialog.callID + " Answering Channel"); dialog.rtc.answer(params.sdp, function () { dialog.setState($.verto.enum.state.active); }, function (e) { console.error(e); dialog.hangup(); }); console.log("Dialog " + dialog.callID + "ANSWER SDP", params.sdp); } } 623 | }; $.verto.dialog.prototype.cidString = function (enc) { var dialog = this; var party = dialog.params.remote_caller_id_name + (enc ? " <" : " <") + dialog.params.remote_caller_id_number + (enc ? ">" : ">"); return party; }; $.verto.dialog.prototype.sendMessage = function (msg, params) { var dialog = this; if (dialog.callbacks.onMessage) { dialog.callbacks.onMessage(dialog.verto, dialog, msg, params); } }; $.verto.dialog.prototype.handleInfo = function (params) { var dialog = this; dialog.sendMessage($.verto.enum.message.info, params.msg); }; $.verto.dialog.prototype.handleDisplay = function (params) { 624 | var dialog = this; if (params.display_name) { dialog.params.remote_caller_id_name = params.display_name; } 625 | if (params.display_number) { dialog.params.remote_caller_id_number = params.display_number; } 626 | dialog.sendMessage($.verto.enum.message.display, {}); 627 | }; $.verto.dialog.prototype.handleMedia = function (params) { 628 | var dialog = this; if (dialog.state.val >= $.verto.enum.state.early.val) { return; } 629 | dialog.gotEarly = true; dialog.rtc.answer(params.sdp, function () { console.log("Dialog " + dialog.callID + "Establishing early media"); dialog.setState($.verto.enum.state.early); if (dialog.gotAnswer) { console.log("Dialog " + dialog.callID + "Answering Channel"); dialog.setState($.verto.enum.state.active); } }, function (e) { console.error(e); dialog.hangup(); }); console.log("Dialog " + dialog.callID + "EARLY SDP", params.sdp); 630 | }; $.verto.ENUM = function (s) { var i = 0, o = {}; s.split(" ").map(function (x) { o[x] = { name: x, val: i++ }; }); return Object.freeze(o); }; $.verto.enum = {}; $.verto.enum.states = Object.freeze({ new: { requesting: 1, recovering: 1, ringing: 1, destroy: 1, answering: 1, hangup: 1 }, requesting: { trying: 1, hangup: 1, active: 1 }, recovering: { answering: 1, hangup: 1 }, trying: { active: 1, early: 1, hangup: 1 }, ringing: { answering: 1, hangup: 1 }, answering: { active: 1, hangup: 1 }, active: { answering: 1, requesting: 1, hangup: 1, held: 1 }, held: { hangup: 1, active: 1 }, early: { hangup: 1, active: 1 }, hangup: { destroy: 1 }, destroy: {}, purge: { destroy: 1 } }); $.verto.enum.state = $.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge"); $.verto.enum.direction = $.verto.ENUM("inbound outbound"); $.verto.enum.message = $.verto.ENUM("display info pvtEvent"); $.verto.enum = Object.freeze($.verto.enum); $.verto.saved = []; $.verto.unloadJobs = []; $(window).bind('beforeunload', function () { 631 | for (var f in $.verto.unloadJobs) { $.verto.unloadJobs[f](); } 632 | for (var i in $.verto.saved) { var verto = $.verto.saved[i]; if (verto) { verto.purge(); verto.logout(); } } 633 | return $.verto.warnOnUnload; 634 | }); $.verto.videoDevices = []; $.verto.audioInDevices = []; $.verto.audioOutDevices = []; var checkDevices = function (runtime) { 635 | console.info("enumerating devices"); var aud_in = [], aud_out = [], vid = []; if ((!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) && MediaStreamTrack.getSources) { 636 | MediaStreamTrack.getSources(function (media_sources) { 637 | for (var i = 0; i < media_sources.length; i++) { if (media_sources[i].kind == 'video') { vid.push(media_sources[i]); } else { aud_in.push(media_sources[i]); } } 638 | $.verto.videoDevices = vid; $.verto.audioInDevices = aud_in; console.info("Audio Devices", $.verto.audioInDevices); console.info("Video Devices", $.verto.videoDevices); runtime(true); 639 | }); 640 | } else { 641 | if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { console.log("enumerateDevices() not supported."); return; } 642 | navigator.mediaDevices.enumerateDevices().then(function (devices) { devices.forEach(function (device) { console.log(device); console.log(device.kind + ": " + device.label + " id = " + device.deviceId); if (device.kind === "videoinput") { vid.push({ id: device.deviceId, kind: "video", label: device.label }); } else if (device.kind === "audioinput") { aud_in.push({ id: device.deviceId, kind: "audio_in", label: device.label }); } else if (device.kind === "audiooutput") { aud_out.push({ id: device.deviceId, kind: "audio_out", label: device.label }); } }); $.verto.videoDevices = vid; $.verto.audioInDevices = aud_in; $.verto.audioOutDevices = aud_out; console.info("Audio IN Devices", $.verto.audioInDevices); console.info("Audio Out Devices", $.verto.audioOutDevices); console.info("Video Devices", $.verto.videoDevices); runtime(true); }).catch(function (err) { console.log(" Device Enumeration ERROR: " + err.name + ": " + err.message); runtime(false); }); 643 | } 644 | }; $.verto.refreshDevices = function (runtime) { checkDevices(runtime); } 645 | $.verto.init = function (obj, runtime) { 646 | if (!obj) { obj = {}; } 647 | if (!obj.skipPermCheck && !obj.skipDeviceCheck) { $.FSRTC.checkPerms(function (status) { checkDevices(runtime); }, true, true); } else if (obj.skipPermCheck && !obj.skipDeviceCheck) { checkDevices(runtime); } else if (!obj.skipPermCheck && obj.skipDeviceCheck) { $.FSRTC.checkPerms(function (status) { runtime(status); }, true, true); } else { runtime(null); } 648 | } 649 | $.verto.genUUID = function () { return generateGUID(); } 650 | })(jQuery); -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | var server = require('../../build/dev-server.js') 4 | 5 | server.ready.then(() => { 6 | // 2. run the nightwatch test suite against it 7 | // to run in additional browsers: 8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 9 | // 2. add it to the --env flag below 10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 11 | // For more information on Nightwatch's config file, see 12 | // http://nightwatchjs.org/guide#settings-file 13 | var opts = process.argv.slice(2) 14 | if (opts.indexOf('--config') === -1) { 15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 16 | } 17 | if (opts.indexOf('--env') === -1) { 18 | opts = opts.concat(['--env', 'chrome']) 19 | } 20 | 21 | var spawn = require('cross-spawn') 22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 23 | 24 | runner.on('exit', function (code) { 25 | server.close() 26 | process.exit(code) 27 | }) 28 | 29 | runner.on('error', function (err) { 30 | server.close() 31 | throw err 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build/webpack.test.conf') 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], 16 | reporters: ['spec', 'coverage'], 17 | files: ['./index.js'], 18 | preprocessors: { 19 | './index.js': ['webpack', 'sourcemap'] 20 | }, 21 | webpack: webpackConfig, 22 | webpackMiddleware: { 23 | noInfo: true 24 | }, 25 | coverageReporter: { 26 | dir: './coverage', 27 | reporters: [ 28 | { type: 'lcov', subdir: '.' }, 29 | { type: 'text-summary' } 30 | ] 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /test/unit/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Hello from '@/components/Hello' 3 | 4 | describe('Hello.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(Hello) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .to.equal('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | --------------------------------------------------------------------------------