Skip to content

Commit c18c099

Browse files
committed
Add support for emscripten websocket API
Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent 1ab26c1 commit c18c099

21 files changed

+1402
-65
lines changed

examples/emscripten/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ include(BuildErlang)
2525
pack_runnable(run_script run_script estdlib eavmlib)
2626
pack_runnable(call_cast call_cast eavmlib)
2727
pack_runnable(html5_events html5_events estdlib eavmlib)
28+
pack_runnable(echo_websocket echo_websocket estdlib eavmlib)
2829
pack_runnable(wasm_webserver wasm_webserver estdlib eavmlib)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2025 Paul Guyot <pguyot@kallisys.net>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License");
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
21+
-module(echo_websocket).
22+
-export([start/0]).
23+
24+
-define(ECHO_WEBSOCKET_URL, <<"wss://echo.websocket.org">>).
25+
26+
start() ->
27+
register(main, self()),
28+
Supported = websocket:is_supported(),
29+
if
30+
Supported ->
31+
emscripten:run_script(
32+
[
33+
<<"window.document.getElementById('supported').innerHTML = 'yes';">>,
34+
<<"window.document.getElementById('send-button').onclick = () => { Module.cast('main', window.document.getElementById('send-text').value); };">>
35+
],
36+
[main_thread, async]
37+
),
38+
websocket_loop();
39+
true ->
40+
emscripten:run_script(
41+
[
42+
<<"window.document.getElementById('supported').innerHTML = 'no';">>
43+
],
44+
[main_thread, async]
45+
)
46+
end.
47+
48+
websocket_loop() ->
49+
Websocket = websocket:new(?ECHO_WEBSOCKET_URL),
50+
websocket_loop(Websocket).
51+
52+
websocket_loop(Websocket) ->
53+
ReadyState = websocket:ready_state(Websocket),
54+
URL = websocket:url(Websocket),
55+
Protocol = websocket:protocol(Websocket),
56+
Extensions = websocket:extensions(Websocket),
57+
emscripten:run_script(
58+
[
59+
<<"window.document.getElementById('ready-state').innerHTML = '">>, atom_to_list(ReadyState), <<"';">>,
60+
<<"window.document.getElementById('url').innerHTML = '">>, URL, <<"';">>,
61+
<<"window.document.getElementById('protocol').innerHTML = '">>, Protocol, <<"';">>,
62+
<<"window.document.getElementById('extensions').innerHTML = '">>, Extensions, <<"';">>
63+
],
64+
[main_thread, async]
65+
),
66+
receive
67+
{emscripten, {cast, Message}} ->
68+
emscripten:run_script(
69+
[
70+
<<"const e = window.document.createElement('div');">>,
71+
<<"e.classList.add('client-msg');">>,
72+
<<"e.append(\"">>,
73+
escape_js_str(binary_to_list(Message)),
74+
<<"\");">>,
75+
<<"window.document.getElementById('transcript').appendChild(e);">>
76+
],
77+
[main_thread, async]
78+
),
79+
ok = websocket:send(Websocket, Message),
80+
websocket_loop(Websocket);
81+
{websocket_open, Websocket} ->
82+
emscripten:run_script(
83+
[
84+
<<"window.document.getElementById('send-text').disabled = false;">>,
85+
<<"window.document.getElementById('send-button').disabled = false;">>
86+
],
87+
[main_thread, async]
88+
),
89+
websocket_loop(Websocket);
90+
{websocket, Websocket, Data} ->
91+
emscripten:run_script(
92+
[
93+
<<"const e = window.document.createElement('div');">>,
94+
<<"e.classList.add('server-msg');">>,
95+
<<"e.append(\"">>,
96+
escape_js_str(binary_to_list(Data)),
97+
<<"\");">>,
98+
<<"window.document.getElementById('transcript').appendChild(e);">>
99+
],
100+
[main_thread, async]
101+
),
102+
websocket_loop(Websocket);
103+
{websocket_error, Websocket} ->
104+
emscripten:run_script(
105+
[
106+
<<"window.document.getElementById('send-text').disabled = false;">>,
107+
<<"window.document.getElementById('send-button').disabled = false;">>,
108+
<<"const e = window.document.createElement('div');">>,
109+
<<"e.classList.add('error-msg');">>,
110+
<<"e.append('Error');">>,
111+
<<"window.document.getElementById('transcript').appendChild(e);">>
112+
],
113+
[main_thread, async]
114+
);
115+
{websocket_closed, Websocket, {WasClean, Code, Reason}} ->
116+
emscripten:run_script(
117+
[
118+
<<"window.document.getElementById('send-text').disabled = true;">>,
119+
<<"window.document.getElementById('send-button').disabled = true;">>,
120+
<<"const e = window.document.createElement('div');">>,
121+
<<"e.classList.add('close-msg');">>,
122+
<<"const wasClean = window.document.createElement('p');">>,
123+
<<"wasClean.append(\"">>, atom_to_list(WasClean), <<"\");">>,
124+
<<"e.appendChild(wasClean);">>,
125+
<<"const code = window.document.createElement('p');">>,
126+
<<"code.append(\"">>, integer_to_list(Code), <<"\");">>,
127+
<<"e.appendChild(code);">>,
128+
<<"const reason = window.document.createElement('p');">>,
129+
<<"reason.append(\"">>, escape_js_str(binary_to_list(Reason)), <<"\");">>,
130+
<<"e.appendChild(reason);">>,
131+
<<"window.document.getElementById('transcript').appendChild(e);">>
132+
],
133+
[main_thread, async]
134+
);
135+
Other ->
136+
io:format("Unexpected message ~p\n", [Other]),
137+
websocket_loop(Websocket)
138+
end.
139+
140+
escape_js_str(Str) ->
141+
escape_js_str(Str, []).
142+
143+
escape_js_str([$" | Tail], Acc) ->
144+
escape_js_str(Tail, ["\\\"" | Acc]);
145+
escape_js_str([$\n | Tail], Acc) ->
146+
escape_js_str(Tail, ["<br />" | Acc]);
147+
escape_js_str([C | Tail], Acc) ->
148+
escape_js_str(Tail, [C | Acc]);
149+
escape_js_str([], Acc) ->
150+
lists:reverse(Acc).
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!--
2+
This file is part of AtomVM.
3+
4+
Copyright 2025 Paul Guyot <pguyot@kallisys.net>
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
-->
20+
<!doctype html>
21+
<html lang="en" dir="ltr">
22+
<head>
23+
<meta charset="utf-8" />
24+
<title>AtomVM echo websocket example</title>
25+
<style type="text/css">
26+
#transcript {
27+
margin: 1ex 0;
28+
padding: 1ex;
29+
display: flex;
30+
flex-direction: column;
31+
width: 40em;
32+
border: 1px dotted black;
33+
}
34+
#transcript div {
35+
border-bottom-color: rgb(229, 231, 235);
36+
border-radius: 8px;
37+
border-radius: 8px;
38+
border-style: solid;
39+
border-width: 0px;
40+
}
41+
.client-msg {
42+
margin: 1ex;
43+
padding: 2ex;
44+
background-color: rgb(22, 163, 74);
45+
color: white;
46+
align-self: flex-end;
47+
}
48+
.server-msg {
49+
margin: 1ex;
50+
padding: 2ex;
51+
background-color: rgb(239, 239, 239);
52+
color: black;
53+
align-self: flex-start;
54+
}
55+
.error-msg {
56+
margin: 2ex;
57+
background-color: rgba(239, 68, 68);
58+
color: white;
59+
align-self: flex-start;
60+
}
61+
</style>
62+
</head>
63+
<body>
64+
<h1>Echo websocket example</h1>
65+
<p>
66+
This example demonstrates use of websocket API by connecting to
67+
demo API at echo.websocket.org.
68+
</p>
69+
<p>Websockets are supported by this browser: <span id="supported">N/A</span></p>
70+
<p>Websocket ReadyState: <span id="ready-state">N/A</span></p>
71+
<p>Websocket URL: <span id="url">N/A</span></p>
72+
<p>Websocket Protocol: <span id="protocol">N/A</span></p>
73+
<p>Websocket Extensions: <span id="extensions">N/A</span></p>
74+
75+
<div id="transcript"></div>
76+
<input type="text" name="text" id="send-text" disabled></input>
77+
<button id="send-button" disabled>Send</button>
78+
79+
<script>
80+
// Arguments are loaded using fetch API.
81+
// wasm_webserver serves under /build/ files in build subdirectory.
82+
var Module = {
83+
arguments: ["/build/examples/emscripten/echo_websocket.avm"],
84+
};
85+
</script>
86+
<script async src="/AtomVM.js"></script>
87+
</body>
88+
</html>

examples/emscripten/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ <h1>AtomVM emscripten examples</h1>
3636
<li><a href="run_script.html">Run script</a></li>
3737
<li><a href="call_cast.html">Call &amp; cast</a></li>
3838
<li><a href="html5_events.html">HTML5 Events</a></li>
39+
<li><a href="echo_websocket.html">Echo websocket</a></li>
3940
</ul>
4041
</body>
4142
</html>

libs/eavmlib/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ set(ERLANG_MODULES
4545
timer_manager
4646
timestamp_util
4747
uart
48+
websocket
4849
)
4950

5051
pack_archive(eavmlib ${ERLANG_MODULES})

0 commit comments

Comments
 (0)