mirror of
https://github.com/Luzifer/browserphone.git
synced 2024-11-08 05:40:07 +00:00
initial version
This commit is contained in:
commit
d734ea9f3a
11 changed files with 7336 additions and 0 deletions
1
.dockerignore
Symbolic link
1
.dockerignore
Symbolic link
|
@ -0,0 +1 @@
|
|||
.gitignore
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
dist/app.js
|
||||
node_modules
|
31
Dockerfile
Normal file
31
Dockerfile
Normal file
|
@ -0,0 +1,31 @@
|
|||
FROM node:alpine as builder
|
||||
|
||||
COPY . /src
|
||||
WORKDIR /src
|
||||
|
||||
RUN set -ex \
|
||||
&& npm ci \
|
||||
&& npm run build
|
||||
|
||||
RUN set -ex \
|
||||
&& apk add curl \
|
||||
&& curl -sSfLo /tmp/dumb-init "https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64" \
|
||||
&& chmod +x /tmp/dumb-init \
|
||||
&& curl -sSfL "https://github.com/Luzifer/korvike/releases/download/v0.5.0/korvike_linux_amd64.tar.gz" | tar -xz -C /tmp
|
||||
|
||||
|
||||
FROM python:3-alpine
|
||||
|
||||
LABEL maintainer Knut Ahlers <knut@ahlers.me>
|
||||
|
||||
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
COPY --from=builder /tmp/dumb-init /usr/local/bin/dumb-init
|
||||
COPY --from=builder /tmp/korvike_linux_amd64 /usr/local/bin/korvike
|
||||
COPY --from=builder /src/dist /usr/local/share/browserphone/dist
|
||||
|
||||
RUN set -ex \
|
||||
&& apk --no-cache add bash
|
||||
|
||||
EXPOSE 3000/tcp
|
||||
WORKDIR "/usr/local/share/browserphone"
|
||||
CMD ["/usr/local/bin/docker-entrypoint.sh"]
|
22
dist/index.html
vendored
Normal file
22
dist/index.html
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
|
||||
integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
|
||||
|
||||
<title>BrowserPhone</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.capabilityTokenURL = '{{ env `CAP_TOKEN_URL` }}'
|
||||
</script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
7
docker-entrypoint.sh
Executable file
7
docker-entrypoint.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/usr/local/bin/dumb-init /bin/bash
|
||||
set -euxo pipefail
|
||||
|
||||
ln dist/app.js app.js
|
||||
korvike -i dist/index.html -o index.html
|
||||
|
||||
exec python -m http.server 3000
|
7009
package-lock.json
generated
Normal file
7009
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
30
package.json
Normal file
30
package.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"bootstrap": "^4.3.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.19",
|
||||
"bootswatch": "^4.3.1",
|
||||
"css-loader": "^1.0.0",
|
||||
"node-sass": "^4.9.2",
|
||||
"popper.js": "^1.15.0",
|
||||
"sass-loader": "^7.0.3",
|
||||
"style-loader": "^0.21.0",
|
||||
"twilio-client": "^1.7.3",
|
||||
"vue": "^2.6.10",
|
||||
"vue-i18n": "^8.11.2",
|
||||
"vue-loader": "^15.7.0",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"webpack": "^4.16.2",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-middleware": "^1.4.0",
|
||||
"webpack-hot-middleware": "^2.9.1"
|
||||
},
|
||||
"name": "browserphone",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack -p"
|
||||
}
|
||||
}
|
154
src/app.vue
Normal file
154
src/app.vue
Normal file
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<b-navbar toggleable="lg" type="dark" variant="primary">
|
||||
<b-navbar-brand href="#">BrowserPhone</b-navbar-brand>
|
||||
|
||||
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
|
||||
|
||||
<b-collapse id="nav-collapse" is-nav>
|
||||
<b-navbar-nav class="ml-auto">
|
||||
<b-nav-text v-if="identity">Signed in as <strong>{{ identity }}</strong></b-nav-text>
|
||||
</b-navbar-nav>
|
||||
</b-collapse>
|
||||
</b-navbar>
|
||||
|
||||
<b-container>
|
||||
<b-row class="justify-content-center mt-5">
|
||||
<b-col cols="12" md="6">
|
||||
|
||||
<b-card>
|
||||
<b-form-input size="lg" slot="header" type="tel" class="my-2 number border-info" autofocus v-model="number"></b-form-input>
|
||||
|
||||
<b-row>
|
||||
<b-col v-for="key in keys" cols="4" class="text-center mb-3" :key="key">
|
||||
<b-btn size="lg" @click="keyDown(key)">{{ key }}</b-btn>
|
||||
</b-col>
|
||||
|
||||
<b-col cols="4" class="text-center mb-2">
|
||||
<b-btn size="lg" :variant="ongoingCall ? 'danger' : 'success'" @click="toggleCall">
|
||||
<i :class="{ 'fas': true, 'fa-phone': !ongoingCall, 'fa-phone-slash': ongoingCall }"></i>
|
||||
</b-btn>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<div slot="footer" v-if="state">
|
||||
{{ state }}
|
||||
</div>
|
||||
</b-card>
|
||||
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import Twilio from 'twilio-client'
|
||||
|
||||
import config from './config.js'
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
|
||||
computed: {
|
||||
keys() {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, '+', 0]
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
identity: null,
|
||||
number: '',
|
||||
ongoingCall: false,
|
||||
phone: {
|
||||
conn: null,
|
||||
device: null,
|
||||
},
|
||||
state: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
announceStatus(state) {
|
||||
this.state = state
|
||||
window.setTimeout(() => {
|
||||
this.state = ''
|
||||
}, 2000)
|
||||
},
|
||||
|
||||
keyDown(key) {
|
||||
this.number = `${this.number}${key}`
|
||||
},
|
||||
|
||||
setupPhone() {
|
||||
if (!this.phone.device) {
|
||||
this.phone.device = new Twilio.Device()
|
||||
this.phone.device.on('ready', () => this.announceStatus('Phone ready...'))
|
||||
this.phone.device.on('error', err => console.error(err))
|
||||
this.phone.device.on('connect', conn => {
|
||||
this.phone.conn = conn
|
||||
this.ongoingCall = true
|
||||
this.announeState('Call connected')
|
||||
})
|
||||
this.phone.device.on('disconnect', () => {
|
||||
this.phone.conn = null
|
||||
this.ongoingCall = false
|
||||
this.announeState('Call disconnected')
|
||||
})
|
||||
this.phone.device.on('offline', () => {
|
||||
this.announceStatus('Phone disconnected, reconnecting...')
|
||||
this.setupPhone()
|
||||
})
|
||||
}
|
||||
|
||||
const opts = { codecPreferences: ['opus', 'pcmu'], fakeLocalDTMF: true }
|
||||
|
||||
axios.get(config.capabilityTokenURL)
|
||||
.then(resp => {
|
||||
this.identity = resp.data.identity
|
||||
this.phone.device.setup(resp.data.token, opts)
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
},
|
||||
|
||||
toggleCall() {
|
||||
if (this.ongoingCall) {
|
||||
this.phone.device.disconnectAll()
|
||||
return
|
||||
}
|
||||
|
||||
// handle incoming
|
||||
|
||||
if (this.number) {
|
||||
this.announceStatus(`Dialing ${this.number}...`)
|
||||
this.phone.device.connect({
|
||||
To: this.number,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.announceStatus('Phone loaded...')
|
||||
this.setupPhone()
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.btn-lg {
|
||||
font-size: 2rem;
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
}
|
||||
input.form-control-lg {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.number {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
3
src/config.js
Normal file
3
src/config.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
capabilityTokenURL: window.capabilityTokenURL,
|
||||
}
|
17
src/main.js
Normal file
17
src/main.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
import BootstrapVue from 'bootstrap-vue'
|
||||
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
import 'bootswatch/dist/darkly/bootstrap.css'
|
||||
|
||||
import app from './app.vue'
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
new Vue({
|
||||
components: { app },
|
||||
el: '#app',
|
||||
render: c => c('app'),
|
||||
})
|
60
webpack.config.js
Normal file
60
webpack.config.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
const path = require('path')
|
||||
const webpack = require('webpack');
|
||||
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, dir)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
entry: './src/main.js',
|
||||
output: {
|
||||
filename: 'app.js',
|
||||
path: resolve('./dist'),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'NODE_ENV': JSON.stringify('production')
|
||||
}
|
||||
}),
|
||||
new VueLoaderPlugin(),
|
||||
],
|
||||
optimization: {
|
||||
minimize: true
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
||||
{
|
||||
test: /\.(s?)css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
['env', { "targets": { "browsers": [">0.25%", "not ie 11", "not op_mini all"] } }]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue