coturn

firewallを超える場合に、turnサーバーでドメインを指定する

$ sudo cp /usr/local/etc/turnserver.conf.default /usr/local/etc/turnserver.conf
$ sudo vi /usr/local/etc/turnserver.conf

# The default realm to be used for the users when no explicit 
# origin/realm relationship was found in the database, or if the TURN
# server is not using any database (just the commands-line settings
# and the userdb file). Must be used with long-term credentials 
# mechanism or with TURN REST API.
#
#realm=mycompany.org
...

# Uncomment if no UDP client listener is desired.
# By default UDP client listener is always started.
#
#no-udp

# Uncomment if no TCP client listener is desired.
# By default TCP client listener is always started.
#
#no-tcp

# Uncomment if no TLS client listener is desired.
# By default TLS client listener is always started.
#
#no-tls

# Uncomment if no DTLS client listener is desired.
# By default DTLS client listener is always started.
#
#no-dtls

ってことはSTUNは外部で調達して、TURNはインスタンスで立てるって理解でOK?

NATとSTUN・TURNサーバー

WebRTC通信
– Peer to Peer、ブラウザ間で直接通信
– UDP/IPを使用、オーバーヘッドが少ない
– 鍵交換で暗号化通信を行う
-> 相手のIPアドレス、動的なUDPポート番号を知る必要がある
-> 通信経路の仕組みがInteractive Connectivity Establishment、その候補がICE Candidate
–> NATを通過するためのSTUNサーバから取得したポートマッピング
–> Firewallを越えるための、TURNによるリレーサーバーを介した中継通信
–> このやりとりをシグナリングと言う。WebSocketなど複数の方法がある
-> 複数人通信の場合には、それぞれのユーザとSDP/IPのconnectionをつくる必要がある

NATとは
 グローバルIPとローカルのネットワークIPとの変換
 複数のPC/デバイスが同時に通信できるよう、ポートマッピングによるポート変換
 →ブラウザはローカルのIP、UDPポートはわかるが、グローバルのIP、UDPはわからない
 →→Peer to Peerはグローバルの情報を交換する必要がある

STUN(Session Traversal Utilities for NATs)
NATで変換されたIP/UDPを外のSTUNサーバーから教えてもらう
→グローバル情報をシグナリングサーバーけいゆうで相手に渡す
STUNサーバーはGoogleのstun.l.google.com:19302など

TURN(Traversal Using Relays around NAT)
ストリームデータの受け渡しにリレーする
TURNサーバが入ると厳密にはPeer to Peerではなくなる
データのデコード、エンコードは行わないので、ネットワーク負荷が高くなる

あれ、Bitcoinって、P2Pだけど、STAN, TURN使ってるんだっけ?
否、BitcoinはTCP😂

php build-in serverでhttpsサーバーを起動

ローカル環境で、httpsの挙動を確認したい時に使えるのが、hyper-builtinというライブラリ
https://github.com/mpyw/php-hyper-builtin-server

opensslでサーバー証明書を生成し、composerでhyper-builtinを入れて起動
※下はawslinuxだが、centosでも同様

### sslモジュールインストール(centOSの場合はmod_ssl)
$ sudo yum install mod24_ssl
$ httpd -M | grep ssl

### 秘密鍵作成
$ openssl genrsa > server.key

### CSR作成
$ openssl req -new -key server.key > server.csr

### サーバー証明書作成
$ openssl x509 -req -signkey server.key < server.csr > server.crt
$ rm server.csr

### 秘密鍵&サーバー証明書配置
$ sudo mkdir /etc/httpd/conf/ssl.key
$ sudo mkdir /etc/httpd/conf/ssl.crt
$ sudo mv server.key /etc/httpd/conf/ssl.key/
$ sudo mv server.crt /etc/httpd/conf/ssl.crt/

### ssl.conf編集
sudo vi /etc/httpd/conf.d/ssl.conf

# SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateFile /etc/httpd/conf/ssl.crt/server.crt
# SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
SSLCertificateKeyFile /etc/httpd/conf/ssl.key/server.key

### apache再起動
$ sudo service httpd restart

### composerでhttps用のphp buildin-server libraryインストール
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require –dev mpyw/php-hyper-builtin-server:^2.0

### httpsサーバー起動
$ vendor/bin/hyper-run -s 192.168.33.10:8000

うおおおおおおおおおおおおおお、めんどくせええええええええええええ
これ、playbook.ymlで一括管理してーーーーーーーーーーーーー

Vagrant AmazonLinuxでWebRTC

まず、WebRTCとは?
-WebReal-Time Communicationsの略
-ウェブでシンプルなAPI経由でリアルタイム通信を提供する
-P2P通信
-オープンソース

