1
0
Fork 0
mirror of https://github.com/Luzifer/browserphone.git synced 2024-11-08 05:40:07 +00:00

initial version

This commit is contained in:
Knut Ahlers 2019-05-28 22:59:00 +02:00
commit d734ea9f3a
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
11 changed files with 7336 additions and 0 deletions

1
.dockerignore Symbolic link
View file

@ -0,0 +1 @@
.gitignore

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
dist/app.js
node_modules

31
Dockerfile Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

30
package.json Normal file
View 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
View 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
View file

@ -0,0 +1,3 @@
export default {
capabilityTokenURL: window.capabilityTokenURL,
}

17
src/main.js Normal file
View 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
View 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',
},
]
}
}