1// +build js,wasm
2
3package main
4
5import (
6	"fmt"
7	"io"
8	"syscall/js"
9	"time"
10
11	"github.com/pion/webrtc/v3"
12
13	"github.com/pion/webrtc/v3/examples/internal/signal"
14)
15
16const messageSize = 15
17
18func main() {
19	// Since this behavior diverges from the WebRTC API it has to be
20	// enabled using a settings engine. Mixing both detached and the
21	// OnMessage DataChannel API is not supported.
22
23	// Create a SettingEngine and enable Detach
24	s := webrtc.SettingEngine{}
25	s.DetachDataChannels()
26
27	// Create an API object with the engine
28	api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
29
30	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
31
32	// Prepare the configuration
33	config := webrtc.Configuration{
34		ICEServers: []webrtc.ICEServer{
35			{
36				URLs: []string{"stun:stun.l.google.com:19302"},
37			},
38		},
39	}
40
41	// Create a new RTCPeerConnection using the API object
42	peerConnection, err := api.NewPeerConnection(config)
43	if err != nil {
44		handleError(err)
45	}
46
47	// Create a datachannel with label 'data'
48	dataChannel, err := peerConnection.CreateDataChannel("data", nil)
49	if err != nil {
50		handleError(err)
51	}
52
53	// Set the handler for ICE connection state
54	// This will notify you when the peer has connected/disconnected
55	peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
56		log(fmt.Sprintf("ICE Connection State has changed: %s\n", connectionState.String()))
57	})
58
59	// Register channel opening handling
60	dataChannel.OnOpen(func() {
61		log(fmt.Sprintf("Data channel '%s'-'%d' open.\n", dataChannel.Label(), dataChannel.ID()))
62
63		// Detach the data channel
64		raw, dErr := dataChannel.Detach()
65		if dErr != nil {
66			handleError(dErr)
67		}
68
69		// Handle reading from the data channel
70		go ReadLoop(raw)
71
72		// Handle writing to the data channel
73		go WriteLoop(raw)
74	})
75
76	// Create an offer to send to the browser
77	offer, err := peerConnection.CreateOffer(nil)
78	if err != nil {
79		handleError(err)
80	}
81
82	// Sets the LocalDescription, and starts our UDP listeners
83	err = peerConnection.SetLocalDescription(offer)
84	if err != nil {
85		handleError(err)
86	}
87
88	// Add handlers for setting up the connection.
89	peerConnection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
90		log(fmt.Sprint(state))
91	})
92	peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
93		if candidate != nil {
94			encodedDescr := signal.Encode(peerConnection.LocalDescription())
95			el := getElementByID("localSessionDescription")
96			el.Set("value", encodedDescr)
97		}
98	})
99
100	// Set up global callbacks which will be triggered on button clicks.
101	/*js.Global().Set("sendMessage", js.FuncOf(func(_ js.Value, _ []js.Value) interface{} {
102		go func() {
103			el := getElementByID("message")
104			message := el.Get("value").String()
105			if message == "" {
106				js.Global().Call("alert", "Message must not be empty")
107				return
108			}
109			if err := sendChannel.SendText(message); err != nil {
110				handleError(err)
111			}
112		}()
113		return js.Undefined()
114	}))*/
115	js.Global().Set("startSession", js.FuncOf(func(_ js.Value, _ []js.Value) interface{} {
116		go func() {
117			el := getElementByID("remoteSessionDescription")
118			sd := el.Get("value").String()
119			if sd == "" {
120				js.Global().Call("alert", "Session Description must not be empty")
121				return
122			}
123
124			descr := webrtc.SessionDescription{}
125			signal.Decode(sd, &descr)
126			if err := peerConnection.SetRemoteDescription(descr); err != nil {
127				handleError(err)
128			}
129		}()
130		return js.Undefined()
131	}))
132
133	// Block forever
134	select {}
135}
136
137// ReadLoop shows how to read from the datachannel directly
138func ReadLoop(d io.Reader) {
139	for {
140		buffer := make([]byte, messageSize)
141		n, err := d.Read(buffer)
142		if err != nil {
143			log(fmt.Sprintf("Datachannel closed; Exit the readloop: %v", err))
144			return
145		}
146
147		log(fmt.Sprintf("Message from DataChannel: %s\n", string(buffer[:n])))
148	}
149}
150
151// WriteLoop shows how to write to the datachannel directly
152func WriteLoop(d io.Writer) {
153	for range time.NewTicker(5 * time.Second).C {
154		message := signal.RandSeq(messageSize)
155		log(fmt.Sprintf("Sending %s \n", message))
156
157		_, err := d.Write([]byte(message))
158		if err != nil {
159			handleError(err)
160		}
161	}
162}
163
164func log(msg string) {
165	el := getElementByID("logs")
166	el.Set("innerHTML", el.Get("innerHTML").String()+msg+"<br>")
167}
168
169func handleError(err error) {
170	log("Unexpected error. Check console.")
171	panic(err)
172}
173
174func getElementByID(id string) js.Value {
175	return js.Global().Get("document").Call("getElementById", id)
176}
177