├── .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 | 
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 |
3 |
4 |
5 |
6 |
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 |
6 |
7 |
22 |
23 |
24 |
71 |
72 |
--------------------------------------------------------------------------------
/src/components/evaluate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | -
7 | {{items.scoreTitle}}
8 |
12 |
13 |
14 |
15 |
确 定
16 |
17 |
18 |
19 |
20 |
47 |
48 |
--------------------------------------------------------------------------------
/src/components/toast.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
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 |
2 |
3 |
28 |
29 |
30 |
31 |
32 | -
33 |
34 |
{{messageList.time}}
35 |
36 |
37 |

39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
{{messageList.time}}
49 |
50 |
51 |

52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
113 |
114 |
115 |
118 |
119 |
120 |
121 | 显示大窗
122 |
123 |
124 |
125 |
126 |
客服
127 | 显示小窗
128 |
129 |
130 |
131 |
132 | 顾客
133 |
134 |

135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
{{toastText}}
143 |
144 |
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 |
--------------------------------------------------------------------------------