diff --git a/.gitignore b/.gitignore index 45104823..698f6f85 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ ############### bin/ vendor/ +node_modules/ ### Files ### ############# diff --git a/.travis.yml b/.travis.yml index 0957074b..c8f9aeee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,3 @@ -language: go - -go: - - "1.x" # use the latest Go release - env: - GO111MODULE=on @@ -11,8 +6,26 @@ notifications: slack: secure: IYjXoe03KykZ3v4GgUwGzfWRepO5DnJdxB87lSQ2IMsF6PBFSc3CaOX3GUclHIlzTdchR+PHj1jtEZZVSkgfp9amZBCcqbJTBOPG1YA6hxOvTpgeWIttMH0cmMxSCuCa4RfkuRH2+UXbjREMJ3ENau2CTMKReyW4Jddh9dREZohVmYuqN6uuBqCndYpt3Lm1Hv+T+vqxwTDdE/q0hwGMiwgvQm7N3K397e1q1mg+o4tMGwqyUIPnEPjaSKcEuOBa8Rqyl96nn+HGZK0zvNqUOxlzeRMM0VBcxe2s+zY/SuLj4OwNl1zEmIfY6Qj70t2cmT3xJvJprB4pCwR7q78b4lfpNu6rqCJPIZG/qDFT+XSuhDCmLlCO/+Uhtu11pgjV8UMNLTKJth+7hurH7oLNb7jYk9VYsiKhs41LICyDjJNzS5yPatF5xj0HOujb6Uh/pfI+9a+IpPSeXv1gBo8H3oWa6TfRhuTUS3Jc48p/jriZmgWgbKa1HKTaY9ENvAdZFfxJdrRg3Y4SKnjZcAPw7ijRIx1oaM3rHYbOTm/dj4ggho7EgTO3k8toQ5PKohrbBG5RERqHJvC47SXDt0fEjeGnAfN7Xtj0Pq8YyaFIj7CmCCGoI//2sWkK3AmjnwIuW0hUMsL3GsED+p0lsu6FX9wysJwy2Z2mTfIX/CXmB6w= +install: + # Manually download and install Go 1.12 instead of using gimme. + # It looks like gimme Go causes some errors on go-test for Wasm. + - wget -O go.tar.gz https://dl.google.com/go/go1.12.linux-amd64.tar.gz + - tar -C ~ -xzf go.tar.gz + - rm go.tar.gz + - export GOROOT=~/go + - export PATH=$GOROOT/bin:$PATH + - go version + - go env + # Install Node 11 (required for WASM tests) + - source ~/.nvm/nvm.sh + - nvm install 11 + - node --version + - npm i -g yarn + - yarn install + before_install: - sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev + - wget https://raw.githubusercontent.com/creationix/nvm/v0.31.0/nvm.sh -O ~/.nvm/nvm.sh before_script: - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.14.0 @@ -22,6 +35,7 @@ script: - golangci-lint run ./... - rm -rf examples # Remove examples, no test coverage for them - go test -coverpkg=$(go list ./... | tr '\n' ',') -coverprofile=cover.out -v -race -covermode=atomic ./... + - GOOS=js GOARCH=wasm go test -exec="./test-wasm/go_js_wasm_exec" -v . - goveralls -coverprofile=cover.out -service=travis-ci - bash .github/assert-contributors.sh - bash .github/lint-disallowed-functions-in-library.sh diff --git a/api_test.go b/api_test.go index 280edf55..c930671d 100644 --- a/api_test.go +++ b/api_test.go @@ -1,3 +1,5 @@ +// +build !js + package webrtc import ( diff --git a/certificate_test.go b/certificate_test.go index 06d08c89..5e7b431d 100644 --- a/certificate_test.go +++ b/certificate_test.go @@ -1,3 +1,5 @@ +// +build !js + package webrtc import ( diff --git a/datachannel_go_test.go b/datachannel_go_test.go new file mode 100644 index 00000000..55452f7e --- /dev/null +++ b/datachannel_go_test.go @@ -0,0 +1,248 @@ +// +build !js + +package webrtc + +import ( + "crypto/rand" + "encoding/binary" + "math/big" + "testing" + "time" + + "github.com/pions/transport/test" + "github.com/stretchr/testify/assert" +) + +func TestGenerateDataChannelID(t *testing.T) { + api := NewAPI() + + testCases := []struct { + client bool + c *PeerConnection + result uint16 + }{ + {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{}, api: api}, 0}, + {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{1: nil}, api: api}, 0}, + {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{0: nil}, api: api}, 2}, + {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{0: nil, 2: nil}, api: api}, 4}, + {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{0: nil, 4: nil}, api: api}, 2}, + {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{}, api: api}, 1}, + {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{0: nil}, api: api}, 1}, + {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{1: nil}, api: api}, 3}, + {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{1: nil, 3: nil}, api: api}, 5}, + {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{1: nil, 5: nil}, api: api}, 3}, + } + + for _, testCase := range testCases { + id, err := testCase.c.generateDataChannelID(testCase.client) + if err != nil { + t.Errorf("failed to generate id: %v", err) + return + } + if id != testCase.result { + t.Errorf("Wrong id: %d expected %d", id, testCase.result) + } + } +} + +func TestDataChannel_Send(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + + api := NewAPI() + offerPC, answerPC, err := api.newPair() + + if err != nil { + t.Fatalf("Failed to create a PC pair for testing") + } + + done := make(chan bool) + + dc, err := offerPC.CreateDataChannel("data", nil) + + if err != nil { + t.Fatalf("Failed to create a PC pair for testing") + } + + assert.True(t, dc.Ordered, "Ordered should be set to true") + + dc.OnOpen(func() { + e := dc.SendText("Ping") + if e != nil { + t.Fatalf("Failed to send string on data channel") + } + }) + dc.OnMessage(func(msg DataChannelMessage) { + done <- true + }) + + answerPC.OnDataChannel(func(d *DataChannel) { + assert.True(t, d.Ordered, "Ordered should be set to true") + + d.OnMessage(func(msg DataChannelMessage) { + e := d.Send([]byte("Pong")) + if e != nil { + t.Fatalf("Failed to send string on data channel") + } + }) + }) + + err = signalPair(offerPC, answerPC) + + if err != nil { + t.Fatalf("Failed to signal our PC pair for testing") + } + + closePair(t, offerPC, answerPC, done) +} + +func TestDataChannel_EventHandlers(t *testing.T) { + to := test.TimeOut(time.Second * 20) + defer to.Stop() + + report := test.CheckRoutines(t) + defer report() + + api := NewAPI() + dc := &DataChannel{api: api} + + onOpenCalled := make(chan struct{}) + onMessageCalled := make(chan struct{}) + + // Verify that the noop case works + assert.NotPanics(t, func() { dc.onOpen() }) + + dc.OnOpen(func() { + close(onOpenCalled) + }) + + dc.OnMessage(func(p DataChannelMessage) { + close(onMessageCalled) + }) + + // Verify that the set handlers are called + assert.NotPanics(t, func() { dc.onOpen() }) + assert.NotPanics(t, func() { dc.onMessage(DataChannelMessage{Data: []byte("o hai")}) }) + + // Wait for all handlers to be called + <-onOpenCalled + <-onMessageCalled +} + +func TestDataChannel_MessagesAreOrdered(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + + api := NewAPI() + dc := &DataChannel{api: api} + + max := 512 + out := make(chan int) + inner := func(msg DataChannelMessage) { + // randomly sleep + // math/rand a weak RNG, but this does not need to be secure. Ignore with #nosec + /* #nosec */ + randInt, err := rand.Int(rand.Reader, big.NewInt(int64(max))) + /* #nosec */ + if err != nil { + t.Fatalf("Failed to get random sleep duration: %s", err) + } + time.Sleep(time.Duration(randInt.Int64()) * time.Microsecond) + s, _ := binary.Varint(msg.Data) + out <- int(s) + } + dc.OnMessage(func(p DataChannelMessage) { + inner(p) + }) + + go func() { + for i := 1; i <= max; i++ { + buf := make([]byte, 8) + binary.PutVarint(buf, int64(i)) + dc.onMessage(DataChannelMessage{Data: buf}) + // Change the registered handler a couple of times to make sure + // that everything continues to work, we don't lose messages, etc. + if i%2 == 0 { + hdlr := func(msg DataChannelMessage) { + inner(msg) + } + dc.OnMessage(hdlr) + } + } + }() + + values := make([]int, 0, max) + for v := range out { + values = append(values, v) + if len(values) == max { + close(out) + } + } + + expected := make([]int, max) + for i := 1; i <= max; i++ { + expected[i-1] = i + } + assert.EqualValues(t, expected, values) +} + +func TestDataChannelParamters(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + + t.Run("MaxPacketLifeTime exchange", func(t *testing.T) { + var ordered = true + var maxPacketLifeTime uint16 = 3 + options := &DataChannelInit{ + Ordered: &ordered, + MaxPacketLifeTime: &maxPacketLifeTime, + } + + offerPC, answerPC, dc, done := setUpReliabilityParamTest(t, options) + + // Check if parameters are correctly set + assert.True(t, dc.Ordered, "Ordered should be set to true") + if assert.NotNil(t, dc.MaxPacketLifeTime, "should not be nil") { + assert.Equal(t, maxPacketLifeTime, *dc.MaxPacketLifeTime, "should match") + } + + answerPC.OnDataChannel(func(d *DataChannel) { + // Check if parameters are correctly set + assert.True(t, d.Ordered, "Ordered should be set to true") + if assert.NotNil(t, d.MaxPacketLifeTime, "should not be nil") { + assert.Equal(t, maxPacketLifeTime, *d.MaxPacketLifeTime, "should match") + } + done <- true + }) + + closeReliabilityParamTest(t, offerPC, answerPC, done) + }) + + t.Run("MaxRetransmits exchange", func(t *testing.T) { + var ordered = false + var maxRetransmits uint16 = 3000 + options := &DataChannelInit{ + Ordered: &ordered, + MaxRetransmits: &maxRetransmits, + } + + offerPC, answerPC, dc, done := setUpReliabilityParamTest(t, options) + + // Check if parameters are correctly set + assert.False(t, dc.Ordered, "Ordered should be set to false") + if assert.NotNil(t, dc.MaxRetransmits, "should not be nil") { + assert.Equal(t, maxRetransmits, *dc.MaxRetransmits, "should match") + } + + answerPC.OnDataChannel(func(d *DataChannel) { + // Check if parameters are correctly set + assert.False(t, d.Ordered, "Ordered should be set to false") + if assert.NotNil(t, d.MaxRetransmits, "should not be nil") { + assert.Equal(t, maxRetransmits, *d.MaxRetransmits, "should match") + } + done <- true + }) + + closeReliabilityParamTest(t, offerPC, answerPC, done) + }) +} diff --git a/datachannel_js.go b/datachannel_js.go index dcc41958..e6aae3db 100644 --- a/datachannel_js.go +++ b/datachannel_js.go @@ -127,13 +127,23 @@ func (d *DataChannel) Label() string { // Ordered represents if the DataChannel is ordered, and false if // out-of-order delivery is allowed. func (d *DataChannel) Ordered() bool { - return d.underlying.Get("ordered").Bool() + ordered := d.underlying.Get("ordered") + if ordered == js.Undefined() { + return true // default is true + } + return ordered.Bool() } // MaxPacketLifeTime represents the length of the time window (msec) during // which transmissions and retransmissions may occur in unreliable mode. func (d *DataChannel) MaxPacketLifeTime() *uint16 { - return valueToUint16Pointer(d.underlying.Get("maxPacketLifeTime")) + if d.underlying.Get("maxPacketLifeTime") != js.Undefined() { + return valueToUint16Pointer(d.underlying.Get("maxPacketLifeTime")) + } else { + // See https://bugs.chromium.org/p/chromium/issues/detail?id=696681 + // Chrome calls this "maxRetransmitTime" + return valueToUint16Pointer(d.underlying.Get("maxRetransmitTime")) + } } // MaxRetransmits represents the maximum number of retransmissions that are diff --git a/datachannel_js_test.go b/datachannel_js_test.go new file mode 100644 index 00000000..c928350a --- /dev/null +++ b/datachannel_js_test.go @@ -0,0 +1,140 @@ +// +build js + +package webrtc + +import ( + "testing" + + "github.com/pions/transport/test" + "github.com/stretchr/testify/assert" +) + +// TODO(albrow): This test can be combined into a single test for both Go and +// the WASM binding after the Go API has been updated to make Ordered a method +// instead of a struct field. +func TestDataChannel_Send(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + + offerPC, answerPC, err := newPair() + if err != nil { + t.Fatalf("Failed to create a PC pair for testing") + } + + done := make(chan bool) + + answerPC.OnDataChannel(func(d *DataChannel) { + // Make sure this is the data channel we were looking for. (Not the one + // created in signalPair). + if d.Label() != "data" { + return + } + d.OnMessage(func(msg DataChannelMessage) { + e := d.Send([]byte("Pong")) + if e != nil { + t.Fatalf("Failed to send string on data channel") + } + }) + assert.True(t, d.Ordered(), "Ordered should be set to true") + }) + + dc, err := offerPC.CreateDataChannel("data", nil) + if err != nil { + t.Fatalf("Failed to create a PC pair for testing") + } + + assert.True(t, dc.Ordered(), "Ordered should be set to true") + + dc.OnOpen(func() { + e := dc.SendText("Ping") + if e != nil { + t.Fatalf("Failed to send string on data channel") + } + }) + dc.OnMessage(func(msg DataChannelMessage) { + done <- true + }) + + err = signalPair(offerPC, answerPC) + if err != nil { + t.Fatalf("Failed to signal our PC pair for testing") + } + + closePair(t, offerPC, answerPC, done) +} + +// TODO(albrow): This test can be combined into a single test for both Go and +// the WASM binding after the Go API has been updated to make Ordered and +// MaxPacketLifeTime methods instead of struct fields. +func TestDataChannelParamters(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + + t.Run("MaxPacketLifeTime exchange", func(t *testing.T) { + // Note(albrow): See https://github.com/node-webrtc/node-webrtc/issues/492. + // There is a bug in the npm wrtc package which causes this test to fail. + t.Skip("Skipping because of upstream issue") + + // var ordered = true + // var maxPacketLifeTime uint16 = 3 + // options := &DataChannelInit{ + // Ordered: &ordered, + // MaxPacketLifeTime: &maxPacketLifeTime, + // } + + // offerPC, answerPC, dc, done := setUpReliabilityParamTest(t, options) + + // // Check if parameters are correctly set + // assert.True(t, dc.Ordered(), "Ordered should be set to true") + // if assert.NotNil(t, dc.MaxPacketLifeTime(), "should not be nil") { + // assert.Equal(t, maxPacketLifeTime, *dc.MaxPacketLifeTime(), "should match") + // } + + // answerPC.OnDataChannel(func(d *DataChannel) { + // if d.Label() != "data" { + // return + // } + // // Check if parameters are correctly set + // assert.True(t, d.Ordered(), "Ordered should be set to true") + // if assert.NotNil(t, d.MaxPacketLifeTime(), "should not be nil") { + // assert.Equal(t, maxPacketLifeTime, *d.MaxPacketLifeTime(), "should match") + // } + // done <- true + // }) + + // closeReliabilityParamTest(t, offerPC, answerPC, done) + }) + + t.Run("MaxRetransmits exchange", func(t *testing.T) { + var ordered = false + var maxRetransmits uint16 = 3000 + options := &DataChannelInit{ + Ordered: &ordered, + MaxRetransmits: &maxRetransmits, + } + + offerPC, answerPC, dc, done := setUpReliabilityParamTest(t, options) + + // Check if parameters are correctly set + assert.False(t, dc.Ordered(), "Ordered should be set to false") + if assert.NotNil(t, dc.MaxRetransmits(), "should not be nil") { + assert.Equal(t, maxRetransmits, *dc.MaxRetransmits(), "should match") + } + + answerPC.OnDataChannel(func(d *DataChannel) { + // Make sure this is the data channel we were looking for. (Not the one + // created in signalPair). + if d.Label() != "data" { + return + } + // Check if parameters are correctly set + assert.False(t, d.Ordered(), "Ordered should be set to false") + if assert.NotNil(t, d.MaxRetransmits(), "should not be nil") { + assert.Equal(t, maxRetransmits, *d.MaxRetransmits(), "should match") + } + done <- true + }) + + closeReliabilityParamTest(t, offerPC, answerPC, done) + }) +} diff --git a/datachannel_ortc_test.go b/datachannel_ortc_test.go index 346ccd7f..489f161f 100644 --- a/datachannel_ortc_test.go +++ b/datachannel_ortc_test.go @@ -1,3 +1,5 @@ +// +build !js + package webrtc import ( diff --git a/datachannel_test.go b/datachannel_test.go index f6e24e0a..590cac66 100644 --- a/datachannel_test.go +++ b/datachannel_test.go @@ -1,22 +1,16 @@ package webrtc import ( - "crypto/rand" - "encoding/binary" "io" - "math/big" "testing" "time" - - "github.com/pions/transport/test" - "github.com/stretchr/testify/assert" ) func closePair(t *testing.T, pc1, pc2 io.Closer, done chan bool) { var err error select { case <-time.After(10 * time.Second): - t.Fatalf("Datachannel Send Test Timeout") + t.Fatalf("closePair timed out waiting for done signal") case <-done: err = pc1.Close() if err != nil { @@ -29,182 +23,8 @@ func closePair(t *testing.T, pc1, pc2 io.Closer, done chan bool) { } } -func TestGenerateDataChannelID(t *testing.T) { - api := NewAPI() - - testCases := []struct { - client bool - c *PeerConnection - result uint16 - }{ - {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{}, api: api}, 0}, - {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{1: nil}, api: api}, 0}, - {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{0: nil}, api: api}, 2}, - {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{0: nil, 2: nil}, api: api}, 4}, - {true, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{0: nil, 4: nil}, api: api}, 2}, - {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{}, api: api}, 1}, - {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{0: nil}, api: api}, 1}, - {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{1: nil}, api: api}, 3}, - {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{1: nil, 3: nil}, api: api}, 5}, - {false, &PeerConnection{sctpTransport: api.NewSCTPTransport(nil), dataChannels: map[uint16]*DataChannel{1: nil, 5: nil}, api: api}, 3}, - } - - for _, testCase := range testCases { - id, err := testCase.c.generateDataChannelID(testCase.client) - if err != nil { - t.Errorf("failed to generate id: %v", err) - return - } - if id != testCase.result { - t.Errorf("Wrong id: %d expected %d", id, testCase.result) - } - } -} - -func TestDataChannel_Send(t *testing.T) { - report := test.CheckRoutines(t) - defer report() - - api := NewAPI() - offerPC, answerPC, err := api.newPair() - - if err != nil { - t.Fatalf("Failed to create a PC pair for testing") - } - - done := make(chan bool) - - dc, err := offerPC.CreateDataChannel("data", nil) - - if err != nil { - t.Fatalf("Failed to create a PC pair for testing") - } - - assert.True(t, dc.Ordered, "Ordered should be set to true") - - dc.OnOpen(func() { - e := dc.SendText("Ping") - if e != nil { - t.Fatalf("Failed to send string on data channel") - } - }) - dc.OnMessage(func(msg DataChannelMessage) { - done <- true - }) - - answerPC.OnDataChannel(func(d *DataChannel) { - assert.True(t, d.Ordered, "Ordered should be set to true") - - d.OnMessage(func(msg DataChannelMessage) { - e := d.Send([]byte("Pong")) - if e != nil { - t.Fatalf("Failed to send string on data channel") - } - }) - }) - - err = signalPair(offerPC, answerPC) - - if err != nil { - t.Fatalf("Failed to signal our PC pair for testing") - } - - closePair(t, offerPC, answerPC, done) -} - -func TestDataChannel_EventHandlers(t *testing.T) { - to := test.TimeOut(time.Second * 20) - defer to.Stop() - - report := test.CheckRoutines(t) - defer report() - - api := NewAPI() - dc := &DataChannel{api: api} - - onOpenCalled := make(chan struct{}) - onMessageCalled := make(chan struct{}) - - // Verify that the noop case works - assert.NotPanics(t, func() { dc.onOpen() }) - - dc.OnOpen(func() { - close(onOpenCalled) - }) - - dc.OnMessage(func(p DataChannelMessage) { - close(onMessageCalled) - }) - - // Verify that the set handlers are called - assert.NotPanics(t, func() { dc.onOpen() }) - assert.NotPanics(t, func() { dc.onMessage(DataChannelMessage{Data: []byte("o hai")}) }) - - // Wait for all handlers to be called - <-onOpenCalled - <-onMessageCalled -} - -func TestDataChannel_MessagesAreOrdered(t *testing.T) { - report := test.CheckRoutines(t) - defer report() - - api := NewAPI() - dc := &DataChannel{api: api} - - max := 512 - out := make(chan int) - inner := func(msg DataChannelMessage) { - // randomly sleep - // math/rand a weak RNG, but this does not need to be secure. Ignore with #nosec - /* #nosec */ - randInt, err := rand.Int(rand.Reader, big.NewInt(int64(max))) - /* #nosec */ - if err != nil { - t.Fatalf("Failed to get random sleep duration: %s", err) - } - time.Sleep(time.Duration(randInt.Int64()) * time.Microsecond) - s, _ := binary.Varint(msg.Data) - out <- int(s) - } - dc.OnMessage(func(p DataChannelMessage) { - inner(p) - }) - - go func() { - for i := 1; i <= max; i++ { - buf := make([]byte, 8) - binary.PutVarint(buf, int64(i)) - dc.onMessage(DataChannelMessage{Data: buf}) - // Change the registered handler a couple of times to make sure - // that everything continues to work, we don't lose messages, etc. - if i%2 == 0 { - hdlr := func(msg DataChannelMessage) { - inner(msg) - } - dc.OnMessage(hdlr) - } - } - }() - - values := make([]int, 0, max) - for v := range out { - values = append(values, v) - if len(values) == max { - close(out) - } - } - - expected := make([]int, max) - for i := 1; i <= max; i++ { - expected[i-1] = i - } - assert.EqualValues(t, expected, values) -} - func setUpReliabilityParamTest(t *testing.T, options *DataChannelInit) (*PeerConnection, *PeerConnection, *DataChannel, chan bool) { - api := NewAPI() - offerPC, answerPC, err := api.newPair() + offerPC, answerPC, err := newPair() if err != nil { t.Fatalf("Failed to create a PC pair for testing") } @@ -226,64 +46,3 @@ func closeReliabilityParamTest(t *testing.T, pc1, pc2 *PeerConnection, done chan closePair(t, pc1, pc2, done) } - -func TestDataChannelParamters(t *testing.T) { - report := test.CheckRoutines(t) - defer report() - - t.Run("MaxPacketLifeTime exchange", func(t *testing.T) { - var ordered = true - var maxPacketLifeTime uint16 = 3 - options := &DataChannelInit{ - Ordered: &ordered, - MaxPacketLifeTime: &maxPacketLifeTime, - } - - offerPC, answerPC, dc, done := setUpReliabilityParamTest(t, options) - - // Check if parameters are correctly set - assert.True(t, dc.Ordered, "Ordered should be set to true") - if assert.NotNil(t, dc.MaxPacketLifeTime, "should not be nil") { - assert.Equal(t, maxPacketLifeTime, *dc.MaxPacketLifeTime, "should match") - } - - answerPC.OnDataChannel(func(d *DataChannel) { - // Check if parameters are correctly set - assert.True(t, d.Ordered, "Ordered should be set to true") - if assert.NotNil(t, d.MaxPacketLifeTime, "should not be nil") { - assert.Equal(t, maxPacketLifeTime, *d.MaxPacketLifeTime, "should match") - } - done <- true - }) - - closeReliabilityParamTest(t, offerPC, answerPC, done) - }) - - t.Run("MaxRetransmits exchange", func(t *testing.T) { - var ordered = false - var maxRetransmits uint16 = 3000 - options := &DataChannelInit{ - Ordered: &ordered, - MaxRetransmits: &maxRetransmits, - } - - offerPC, answerPC, dc, done := setUpReliabilityParamTest(t, options) - - // Check if parameters are correctly set - assert.False(t, dc.Ordered, "Ordered should be set to false") - if assert.NotNil(t, dc.MaxRetransmits, "should not be nil") { - assert.Equal(t, maxRetransmits, *dc.MaxRetransmits, "should match") - } - - answerPC.OnDataChannel(func(d *DataChannel) { - // Check if parameters are correctly set - assert.False(t, d.Ordered, "Ordered should be set to false") - if assert.NotNil(t, d.MaxRetransmits, "should not be nil") { - assert.Equal(t, maxRetransmits, *d.MaxRetransmits, "should match") - } - done <- true - }) - - closeReliabilityParamTest(t, offerPC, answerPC, done) - }) -} diff --git a/icegatherer_test.go b/icegatherer_test.go index 9aea01b8..1a1e8548 100644 --- a/icegatherer_test.go +++ b/icegatherer_test.go @@ -1,3 +1,5 @@ +// +build !js + package webrtc import ( diff --git a/package.json b/package.json new file mode 100644 index 00000000..bbbb71d4 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "webrtc", + "repository": "git@github.com:pions/webrtc.git", + "private": true, + "devDependencies": { + "wrtc": "^0.3.5" + } +} diff --git a/peerconnection_close_test.go b/peerconnection_close_go_test.go similarity index 86% rename from peerconnection_close_test.go rename to peerconnection_close_go_test.go index 190a24f1..7a48e845 100644 --- a/peerconnection_close_test.go +++ b/peerconnection_close_go_test.go @@ -1,3 +1,5 @@ +// +build !js + package webrtc import ( @@ -7,12 +9,10 @@ import ( "github.com/pions/transport/test" ) -// TestPeerConnection_Close is moved to it's on file because the tests +// TestPeerConnection_Close is moved to it's own file because the tests // in rtcpeerconnection_test.go are leaky, making the goroutine report useless. func TestPeerConnection_Close(t *testing.T) { - api := NewAPI() - // Limit runtime in case of deadlocks lim := test.TimeOut(time.Second * 20) defer lim.Stop() @@ -20,7 +20,7 @@ func TestPeerConnection_Close(t *testing.T) { report := test.CheckRoutines(t) defer report() - pcOffer, pcAnswer, err := api.newPair() + pcOffer, pcAnswer, err := newPair() if err != nil { t.Fatal(err) } diff --git a/peerconnection_close_js_test.go b/peerconnection_close_js_test.go new file mode 100644 index 00000000..c1c6e611 --- /dev/null +++ b/peerconnection_close_js_test.go @@ -0,0 +1,61 @@ +// +build js + +package webrtc + +import ( + "testing" + "time" + + "github.com/pions/transport/test" +) + +// TestPeerConnection_Close is moved to it's own file because the tests +// in rtcpeerconnection_test.go are leaky, making the goroutine report useless. +// TODO(albrow): This test can be combined into a single test for both Go and +// the WASM binding after the Go API has been updated to make Label a method +// instead of a struct field. +func TestPeerConnection_Close(t *testing.T) { + // Limit runtime in case of deadlocks + lim := test.TimeOut(time.Second * 20) + defer lim.Stop() + + report := test.CheckRoutines(t) + defer report() + + pcOffer, pcAnswer, err := newPair() + if err != nil { + t.Fatal(err) + } + + awaitSetup := make(chan struct{}) + pcAnswer.OnDataChannel(func(d *DataChannel) { + // Make sure this is the data channel we were looking for. (Not the one + // created in signalPair). + if d.Label() != "data" { + return + } + close(awaitSetup) + }) + + _, err = pcOffer.CreateDataChannel("data", nil) + if err != nil { + t.Fatal(err) + } + + err = signalPair(pcOffer, pcAnswer) + if err != nil { + t.Fatal(err) + } + + <-awaitSetup + + err = pcOffer.Close() + if err != nil { + t.Fatal(err) + } + + err = pcAnswer.Close() + if err != nil { + t.Fatal(err) + } +} diff --git a/peerconnection_go_test.go b/peerconnection_go_test.go new file mode 100644 index 00000000..e2a3bbff --- /dev/null +++ b/peerconnection_go_test.go @@ -0,0 +1,359 @@ +// +build !js + +package webrtc + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "math/big" + "reflect" + "testing" + "time" + + "github.com/pions/transport/test" + "github.com/pions/webrtc/internal/ice" + "github.com/pions/webrtc/internal/mux" + "github.com/pions/webrtc/pkg/rtcerr" + "github.com/stretchr/testify/assert" +) + +// newPair creates two new peer connections (an offerer and an answerer) using +// the api. +func (api *API) newPair() (pcOffer *PeerConnection, pcAnswer *PeerConnection, err error) { + pca, err := api.NewPeerConnection(Configuration{}) + if err != nil { + return nil, nil, err + } + + pcb, err := api.NewPeerConnection(Configuration{}) + if err != nil { + return nil, nil, err + } + + return pca, pcb, nil +} + +func signalPair(pcOffer *PeerConnection, pcAnswer *PeerConnection) error { + offer, err := pcOffer.CreateOffer(nil) + if err != nil { + return err + } + + if err = pcOffer.SetLocalDescription(offer); err != nil { + return err + } + + err = pcAnswer.SetRemoteDescription(offer) + if err != nil { + return err + } + + answer, err := pcAnswer.CreateAnswer(nil) + if err != nil { + return err + } + + if err = pcAnswer.SetLocalDescription(answer); err != nil { + return err + } + + err = pcOffer.SetRemoteDescription(answer) + if err != nil { + return err + } + + return nil +} + +func TestNew_Go(t *testing.T) { + api := NewAPI() + t.Run("Success", func(t *testing.T) { + secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + assert.Nil(t, err) + + certificate, err := GenerateCertificate(secretKey) + assert.Nil(t, err) + + pc, err := api.NewPeerConnection(Configuration{ + ICEServers: []ICEServer{ + { + URLs: []string{ + "stun:stun.l.google.com:19302", + "turns:google.de?transport=tcp", + }, + Username: "unittest", + Credential: OAuthCredential{ + MACKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", + AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", + }, + CredentialType: ICECredentialTypeOauth, + }, + }, + ICETransportPolicy: ICETransportPolicyRelay, + BundlePolicy: BundlePolicyMaxCompat, + RTCPMuxPolicy: RTCPMuxPolicyNegotiate, + PeerIdentity: "unittest", + Certificates: []Certificate{*certificate}, + ICECandidatePoolSize: 5, + }) + assert.Nil(t, err) + assert.NotNil(t, pc) + }) + t.Run("Failure", func(t *testing.T) { + testCases := []struct { + initialize func() (*PeerConnection, error) + expectedErr error + }{ + {func() (*PeerConnection, error) { + secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + assert.Nil(t, err) + + certificate, err := NewCertificate(secretKey, x509.Certificate{ + Version: 2, + SerialNumber: big.NewInt(1653), + NotBefore: time.Now().AddDate(0, -2, 0), + NotAfter: time.Now().AddDate(0, -1, 0), + }) + assert.Nil(t, err) + + return api.NewPeerConnection(Configuration{ + Certificates: []Certificate{*certificate}, + }) + }, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}}, + {func() (*PeerConnection, error) { + return api.NewPeerConnection(Configuration{ + ICEServers: []ICEServer{ + { + URLs: []string{ + "stun:stun.l.google.com:19302", + "turns:google.de?transport=tcp", + }, + Username: "unittest", + }, + }, + }) + }, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredencials}}, + } + + for i, testCase := range testCases { + _, err := testCase.initialize() + assert.EqualError(t, err, testCase.expectedErr.Error(), + "testCase: %d %v", i, testCase, + ) + } + }) +} + +func TestPeerConnection_SetConfiguration_Go(t *testing.T) { + // Note: this test includes all SetConfiguration features that are supported + // by Go but not the WASM bindings, namely: ICEServer.Credential, + // ICEServer.CredentialType, and Certificates. + + api := NewAPI() + + secretKey1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + assert.Nil(t, err) + + certificate1, err := GenerateCertificate(secretKey1) + assert.Nil(t, err) + + secretKey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + assert.Nil(t, err) + + certificate2, err := GenerateCertificate(secretKey2) + assert.Nil(t, err) + + for _, test := range []struct { + name string + init func() (*PeerConnection, error) + config Configuration + wantErr error + }{ + { + name: "valid", + init: func() (*PeerConnection, error) { + pc, err := api.NewPeerConnection(Configuration{ + PeerIdentity: "unittest", + Certificates: []Certificate{*certificate1}, + ICECandidatePoolSize: 5, + }) + if err != nil { + return pc, err + } + + err = pc.SetConfiguration(Configuration{ + ICEServers: []ICEServer{ + { + URLs: []string{ + "stun:stun.l.google.com:19302", + "turns:google.de?transport=tcp", + }, + Username: "unittest", + Credential: OAuthCredential{ + MACKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", + AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", + }, + CredentialType: ICECredentialTypeOauth, + }, + }, + ICETransportPolicy: ICETransportPolicyAll, + BundlePolicy: BundlePolicyBalanced, + RTCPMuxPolicy: RTCPMuxPolicyRequire, + PeerIdentity: "unittest", + Certificates: []Certificate{*certificate1}, + ICECandidatePoolSize: 5, + }) + if err != nil { + return pc, err + } + + return pc, nil + }, + config: Configuration{}, + wantErr: nil, + }, + { + name: "update multiple certificates", + init: func() (*PeerConnection, error) { + return api.NewPeerConnection(Configuration{}) + }, + config: Configuration{ + Certificates: []Certificate{*certificate1, *certificate2}, + }, + wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}, + }, + { + name: "update certificate", + init: func() (*PeerConnection, error) { + return api.NewPeerConnection(Configuration{}) + }, + config: Configuration{ + Certificates: []Certificate{*certificate1}, + }, + wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}, + }, + } { + pc, err := test.init() + if err != nil { + t.Errorf("SetConfiguration %q: init failed: %v", test.name, err) + } + + err = pc.SetConfiguration(test.config) + if got, want := err, test.wantErr; !reflect.DeepEqual(got, want) { + t.Errorf("SetConfiguration %q: err = %v, want %v", test.name, got, want) + } + } +} + +// TODO - This unittest needs to be completed when CreateDataChannel is complete +// func TestPeerConnection_CreateDataChannel(t *testing.T) { +// pc, err := New(Configuration{}) +// assert.Nil(t, err) +// +// _, err = pc.CreateDataChannel("data", &DataChannelInit{ +// +// }) +// assert.Nil(t, err) +// } + +func TestPeerConnection_EventHandlers_Go(t *testing.T) { + // Note: When testing the Go event handlers we peer into the state a bit more + // than what is possible for the environment agnostic (Go or WASM/JavaScript) + // EventHandlers test. + api := NewAPI() + pc, err := api.NewPeerConnection(Configuration{}) + assert.Nil(t, err) + + onTrackCalled := make(chan bool) + onICEConnectionStateChangeCalled := make(chan bool) + onDataChannelCalled := make(chan bool) + + // Verify that the noop case works + assert.NotPanics(t, func() { pc.onTrack(nil, nil) }) + assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) + + pc.OnTrack(func(t *Track, r *RTPReceiver) { + onTrackCalled <- true + }) + + pc.OnICEConnectionStateChange(func(cs ICEConnectionState) { + onICEConnectionStateChangeCalled <- true + }) + + pc.OnDataChannel(func(dc *DataChannel) { + onDataChannelCalled <- true + }) + + // Verify that the handlers deal with nil inputs + assert.NotPanics(t, func() { pc.onTrack(nil, nil) }) + assert.NotPanics(t, func() { go pc.onDataChannelHandler(nil) }) + + // Verify that the set handlers are called + assert.NotPanics(t, func() { pc.onTrack(&Track{}, &RTPReceiver{}) }) + assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) + assert.NotPanics(t, func() { go pc.onDataChannelHandler(&DataChannel{api: api}) }) + + allTrue := func(vals []bool) bool { + for _, val := range vals { + if !val { + return false + } + } + return true + } + + assert.True(t, allTrue([]bool{ + <-onTrackCalled, + <-onICEConnectionStateChangeCalled, + <-onDataChannelCalled, + })) +} + +// This test asserts that nothing deadlocks we try to shutdown when DTLS is in flight +// We ensure that DTLS is in flight by removing the mux func for it, so all inbound DTLS is lost +func TestPeerConnection_ShutdownNoDTLS(t *testing.T) { + dtlsMatchFunc := mux.MatchDTLS + defer func() { + mux.MatchDTLS = dtlsMatchFunc + }() + + // Drop all incoming DTLS traffic + mux.MatchDTLS = func([]byte) bool { + return false + } + + lim := test.TimeOut(time.Second * 10) + defer lim.Stop() + + api := NewAPI() + offerPC, answerPC, err := api.newPair() + if err != nil { + t.Fatal(err) + } + + if err = signalPair(offerPC, answerPC); err != nil { + t.Fatal(err) + } + + iceComplete := make(chan interface{}) + answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) { + if iceState == ICEConnectionStateConnected { + time.Sleep(time.Second) // Give time for DTLS to start + + select { + case <-iceComplete: + default: + close(iceComplete) + } + } + }) + + <-iceComplete + if err = offerPC.Close(); err != nil { + t.Fatal(err) + } else if err = answerPC.Close(); err != nil { + t.Fatal(err) + } +} diff --git a/peerconnection_js.go b/peerconnection_js.go index ed18b8da..0c4762d3 100644 --- a/peerconnection_js.go +++ b/peerconnection_js.go @@ -5,6 +5,8 @@ package webrtc import ( "syscall/js" + + "github.com/pions/webrtc/pkg/rtcerr" ) // PeerConnection represents a WebRTC connection that establishes a @@ -69,13 +71,13 @@ func (pc *PeerConnection) OnDataChannel(f func(*DataChannel)) { // fix by keeping a mutex-protected list of all DataChannel references as a // property of this PeerConnection, but at the cost of additional overhead. dataChannel := &DataChannel{ - underlying: args[0], + underlying: args[0].Get("channel"), } go f(dataChannel) return js.Undefined() }) pc.onDataChannelHandler = &onDataChannelHandler - pc.underlying.Set("onsignalingstatechange", onDataChannelHandler) + pc.underlying.Set("ondatachannel", onDataChannelHandler) } // OnTrack sets an event handler which is called when remote track @@ -99,6 +101,69 @@ func (pc *PeerConnection) OnICEConnectionStateChange(f func(ICEConnectionState)) pc.underlying.Set("oniceconnectionstatechange", onICEConectionStateChangeHandler) } +func (pc *PeerConnection) checkConfiguration(configuration Configuration) error { + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2) + if pc.ConnectionState() == PeerConnectionStateClosed { + return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + existingConfig := pc.GetConfiguration() + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #3) + if configuration.PeerIdentity != "" { + if configuration.PeerIdentity != existingConfig.PeerIdentity { + return &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity} + } + } + + // TODO: Enable these checks once Certificates are supported. + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #4) + // if len(configuration.Certificates) > 0 { + // if len(configuration.Certificates) != len(existingConfiguration.Certificates) { + // return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates} + // } + + // for i, certificate := range configuration.Certificates { + // if !pc.configuration.Certificates[i].Equals(certificate) { + // return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates} + // } + // } + // pc.configuration.Certificates = configuration.Certificates + // } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #5) + if configuration.BundlePolicy != BundlePolicy(Unknown) { + if configuration.BundlePolicy != existingConfig.BundlePolicy { + return &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy} + } + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #6) + if configuration.RTCPMuxPolicy != RTCPMuxPolicy(Unknown) { + if configuration.RTCPMuxPolicy != existingConfig.RTCPMuxPolicy { + return &rtcerr.InvalidModificationError{Err: ErrModifyingRTCPMuxPolicy} + } + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #7) + if configuration.ICECandidatePoolSize != 0 { + if configuration.ICECandidatePoolSize != existingConfig.ICECandidatePoolSize && + pc.LocalDescription() != nil { + return &rtcerr.InvalidModificationError{Err: ErrModifyingICECandidatePoolSize} + } + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11) + if len(configuration.ICEServers) > 0 { + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3) + for _, server := range configuration.ICEServers { + if _, err := server.validate(); err != nil { + return err + } + } + } + return nil +} + // SetConfiguration updates the configuration of this PeerConnection object. func (pc *PeerConnection) SetConfiguration(configuration Configuration) (err error) { defer func() { @@ -106,6 +171,9 @@ func (pc *PeerConnection) SetConfiguration(configuration Configuration) (err err err = recoveryToError(e) } }() + if err := pc.checkConfiguration(configuration); err != nil { + return err + } configMap := configurationToValue(configuration) pc.underlying.Call("setConfiguration", configMap) return nil @@ -421,9 +489,10 @@ func iceServersToValue(iceServers []ICEServer) js.Value { func iceServerToValue(server ICEServer) js.Value { return js.ValueOf(map[string]interface{}{ - "urls": stringsToValue(server.URLs), // required - "username": stringToValueOrUndefined(server.Username), - "credential": interfaceToValueOrUndefined(server.Credential), + "urls": stringsToValue(server.URLs), // required + "username": stringToValueOrUndefined(server.Username), + // TODO(albrow): credential is not currently supported. + // "credential": interfaceToValueOrUndefined(server.Credential), "credentialType": stringEnumToValueOrUndefined(server.CredentialType.String()), }) } @@ -436,7 +505,7 @@ func valueToConfiguration(configValue js.Value) Configuration { ICEServers: valueToICEServers(configValue.Get("iceServers")), ICETransportPolicy: newICETransportPolicy(valueToStringOrZero(configValue.Get("iceTransportPolicy"))), BundlePolicy: newBundlePolicy(valueToStringOrZero(configValue.Get("bundlePolicy"))), - RTCPMuxPolicy: newRTCPMuxPolicy(valueToStringOrZero(configValue.Get("bundlePolicy"))), + RTCPMuxPolicy: newRTCPMuxPolicy(valueToStringOrZero(configValue.Get("rtcpMuxPolicy"))), PeerIdentity: valueToStringOrZero(configValue.Get("peerIdentity")), ICECandidatePoolSize: valueToUint8OrZero(configValue.Get("iceCandidatePoolSize")), @@ -521,9 +590,13 @@ func dataChannelInitToValue(options *DataChannelInit) js.Value { return js.Undefined() } + maxPacketLifeTime := uint16PointerToValue(options.MaxPacketLifeTime) return js.ValueOf(map[string]interface{}{ "ordered": boolPointerToValue(options.Ordered), - "maxPacketLifeTime": uint16PointerToValue(options.MaxPacketLifeTime), + "maxPacketLifeTime": maxPacketLifeTime, + // See https://bugs.chromium.org/p/chromium/issues/detail?id=696681 + // Chrome calls this "maxRetransmitTime" + "maxRetransmitTime": maxPacketLifeTime, "maxRetransmits": uint16PointerToValue(options.MaxRetransmits), "protocol": stringPointerToValue(options.Protocol), "negotiated": boolPointerToValue(options.Negotiated), diff --git a/peerconnection_js_test.go b/peerconnection_js_test.go new file mode 100644 index 00000000..0a36e4e1 --- /dev/null +++ b/peerconnection_js_test.go @@ -0,0 +1,57 @@ +// +build js + +package webrtc + +import ( + "fmt" + "time" +) + +func signalPair(pcOffer *PeerConnection, pcAnswer *PeerConnection) (err error) { + offerChan := make(chan SessionDescription) + pcOffer.OnICECandidate(func(candidate *string) { + if candidate == nil { + offerChan <- *pcOffer.PendingLocalDescription() + } + }) + + // Note(albrow): We need to create a data channel in order to trigger ICE + // candidate gathering in the background. + if _, err := pcOffer.CreateDataChannel("initial_data_channel", nil); err != nil { + return err + } + + offer, err := pcOffer.CreateOffer(nil) + if err != nil { + return err + } + if err := pcOffer.SetLocalDescription(offer); err != nil { + return err + } + + timeout := time.After(3 * time.Second) + select { + case <-timeout: + return fmt.Errorf("timed out waiting to receive offer") + case offer := <-offerChan: + if err := pcAnswer.SetRemoteDescription(offer); err != nil { + return err + } + + answer, err := pcAnswer.CreateAnswer(nil) + if err != nil { + return err + } + + if err = pcAnswer.SetLocalDescription(answer); err != nil { + return err + } + + err = pcOffer.SetRemoteDescription(answer) + if err != nil { + return err + } + return nil + } + return nil +} diff --git a/peerconnection_media_test.go b/peerconnection_media_test.go index 6b51c32c..3cd33b7f 100644 --- a/peerconnection_media_test.go +++ b/peerconnection_media_test.go @@ -1,3 +1,5 @@ +// +build !js + package webrtc import ( diff --git a/peerconnection_test.go b/peerconnection_test.go index 5a9967d5..3f1211b3 100644 --- a/peerconnection_test.go +++ b/peerconnection_test.go @@ -1,30 +1,24 @@ package webrtc import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/x509" - "math/big" "reflect" + "sync" "testing" "time" - "github.com/pions/transport/test" - "github.com/pions/webrtc/internal/ice" - "github.com/pions/webrtc/internal/mux" - "github.com/pions/webrtc/pkg/rtcerr" "github.com/stretchr/testify/assert" ) -func (api *API) newPair() (pcOffer *PeerConnection, pcAnswer *PeerConnection, err error) { - pca, err := api.NewPeerConnection(Configuration{}) +// newPair creates two new peer connections (an offerer and an answerer) +// *without* using an api (i.e. using the default settings). +func newPair() (pcOffer *PeerConnection, pcAnswer *PeerConnection, err error) { + pca, err := NewPeerConnection(Configuration{}) if err != nil { return nil, nil, err } - pcb, err := api.NewPeerConnection(Configuration{}) + pcb, err := NewPeerConnection(Configuration{}) if err != nil { return nil, nil, err } @@ -32,131 +26,30 @@ func (api *API) newPair() (pcOffer *PeerConnection, pcAnswer *PeerConnection, er return pca, pcb, nil } -func signalPair(pcOffer *PeerConnection, pcAnswer *PeerConnection) error { - offer, err := pcOffer.CreateOffer(nil) - if err != nil { - return err - } - - if err = pcOffer.SetLocalDescription(offer); err != nil { - return err - } - - err = pcAnswer.SetRemoteDescription(offer) - if err != nil { - return err - } - - answer, err := pcAnswer.CreateAnswer(nil) - if err != nil { - return err - } - - if err = pcAnswer.SetLocalDescription(answer); err != nil { - return err - } - - err = pcOffer.SetRemoteDescription(answer) - if err != nil { - return err - } - - return nil -} - func TestNew(t *testing.T) { - api := NewAPI() - t.Run("Success", func(t *testing.T) { - secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - assert.Nil(t, err) - - certificate, err := GenerateCertificate(secretKey) - assert.Nil(t, err) - - pc, err := api.NewPeerConnection(Configuration{ - ICEServers: []ICEServer{ - { - URLs: []string{ - "stun:stun.l.google.com:19302", - "turns:google.de?transport=tcp", - }, - Username: "unittest", - Credential: OAuthCredential{ - MACKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", - AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", - }, - CredentialType: ICECredentialTypeOauth, + pc, err := NewPeerConnection(Configuration{ + ICEServers: []ICEServer{ + { + URLs: []string{ + "stun:stun.l.google.com:19302", }, + Username: "unittest", }, - ICETransportPolicy: ICETransportPolicyRelay, - BundlePolicy: BundlePolicyMaxCompat, - RTCPMuxPolicy: RTCPMuxPolicyNegotiate, - PeerIdentity: "unittest", - Certificates: []Certificate{*certificate}, - ICECandidatePoolSize: 5, - }) - assert.Nil(t, err) - assert.NotNil(t, pc) - }) - t.Run("Failure", func(t *testing.T) { - testCases := []struct { - initialize func() (*PeerConnection, error) - expectedErr error - }{ - {func() (*PeerConnection, error) { - secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - assert.Nil(t, err) - - certificate, err := NewCertificate(secretKey, x509.Certificate{ - Version: 2, - SerialNumber: big.NewInt(1653), - NotBefore: time.Now().AddDate(0, -2, 0), - NotAfter: time.Now().AddDate(0, -1, 0), - }) - assert.Nil(t, err) - - return api.NewPeerConnection(Configuration{ - Certificates: []Certificate{*certificate}, - }) - }, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}}, - {func() (*PeerConnection, error) { - return api.NewPeerConnection(Configuration{ - ICEServers: []ICEServer{ - { - URLs: []string{ - "stun:stun.l.google.com:19302", - "turns:google.de?transport=tcp", - }, - Username: "unittest", - }, - }, - }) - }, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredencials}}, - } - - for i, testCase := range testCases { - _, err := testCase.initialize() - assert.EqualError(t, err, testCase.expectedErr.Error(), - "testCase: %d %v", i, testCase, - ) - } + }, + ICETransportPolicy: ICETransportPolicyRelay, + BundlePolicy: BundlePolicyMaxCompat, + RTCPMuxPolicy: RTCPMuxPolicyNegotiate, + PeerIdentity: "unittest", + ICECandidatePoolSize: 5, }) + assert.NoError(t, err) + assert.NotNil(t, pc) } func TestPeerConnection_SetConfiguration(t *testing.T) { - api := NewAPI() - - secretKey1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - assert.Nil(t, err) - - certificate1, err := GenerateCertificate(secretKey1) - assert.Nil(t, err) - - secretKey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - assert.Nil(t, err) - - certificate2, err := GenerateCertificate(secretKey2) - assert.Nil(t, err) + // Note: These tests don't include ICEServer.Credential, + // ICEServer.CredentialType, or Certificates because those are not supported + // in the WASM bindings. for _, test := range []struct { name string @@ -167,9 +60,7 @@ func TestPeerConnection_SetConfiguration(t *testing.T) { { name: "valid", init: func() (*PeerConnection, error) { - pc, err := api.NewPeerConnection(Configuration{ - PeerIdentity: "unittest", - Certificates: []Certificate{*certificate1}, + pc, err := NewPeerConnection(Configuration{ ICECandidatePoolSize: 5, }) if err != nil { @@ -181,21 +72,13 @@ func TestPeerConnection_SetConfiguration(t *testing.T) { { URLs: []string{ "stun:stun.l.google.com:19302", - "turns:google.de?transport=tcp", }, Username: "unittest", - Credential: OAuthCredential{ - MACKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", - AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", - }, - CredentialType: ICECredentialTypeOauth, }, }, ICETransportPolicy: ICETransportPolicyAll, BundlePolicy: BundlePolicyBalanced, RTCPMuxPolicy: RTCPMuxPolicyRequire, - PeerIdentity: "unittest", - Certificates: []Certificate{*certificate1}, ICECandidatePoolSize: 5, }) if err != nil { @@ -210,7 +93,7 @@ func TestPeerConnection_SetConfiguration(t *testing.T) { { name: "closed connection", init: func() (*PeerConnection, error) { - pc, err := api.NewPeerConnection(Configuration{}) + pc, err := NewPeerConnection(Configuration{}) assert.Nil(t, err) err = pc.Close() @@ -223,37 +106,17 @@ func TestPeerConnection_SetConfiguration(t *testing.T) { { name: "update PeerIdentity", init: func() (*PeerConnection, error) { - return api.NewPeerConnection(Configuration{}) + return NewPeerConnection(Configuration{}) }, config: Configuration{ PeerIdentity: "unittest", }, wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity}, }, - { - name: "update multiple certificates", - init: func() (*PeerConnection, error) { - return api.NewPeerConnection(Configuration{}) - }, - config: Configuration{ - Certificates: []Certificate{*certificate1, *certificate2}, - }, - wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}, - }, - { - name: "update certificate", - init: func() (*PeerConnection, error) { - return api.NewPeerConnection(Configuration{}) - }, - config: Configuration{ - Certificates: []Certificate{*certificate1}, - }, - wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}, - }, { name: "update BundlePolicy", init: func() (*PeerConnection, error) { - return api.NewPeerConnection(Configuration{}) + return NewPeerConnection(Configuration{}) }, config: Configuration{ BundlePolicy: BundlePolicyMaxCompat, @@ -263,7 +126,7 @@ func TestPeerConnection_SetConfiguration(t *testing.T) { { name: "update RTCPMuxPolicy", init: func() (*PeerConnection, error) { - return api.NewPeerConnection(Configuration{}) + return NewPeerConnection(Configuration{}) }, config: Configuration{ RTCPMuxPolicy: RTCPMuxPolicyNegotiate, @@ -273,7 +136,7 @@ func TestPeerConnection_SetConfiguration(t *testing.T) { { name: "update ICECandidatePoolSize", init: func() (*PeerConnection, error) { - pc, err := api.NewPeerConnection(Configuration{ + pc, err := NewPeerConnection(Configuration{ ICECandidatePoolSize: 0, }) if err != nil { @@ -297,7 +160,7 @@ func TestPeerConnection_SetConfiguration(t *testing.T) { { name: "update ICEServers, no TURN credentials", init: func() (*PeerConnection, error) { - return api.NewPeerConnection(Configuration{}) + return NewPeerConnection(Configuration{}) }, config: Configuration{ ICEServers: []ICEServer{ @@ -315,20 +178,19 @@ func TestPeerConnection_SetConfiguration(t *testing.T) { } { pc, err := test.init() if err != nil { - t.Fatalf("SetConfiguration %q: init failed: %v", test.name, err) + t.Errorf("SetConfiguration %q: init failed: %v", test.name, err) } err = pc.SetConfiguration(test.config) if got, want := err, test.wantErr; !reflect.DeepEqual(got, want) { - t.Fatalf("SetConfiguration %q: err = %v, want %v", test.name, got, want) + t.Errorf("SetConfiguration %q: err = %v, want %v", test.name, got, want) } } } func TestPeerConnection_GetConfiguration(t *testing.T) { - api := NewAPI() - pc, err := api.NewPeerConnection(Configuration{}) - assert.Nil(t, err) + pc, err := NewPeerConnection(Configuration{}) + assert.NoError(t, err) expected := Configuration{ ICEServers: []ICEServer{}, @@ -344,41 +206,33 @@ func TestPeerConnection_GetConfiguration(t *testing.T) { assert.Equal(t, expected.ICETransportPolicy, actual.ICETransportPolicy) assert.Equal(t, expected.BundlePolicy, actual.BundlePolicy) assert.Equal(t, expected.RTCPMuxPolicy, actual.RTCPMuxPolicy) - assert.NotEqual(t, len(expected.Certificates), len(actual.Certificates)) + // TODO(albrow): Uncomment this after #513 is fixed. + // See: https://github.com/pions/webrtc/issues/513. + // assert.Equal(t, len(expected.Certificates), len(actual.Certificates)) assert.Equal(t, expected.ICECandidatePoolSize, actual.ICECandidatePoolSize) } -// TODO - This unittest needs to be completed when CreateDataChannel is complete -// func TestPeerConnection_CreateDataChannel(t *testing.T) { -// pc, err := New(Configuration{}) -// assert.Nil(t, err) -// -// _, err = pc.CreateDataChannel("data", &DataChannelInit{ -// -// }) -// assert.Nil(t, err) -// } - -// TODO Fix this test const minimalOffer = `v=0 -o=- 7193157174393298413 2 IN IP4 127.0.0.1 +o=- 4596489990601351948 2 IN IP4 127.0.0.1 s=- t=0 0 -a=group:BUNDLE video -m=video 43858 UDP/TLS/RTP/SAVPF 96 -c=IN IP4 172.17.0.1 -a=candidate:3885250869 1 udp 1 127.0.0.1 1 typ host -a=ice-ufrag:OgYk -a=ice-pwd:G0ka4ts7hRhMLNljuuXzqnOF -a=fingerprint:sha-256 D7:06:10:DE:69:66:B1:53:0E:02:33:45:63:F8:AF:78:B2:C7:CE:AF:8E:FD:E5:13:20:50:74:93:CD:B5:C8:69 -a=setup:active -a=mid:video -a=sendrecv -a=rtpmap:96 VP8/90000 +a=msid-semantic: WMS +m=application 47299 DTLS/SCTP 5000 +c=IN IP4 192.168.20.129 +a=candidate:1966762134 1 udp 2122260223 192.168.20.129 47299 typ host generation 0 +a=candidate:211962667 1 udp 2122194687 10.0.3.1 40864 typ host generation 0 +a=candidate:1002017894 1 tcp 1518280447 192.168.20.129 0 typ host tcptype active generation 0 +a=candidate:1109506011 1 tcp 1518214911 10.0.3.1 0 typ host tcptype active generation 0 +a=ice-ufrag:1/MvHwjAyVf27aLu +a=ice-pwd:3dBU7cFOBl120v33cynDvN1E +a=ice-options:google-ice +a=fingerprint:sha-256 75:74:5A:A6:A4:E5:52:F4:A7:67:4C:01:C7:EE:91:3F:21:3D:A2:E3:53:7B:6F:30:86:F2:30:AA:65:FB:04:24 +a=setup:actpass +a=mid:data +a=sctpmap:5000 webrtc-datachannel 1024 ` func TestSetRemoteDescription(t *testing.T) { - api := NewAPI() testCases := []struct { desc SessionDescription }{ @@ -386,7 +240,7 @@ func TestSetRemoteDescription(t *testing.T) { } for i, testCase := range testCases { - peerConn, err := api.NewPeerConnection(Configuration{}) + peerConn, err := NewPeerConnection(Configuration{}) if err != nil { t.Errorf("Case %d: got error: %v", i, err) } @@ -398,8 +252,7 @@ func TestSetRemoteDescription(t *testing.T) { } func TestCreateOfferAnswer(t *testing.T) { - api := NewAPI() - offerPeerConn, err := api.NewPeerConnection(Configuration{}) + offerPeerConn, err := NewPeerConnection(Configuration{}) if err != nil { t.Errorf("New PeerConnection: got error: %v", err) } @@ -410,7 +263,7 @@ func TestCreateOfferAnswer(t *testing.T) { if err = offerPeerConn.SetLocalDescription(offer); err != nil { t.Errorf("SetLocalDescription: got error: %v", err) } - answerPeerConn, err := api.NewPeerConnection(Configuration{}) + answerPeerConn, err := NewPeerConnection(Configuration{}) if err != nil { t.Errorf("New PeerConnection: got error: %v", err) } @@ -432,98 +285,75 @@ func TestCreateOfferAnswer(t *testing.T) { } func TestPeerConnection_EventHandlers(t *testing.T) { - api := NewAPI() - pc, err := api.NewPeerConnection(Configuration{}) - assert.Nil(t, err) + pcOffer, err := NewPeerConnection(Configuration{}) + assert.NoError(t, err) + pcAnswer, err := NewPeerConnection(Configuration{}) + assert.NoError(t, err) - onTrackCalled := make(chan bool) - onICEConnectionStateChangeCalled := make(chan bool) - onDataChannelCalled := make(chan bool) + // wasCalled is a list of event handlers that were called. + wasCalled := []string{} + wasCalledMut := &sync.Mutex{} + // wg is used to wait for all event handlers to be called. + wg := &sync.WaitGroup{} + wg.Add(4) - // Verify that the noop case works - assert.NotPanics(t, func() { pc.onTrack(nil, nil) }) - assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) + // Each sync.Once is used to ensure that we call wg.Done once for each event + // handler and don't add multiple entries to wasCalled. The event handlers can + // be called more than once in some cases. + onceOffererOnICEConnectionStateChange := &sync.Once{} + onceOffererOnSignalingStateChange := &sync.Once{} + onceAnswererOnICEConnectionStateChange := &sync.Once{} + onceAnswererOnSignalingStateChange := &sync.Once{} - pc.OnTrack(func(t *Track, r *RTPReceiver) { - onTrackCalled <- true + // Register all the event handlers. + pcOffer.OnICEConnectionStateChange(func(ICEConnectionState) { + onceOffererOnICEConnectionStateChange.Do(func() { + wasCalledMut.Lock() + defer wasCalledMut.Unlock() + wasCalled = append(wasCalled, "offerer OnICEConnectionStateChange") + wg.Done() + }) + }) + pcOffer.OnSignalingStateChange(func(SignalingState) { + onceOffererOnSignalingStateChange.Do(func() { + wasCalledMut.Lock() + defer wasCalledMut.Unlock() + wasCalled = append(wasCalled, "offerer OnSignalingStateChange") + wg.Done() + }) + }) + pcAnswer.OnICEConnectionStateChange(func(ICEConnectionState) { + onceAnswererOnICEConnectionStateChange.Do(func() { + wasCalledMut.Lock() + defer wasCalledMut.Unlock() + wasCalled = append(wasCalled, "answerer OnICEConnectionStateChange") + wg.Done() + }) + }) + pcAnswer.OnSignalingStateChange(func(SignalingState) { + onceAnswererOnSignalingStateChange.Do(func() { + wasCalledMut.Lock() + defer wasCalledMut.Unlock() + wasCalled = append(wasCalled, "answerer OnSignalingStateChange") + wg.Done() + }) }) - pc.OnICEConnectionStateChange(func(cs ICEConnectionState) { - onICEConnectionStateChangeCalled <- true - }) + // Use signalPair to establish a connection between pcOffer and pcAnswer. This + // process should trigger the above event handlers. + assert.NoError(t, signalPair(pcOffer, pcAnswer)) - pc.OnDataChannel(func(dc *DataChannel) { - onDataChannelCalled <- true - }) - - // Verify that the handlers deal with nil inputs - assert.NotPanics(t, func() { pc.onTrack(nil, nil) }) - assert.NotPanics(t, func() { go pc.onDataChannelHandler(nil) }) - - // Verify that the set handlers are called - assert.NotPanics(t, func() { pc.onTrack(&Track{}, &RTPReceiver{}) }) - assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) - assert.NotPanics(t, func() { go pc.onDataChannelHandler(&DataChannel{api: api}) }) - - allTrue := func(vals []bool) bool { - for _, val := range vals { - if !val { - return false - } - } - return true - } - - assert.True(t, allTrue([]bool{ - <-onTrackCalled, - <-onICEConnectionStateChangeCalled, - <-onDataChannelCalled, - })) -} - -// This test asserts that nothing deadlocks we try to shutdown when DTLS is in flight -// We ensure that DTLS is in flight by removing the mux func for it, so all inbound DTLS is lost -func TestPeerConnection_ShutdownNoDTLS(t *testing.T) { - dtlsMatchFunc := mux.MatchDTLS - defer func() { - mux.MatchDTLS = dtlsMatchFunc + // Wait for all of the event handlers to be triggered. + done := make(chan struct{}) + go func() { + wg.Wait() + done <- struct{}{} }() - - // Drop all incoming DTLS traffic - mux.MatchDTLS = func([]byte) bool { - return false - } - - lim := test.TimeOut(time.Second * 10) - defer lim.Stop() - - api := NewAPI() - offerPC, answerPC, err := api.newPair() - if err != nil { - t.Fatal(err) - } - - if err = signalPair(offerPC, answerPC); err != nil { - t.Fatal(err) - } - - iceComplete := make(chan interface{}) - answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) { - if iceState == ICEConnectionStateConnected { - time.Sleep(time.Second) // Give time for DTLS to start - - select { - case <-iceComplete: - default: - close(iceComplete) - } - } - }) - - <-iceComplete - if err = offerPC.Close(); err != nil { - t.Fatal(err) - } else if err = answerPC.Close(); err != nil { - t.Fatal(err) + timeout := time.After(5 * time.Second) + select { + case <-done: + break + case <-timeout: + t.Fatalf("timed out waiting for one or more events handlers to be called (these *were* called: %+v)", wasCalled) } } diff --git a/quictransport_test.go b/quictransport_test.go index 3e045b54..1a14753e 100644 --- a/quictransport_test.go +++ b/quictransport_test.go @@ -1,3 +1,5 @@ +// +build !js + package webrtc import ( diff --git a/test-wasm/LICENSE b/test-wasm/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/test-wasm/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/test-wasm/go_js_wasm_exec b/test-wasm/go_js_wasm_exec new file mode 100755 index 00000000..e1fb99f1 --- /dev/null +++ b/test-wasm/go_js_wasm_exec @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright 2018 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# Modified work copyright 2019 Alex Browne. + +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ]; do + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" +done +DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + +# We changed this line to require our node_shim.js. +exec node --require=./test-wasm/node_shim.js "$(go env GOROOT)/misc/wasm/wasm_exec.js" "$@" diff --git a/test-wasm/node_shim.js b/test-wasm/node_shim.js new file mode 100644 index 00000000..9aeb245c --- /dev/null +++ b/test-wasm/node_shim.js @@ -0,0 +1,10 @@ +// This file adds RTCPeerConnection to the global context, making Node.js more +// closely match the browser API for WebRTC. + +const wrtc = require("wrtc"); + +global.window = { + RTCPeerConnection: wrtc.RTCPeerConnection +}; + +global.RTCPeerConnection = wrtc.RTCPeerConnection; diff --git a/track_test.go b/track_test.go index c4eaf973..817f88cd 100644 --- a/track_test.go +++ b/track_test.go @@ -1,3 +1,5 @@ +// +build !js + package webrtc import ( diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..e72deb46 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,774 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= + +canvas@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.3.1.tgz#da0c8a916505aa34f9365d6b77d28b969241bfd0" + integrity sha512-jSxwf4V9AGD6t6yBC600xFZKjrfKTR0T0RUNlX/AODs/ifrfJHIQjFEK8iF2euNy6z7K3GNv82DJgTjYZZktqA== + dependencies: + nan "^2.12.1" + node-pre-gyp "^0.11.0" + +chownr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +debug@^2.1.2: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +decamelize@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +error-ex@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== + dependencies: + minipass "^2.2.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + +glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.1.2: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +hosted-git-info@^2.1.4: + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== + +iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + dependencies: + minimatch "^3.0.4" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + dependencies: + invert-kv "^1.0.0" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minipass@^2.2.1, minipass@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" + integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== + dependencies: + minipass "^2.2.1" + +mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +nan@*, nan@^2.12.1, nan@^2.3.2: + version "2.12.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" + integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== + +needle@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + +node-cmake@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/node-cmake/-/node-cmake-2.3.2.tgz#e0fbc54b11405b07705e4d6d41865ae95ad289d0" + integrity sha1-4PvFSxFAWwdwXk1tQYZa6VrSidA= + dependencies: + nan "*" + which "^1.2.14" + yargs "^7.0.2" + +node-pre-gyp@0.11.x, node-pre-gyp@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" + integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +npm-bundled@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" + integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== + +npm-packlist@^1.1.6: + version "1.4.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" + integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= + dependencies: + pinkie-promise "^2.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +readable-stream@^2.0.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= + +resolve@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" + integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== + dependencies: + path-parse "^1.0.6" + +rimraf@^2.6.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +"semver@2 || 3 || 4 || 5", semver@^5.3.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" + integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g== + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= + dependencies: + is-utf8 "^0.2.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +tar@^4: + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" + integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.4" + minizlib "^1.1.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= + +which@^1.2.14: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +wrtc@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/wrtc/-/wrtc-0.3.5.tgz#c766f17f56c30685595224b5728a231ccf0b00cf" + integrity sha512-kzrZpQIrMfcX6qDpDeOyUHm61I9ku6BPEH6PU1Y6+SULQj3yAxqrUCk4PIrqN7ED4OFmk48jk6Qo8oh8ZDepqg== + dependencies: + nan "^2.3.2" + node-cmake "2.3.2" + node-pre-gyp "0.11.x" + optionalDependencies: + canvas "^2.3.0" + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" + integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== + +yargs-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= + dependencies: + camelcase "^3.0.0" + +yargs@^7.0.2: + version "7.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" + integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^5.0.0"