アーキテクチャ

セッションでやりとりしてるんか。。

-getUserMedia
ブラウザから端末に取り付けられているカメラやマイクにアクセスしてストリームデータを取得
-RTCPeerConnection
マルチメディアセッションを確立するAPI
-RTCDataChannel
テキストデータ、バイナリデータのP2Pデータ通信API

vagrant でvideoのテスト

<!doctype html>
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title>Wrap old and new getUserMedia</title>
</head>
<body>
  Wrap old and new getUserMedia<br />
  <button type="button" onclick="startVideo();">Start</button>
  <button type="button" onclick="stopVideo();">Stop</button>
  <br />
  <video id="local_video" autoplay style="width: 320px; height: 240px; border: 1px solid black;"></video>
</body>
<script type="text/javascript">
  var localVideo = document.getElementById('local_video');
  var localStream = null;

  // --- prefix -----
  navigator.getUserMedia  = navigator.getUserMedia    || navigator.webkitGetUserMedia ||
                            navigator.mozGetUserMedia || navigator.msGetUserMedia;

  // ---------------------- video handling ----------------------- 
  // start local video
  function startVideo() {
    getDeviceStream({video: true, audio: false})
    .then(function (stream) { // success
      localStream = stream;
      playVideo(localVideo, stream);
    }).catch(function (error) { // error
      console.error('getUserMedia error:', error);
      return;
    });
  }

  // stop local video
  function stopVideo() {
    pauseVideo(localVideo);
    stopLocalStream(localStream);
  }

  function stopLocalStream(stream) {
    let tracks = stream.getTracks();
    if (! tracks) {
      console.warn('NO tracks');
      return;
    }
    
    for (let track of tracks) {
      track.stop();
    }
  }
  
  function getDeviceStream(option) {
    if ('getUserMedia' in navigator.mediaDevices) {
      console.log('navigator.mediaDevices.getUserMadia');
      return navigator.mediaDevices.getUserMedia(option);
    }
    else {
      console.log('wrap navigator.getUserMadia with Promise');
      return new Promise(function(resolve, reject){    
        navigator.getUserMedia(option,
          resolve,
          reject
        );
      });      
    }
  }

  function playVideo(element, stream) {
    if ('srcObject' in element) {
      element.srcObject = stream;
    }
    else {
      element.src = window.URL.createObjectURL(stream);
    }
    element.play();
    element.volume = 0;
  }

  function pauseVideo(element) {
    element.pause();
    if ('srcObject' in element) {
      element.srcObject = null;
    }
    else {
      if (element.src && (element.src !== '') ) {
        window.URL.revokeObjectURL(element.src);
      }
      element.src = '';
    }
  }
</script>
</html>

built in
[vagrant@localhost webrtc]$ php -S 192.168.33.10:8000
あれ?
なに、みれないぞ。。

ソースコードがおかしいか?
同じソースコードで、*.github.ioにcommitして確認
=> 見れる

なにいいいいいいいいいいいいいいいいいいいいい
server側の設定か?
そんなばかな。。。 
=> 2時間くらい調査
=> 少し疲れたので休憩

=> あれ、videoタグの設定か?
=> chromeのカメラの設定を確認

httpだと、カメラのアクセスがブロックされるのね。。。
amazon linuxにmod-sslを入れます。

[vagrant@localhost webrtc]$ sudo yum install -y mod_ssl
–> Finished Dependency Resolution
Error: httpd24 conflicts with httpd-2.2.34-1.15.amzn1.x86_64
Error: httpd24-tools conflicts with httpd-tools-2.2.34-1.15.amzn1.x86_64

ぎゃああああああああああああああああああああああ

[vagrant@localhost webrtc]$ sudo yum install mod24_ssl
Complete!

もうやだ。

tableでtd要素の中のradio checkedによって下のtr要素を表示・非表示に切り替えたい時

tableの入力画面で、例えばradioボタンの選択状況によって、下のtr要素の表示・非表示を変えたいとする

<table>
	<tr>
		<th>
			hpscript
		</th>
		<td>
			<input type="radio" name="a" value="y" checked>yes
			<input type="radio" name="a" value="n">no
		</td>
	</tr>

	<tr>
		<th>
			タイトル1
		</th>
		<td>
			コンテンツ1
		</td>
	</tr>
</table>

これをVue.jsのv-on:${}で実装しようとしたが、上手く行かない

