Newer
Older
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
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
export default () => {
return {
dial: dial,
listen_on: (addr) => {
let err = new Error("Listening on WebSockets is not possible from within a browser");
err.name = "NotSupportedError";
throw err;
},
};
}
/// Turns a string multiaddress into a WebSockets string URL.
// TODO: support dns addresses as well
const multiaddr_to_ws = (addr) => {
let parsed = addr.match(/^\/(ip4|ip6|dns4|dns6)\/(.*?)\/tcp\/(.*?)\/(ws|wss|x-parity-ws\/(.*)|x-parity-wss\/(.*))$/);
let proto = 'wss';
if (parsed[4] == 'ws' || parsed[4] == 'x-parity-ws') {
proto = 'ws';
}
let url = decodeURIComponent(parsed[5] || parsed[6] || '');
if (parsed != null) {
if (parsed[1] == 'ip6') {
return proto + "://[" + parsed[2] + "]:" + parsed[3] + url;
} else {
return proto + "://" + parsed[2] + ":" + parsed[3] + url;
}
}
let err = new Error("Address not supported: " + addr);
err.name = "NotSupportedError";
throw err;
}
// Attempt to dial a multiaddress.
const dial = (addr) => {
let ws = new WebSocket(multiaddr_to_ws(addr));
let reader = read_queue();
return new Promise((resolve, reject) => {
// TODO: handle ws.onerror properly after dialing has happened
ws.onerror = (ev) => reject(ev);
ws.onmessage = (ev) => reader.inject_blob(ev.data);
ws.onclose = () => reader.inject_eof();
ws.onopen = () => resolve({
read: (function*() { while(ws.readyState == 1) { yield reader.next(); } })(),
write: (data) => {
if (ws.readyState == 1) {
ws.send(data);
return promise_when_ws_finished(ws);
} else {
return Promise.reject("WebSocket is closed");
}
},
shutdown: () => {},
close: () => ws.close()
});
});
}
// Takes a WebSocket object and returns a Promise that resolves when bufferedAmount is 0.
const promise_when_ws_finished = (ws) => {
if (ws.bufferedAmount == 0) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
setTimeout(function check() {
if (ws.bufferedAmount == 0) {
resolve();
} else {
setTimeout(check, 100);
}
}, 2);
})
}
// Creates a queue reading system.
const read_queue = () => {
// State of the queue.
let state = {
// Array of promises resolving to `ArrayBuffer`s, that haven't been transmitted back with
// `next` yet.
queue: new Array(),
// If `resolve` isn't null, it is a "resolve" function of a promise that has already been
// returned by `next`. It should be called with some data.
resolve: null,
};
return {
// Inserts a new Blob in the queue.
inject_blob: (blob) => {
if (state.resolve != null) {
var resolve = state.resolve;
state.resolve = null;
var reader = new FileReader();
reader.addEventListener("loadend", () => resolve(reader.result));
reader.readAsArrayBuffer(blob);
} else {
state.queue.push(new Promise((resolve, reject) => {
var reader = new FileReader();
reader.addEventListener("loadend", () => resolve(reader.result));
reader.readAsArrayBuffer(blob);
}));
}
},
// Inserts an EOF message in the queue.
inject_eof: () => {
if (state.resolve != null) {
var resolve = state.resolve;
state.resolve = null;
resolve(null);
} else {
state.queue.push(Promise.resolve(null));
}
},
// Returns a Promise that yields the next entry as an ArrayBuffer.
next: () => {
if (state.queue.length != 0) {
return state.queue.shift(0);
} else {
if (state.resolve !== null)
throw "Internal error: already have a pending promise";
return new Promise((resolve, reject) => {
state.resolve = resolve;
});
}
}
};
};