forked from TheDiscordian/browser-ipfs-chat
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
260 lines (234 loc) · 8.13 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
<!DOCTYPE html>
<html lang="en">
<head>
<title>ipfs chat</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./bootstrap.min.css">
<script src="./ipfs.min.js"></script>
<style>
html {
height: 100%;
margin: 0;
overflow: hidden;
}
body {
height: 100%;
margin: 0;
overflow: hidden;
display: flex;
flex-flow: column;
}
.container-fluid {
flex: 1 1 auto;
display: flex;
flex-flow: column;
padding: 0;
margin: 0;
}
.input-field {
height:2.1em;
margin-top:auto;
padding-left:0.1em;
padding-right:0.1em;
}
</style>
<script>
const bootstraps = [ '/dns6/ipfs.thedisco.zone/tcp/4430/wss/p2p/12D3KooWChhhfGdB9GJy1GbhghAAKCUR99oCymMEVS4eUcEy67nt', '/dns4/ipfs.thedisco.zone/tcp/4430/wss/p2p/12D3KooWChhhfGdB9GJy1GbhghAAKCUR99oCymMEVS4eUcEy67nt' ];
const prefix = "discochat-";
var lastAlive = 0; // last keep-alive we saw from a relay
var lastPeer = 0; // last keep-alive we saw from another peer
var lastBootstrap = 0; // used for tracking when we last attempted to bootstrap (likely to reconnect to a relay)
var ipfs;
var peerCount = 0; // this is kind of a janky way to track peer count. really it'd be better to store the peers
// in a map, along with their last "peer-alive", to track peer count in a stable way.
// out is used for processing recieved messages and outputting them both to console and the message box.
function out(msg) {
msg = new TextDecoder().decode(msg.data);
console.log(msg);
c = document.getElementById("chat");
c.innerHTML += "<br>"+msg.replace(/</g, "<").replace(/>/g, ">");
c.scrollTop = c.scrollHeight;
}
// usage: await sendmsg("Hello", "example_channel");
async function sendmsg(msg, chan) {
await ipfs.pubsub.publish(prefix+chan, msg);
}
// used for triggering a sendmsg from user input
async function sendMsg() {
displayn = document.getElementById("displayInput").value;
msg = document.getElementById("chatInput").value;
if (displayn == "" || msg == "") {
return true;
}
sendmsg("["+displayn+"] " + msg, "global");
document.getElementById("chatInput").value = "";
}
// usage: await joinchan("example_channel");
async function joinchan(chan) {
await ipfs.pubsub.subscribe(prefix+chan, out);
}
// check if we're still connected to the circuit relay (not required, but let's us know if we can see peers who may be stuck behind NAT)
function checkalive() {
now = new Date().getTime();
if (now-lastAlive >= 35000) {
if (now-lastPeer >= 35000) {
document.getElementById("status-ball").style.color = "red";
} else {
document.getElementById("status-ball").style.color = "yellow";
}
dobootstrap(true); // sometimes we appear to be connected to the bootstrap nodes, but we're not, so let's try to reconnect
} else {
document.getElementById("status-ball").style.color = "lime";
}
}
// processes a circuit-relay announce over pubsub
async function processAnnounce(addr) {
// get our peerid
me = await ipfs.id();
me = me.id;
// not really an announcement if it's from us
if (addr.from == me) {
return;
}
// process the recieved address
addr = new TextDecoder().decode(addr.data);
if (addr == "peer-alive") {
console.log(addr);
pcDisplay = document.getElementById("peerCount");
peerCount += 1;
pcDisplay.innerHTML = peerCount.toString();
setTimeout(function(){
peerCount -= 1;
pcDisplay.innerHTML = peerCount.toString();
}, 15000);
lastPeer = new Date().getTime();
return;
}
// keep-alives are also sent over here, so let's update that global first
lastAlive = new Date().getTime();
if (addr == "keep-alive") {
console.log(addr);
return;
}
peer = addr.split("/")[9];
console.log("Peer: " + peer);
console.log("Me: " + me);
if (peer == me) {
return;
}
// get a list of peers
peers = await ipfs.swarm.peers();
for (i in peers) {
// if we're already connected to the peer, don't bother doing a circuit connection
if (peers[i].peer == peer) {
return;
}
}
// log the address to console as we're about to attempt a connection
console.log(addr);
// connection almost always fails the first time, but almost always succeeds the second time, so we do this:
try {
await ipfs.swarm.connect(addr);
} catch(err) {
console.log(err);
await ipfs.swarm.connect(addr);
}
}
// if reconnect is true, it'll first attempt to disconnect from the bootstrap nodes
async function dobootstrap(reconnect) {
now = new Date().getTime();
if (now-lastBootstrap < 60000) { // don't try to bootstrap again if we just tried within the last 60 seconds
return;
}
lastBootstrap = now;
for (i in bootstraps) {
if (reconnect) {
try {
await ipfs.swarm.disconnect(bootstraps[i]);
} catch (e) {
console.log(e);
}
} else {
await ipfs.bootstrap.add(bootstraps[i]);
}
await ipfs.swarm.connect(bootstraps[i]);
}
}
// set this to body's onload function
async function onload() {
ipfs = await Ipfs.create({
repo: 'ok' + Math.random(), // random so we get a new peerid every time, useful for testing
relay: {
enabled: true,
hop: {
enabled: true
}
},
config: {
Addresses: {
Swarm: [ '/dns4/star.thedisco.zone/tcp/9090/wss/p2p-webrtc-star', '/dns6/star.thedisco.zone/tcp/9090/wss/p2p-webrtc-star' ]
},
// FIXME Announce: [ '/p2p/12D3KooWChhhfGdB9GJy1GbhghAAKCUR99oCymMEVS4eUcEy67nt/p2p-circuit' ],
}});
// add bootstraps for next time, and attempt connection just in case we're not already connected
await dobootstrap(false);
// join a global channel, because we don't have real chat channels implemented yet
joinchan("global");
// publish and subscribe to keepalive to help keep the sockets open
await ipfs.pubsub.subscribe(prefix+"keepalive");
setInterval(function(){sendmsg("1", prefix+"keepalive");}, 4000);
setInterval(checkalive, 1000);
// process announcements over the relay network, and publish our own keep-alives to keep the channel alive
await ipfs.pubsub.subscribe("announce-circuit", processAnnounce);
setInterval(function(){ipfs.pubsub.publish("announce-circuit", "peer-alive");}, 15000);
// block for translating an enter keypress while in the chat input as a message submission
document.getElementById("chatInput").addEventListener("keydown", async function(e) {
if (!e) { var e = window.event; }
// Enter is pressed
if (e.keyCode == 13) {
e.preventDefault();
await sendMsg();
}
}, false);
}
</script>
</head>
<body onload="onload();">
<span id="status-ball" style="color:red;position:fixed;">⬤</span>
<div class="jumbotron text-center" style="margin-bottom:0;height:13vh;min-height:8em;flex: 0 1 auto;">
<div style="margin-top:-2em;">
<h1>ipfs chat (<span style="color:lime;" id="peerCount">0</span>)</h1>
<p>A simple p2p chat demo, showing off some of the abilities of <a href="https://github.com/ipfs/js-ipfs#readme">js-ipfs</a> and <a href="https://docs.libp2p.io/concepts/publish-subscribe/">libp2p's pubsub</a>.</p>
</div>
</div>
<div class="container-fluid">
<div class="row container-fluid">
<!--<div class="col-sm-2">
<h2>Room list</h2>
<p style="height:37em;overflow-y: scroll;">Not implemented</p>
<hr class="d-sm-none">
</div>-->
<div class="col-lg-12 container-fluid" id="main">
<p style="overflow-y: scroll; margin: 0;margin-bottom:4.2em; width: 99.7vw;flex: 1 1 0;" id="chat">
</p>
</div>
<!--<div class="col-sm-2">
<h2>User list</h2>
<p style="height:37em;overflow-y: scroll;">
Not implemented.
</p>
<hr class="d-sm-none">
</div>-->
</div>
<div class="row" style="position:fixed;bottom:0.5em;width:100%;height:4em;margin:auto;">
<div class="col-sm-3 input-field">
<form><input type="text" class="form-control" id="displayInput" placeholder="Enter displayname"></form>
</div>
<div class="col-lg-9 input-field">
<form action="#"><input type="text" class="form-control" id="chatInput" placeholder="Chat here"></form>
</div>
</div>
</div>
</body>
</html>