<table>
	<div id="app">
	<tr>
		<th>
			hpscript
		</th>
		<td>
			<input type="radio" name="a" v-on:change="handler" value="y" checked>yes
			<input type="radio" name="a" v-on:change="handler" value="n">no
		</td>
	</tr>
	<div v-if="show">
	<tr>
		<th>
			タイトル1
		</th>
		<td>
			コンテンツ1
		</td>
	</tr>
	</div>
	</div>
</table>
<script>
	new Vue({
		el: '#app',
		data: {
			show: true
		},
		methods: {
			handler: function(event){
				if(event.target.value === 'y'){
					this.show = true
				} else {
					this.show = false
				}
			}
		}
	})
</script>

ガッテム。結局、on.changeで実装することに。

<table>
	<tr>
		<th>
			hpscript
		</th>
		<td>
			<input type="radio" name="a" value="y" checked>yes
			<input type="radio" name="a" value="n">no
		</td>
	</tr>

	<tr class="changeterm displaynone">
		<th>
			タイトル1
		</th>
		<td>
			コンテンツ1
		</td>
	</tr>
</table>

<script>
$('input[name=a]').on('change', function(){
			if($('.changeterm').hasClass('displaynone')){
				$('.changeterm').removeClass('displaynone');
			} else {
				$('.changeterm').addClass('displaynone');
			}			
		});
</script>
.displaynone {
        display:none;
}

なんでtableでVue.jsのv-onだと上手く行かないのかよくわからない。。
教えて欲しい。。🤖

Vue.jsでインスタンスのネスト(if-vの中でv-mode.trim)の表示方法

v-ifで、trueだった場合に、v-model.trimで文字数をカウントしたい場合。
v-ifの中にv-model.trimが入っているので、いわゆるネスト
この場合、new Vueを続けて書いてもうまく行かない

<div id="app">
	<div v-if="display">
		<div id="job">
		    <input type="text" name="title" v-model.trim="message" maxlength="20" placeholder="20文字以内で入力してください">
		    <span class="char-length">{{ message.length }}/20</span>
		</div>
	</div>
	<div v-else>
		<p>表示なし</p>
	</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
		el: "#app",
		data: { 
			display: true
		}
	});
new Vue({
		el: "#job",
		data: { message: "what up bro!"}
	});
</script>

どうするかというと、子供のインスタンスのidに”v-pre”をつける

<div id="job" v-pre>
		    <input type="text" name="title" v-model.trim="message" maxlength="20" placeholder="20文字以内で入力してください">
		    <span class="char-length">{{ message.length }}/20</span>
		</div>

すると、上手く表示されます。
嵌ったー、15分くらい😱

prettier

npm install -D prettier gulp-prettier-plugin

var gulp = require('gulp');
var prettierPlugin = require('gulp-prettier-plugin');
var imagemin = require('gulp-imagemin');
var sass = require('gulp-sass');
var sassLint = require('gulp-sass-lint');
var eslint = require('gulp-eslint');
var plumber = require('gulp-plumber');
var webserver = require('gulp-webserver');

gulp.task('prettier', function(done){
	gulp.src(['./src/sass/*.scss','./src/*.js'])
		.pipe(prettierPlugin({
			prettier:{
				singleQuote:true
			},
		},{filter: true}))
		.pipe(gulp.dest(file => file.base))
	done();
});

gulp.task('html', function(done){
	gulp.src('./src/*.html')
		.pipe(gulp.dest('./dest'))
	done();
});


gulp.task('img', function(done){
	gulp.src('./src/img/*.png')
		.pipe(imagemin())
		.pipe(gulp.dest('./dest/img'))
	done();
});

gulp.task('sass', function(done){
	gulp.src('./src/sass/*.scss')
		.pipe(plumber())
		.pipe(sassLint())
		.pipe(sassLint.format())
		.pipe(sassLint.failOnError())
		.pipe(sass({outputStyle: 'expand'}))
		.pipe(gulp.dest('./dest/css'))
	done();
});

gulp.task('js', function(done){
	gulp.src(['./src/*.js','!node_modules/**'])
		.pipe(plumber())
		.pipe(eslint({ useEslintrc: true }))
		.pipe(eslint.format())
		.pipe(eslint.failAfterError())
		.pipe(gulp.dest('./dest/js'))
	done();
});

gulp.task('watch', function(done){
	gulp.watch('./src/*.html', gulp.task('html'))
	gulp.watch('./src/js/*.html', gulp.task('js'))
	gulp.watch('./src/sass/*.scss', gulp.task('sass'))
	done();
});

gulp.task('webserver', function(done){
	gulp.src('./dest')
		.pipe(webserver({
			host:'192.168.34.10',
			port: 8000,
			livereload: true,
		}));
	done();
});

gulp.task('default', gulp.series('prettier','html','js','img','sass','watch','webserver'));

とりあえず、環境構築はこんなところか。
さー、front書き始めましょうかね^^

ESLint + gulp

$ npm i -D gulp-eslint

var gulp = require('gulp');
var imagemin = require('gulp-imagemin');
var webserver = require('gulp-webserver');
var sass = require('gulp-sass');
var sassLint = require('gulp-sass-lint');
var eslint = require('gulp-eslint');
var plumber = require('gulp-plumber');

gulp.task('html', function(done){
	gulp.src('./src/*.html')
		.pipe(gulp.dest('./dest'))
	done();
});

gulp.task('js', function(done){
	gulp.src(['./src/*.js','!node_modules/**'])
		.pipe(eslint({ useEslintrc: true }))
		.pipe(eslint.format())
		.pipe(eslint.failAfterError())
		.pipe(gulp.dest('./dest/js'))
	done();
});

gulp.task('img', function(done){
	gulp.src('./src/img/*.png')
		.pipe(imagemin())
		.pipe(gulp.dest('./dest/img'))
	done();
});

gulp.task('sass', function(done){
	gulp.src('./src/sass/*.scss')
		.pipe(plumber())
		.pipe(sassLint())
		.pipe(sassLint.format())
		.pipe(sassLint.failOnError())
		.pipe(sass({outputStyle: 'expand'}))
		.pipe(gulp.dest('./dest/css'))
	done();
});

gulp.task('watch', function(done){
	gulp.watch('./src/*.html', gulp.task('html'))
	gulp.watch('./src/js/*.html', gulp.task('js'))
	gulp.watch('./src/sass/*.scss', gulp.task('sass'))
	done();
});

gulp.task('webserver', function(done){
	gulp.src('./dest')
		.pipe(webserver({
			host:'192.168.34.10',
			port: 8000,
			livereload: true,
		}));
	done();
});

gulp.task('default', gulp.series('html','js','img','sass','watch','webserver'));

gulpにsass-lintを導入する

stylelintとは?
-lint工程を追加することで、コードを綺麗にし、保守性を高める
-styleの重複を減らす

csslintとは?
$ npm install -g csslint
$ csslint main.css

sasslintを導入
$ npm install gulp-sass-lint –save-dev
$ gulp

var sassLint = require('gulp-sass-lint');

gulp.task('sass', function(done){
	gulp.src('./src/sass/*.scss')
		.pipe(plumber())
		.pipe(sassLint())
		.pipe(sassLint.format())
		.pipe(sassLint.failOnError())
		.pipe(sass({outputStyle: 'expand'}))
		.pipe(gulp.dest('./dest/css'))
	done();
});

src/sass/main.scss
2:2 warning Mixed tabs and spaces indentation
2:20 warning Color ‘red’ should be written in its hexadecimal form #ff0000 no-color-keywords
2:20 warning Color literals such as ‘red’ should only be used in variable declarations no-color-literals
3:2 warning Mixed tabs and spaces indentation
4:2 warning Mixed tabs and spaces indentation
5:1 warning Files must end with a new line final-newline

✖ 6 problems (0 errors, 6 warnings)

ああああああ、これスゲー

gulp + sassの環境を整えよう

## sassをインストール
$ npm i -D gulp-sass

imgを圧縮し、sassとhtmlを編集したら、自動反映にします。

var gulp = require('gulp');
var imagemin = require('gulp-imagemin');
var webserver = require('gulp-webserver');
var sass = require('gulp-sass');
var plumber = require('gulp-plumber');

gulp.task('html', function(done){
	gulp.src('./src/*.html')
		.pipe(gulp.dest('./dest'))
	done();
});

gulp.task('img', function(done){
	gulp.src('./src/img/*.png')
		.pipe(imagemin())
		.pipe(gulp.dest('./dest/img'))
	done();
});

gulp.task('sass', function(done){
	gulp.src('./src/sass/*.scss')
		.pipe(plumber())
		.pipe(sass({outputStyle: 'expand'}))
		.pipe(gulp.dest('./dest/css'))
	done();
});

gulp.task('watch', function(done){
	gulp.watch('./src/*.html', gulp.task('html'))
	gulp.watch('./src/sass/*.scss', gulp.task('sass'))
	done();
});

gulp.task('webserver', function(done){
	gulp.src('./dest')
		.pipe(webserver({
			host:'192.168.34.10',
			port: 8000,
			livereload: true,
		}));
	done();
});

gulp.task('default', gulp.series('html','img','sass','watch','webserver'));

あ、これでいいじゃん。あとは、JSとCSSのLint周りか