UNPKG

11.6 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11import { BigNumber } from "@ethersproject/bignumber";
12import { defineReadOnly } from "@ethersproject/properties";
13import { JsonRpcProvider } from "./json-rpc-provider";
14import { WebSocket } from "./ws";
15import { Logger } from "@ethersproject/logger";
16import { version } from "./_version";
17const logger = new Logger(version);
18/**
19 * Notes:
20 *
21 * This provider differs a bit from the polling providers. One main
22 * difference is how it handles consistency. The polling providers
23 * will stall responses to ensure a consistent state, while this
24 * WebSocket provider assumes the connected backend will manage this.
25 *
26 * For example, if a polling provider emits an event which indicates
27 * the event occurred in blockhash XXX, a call to fetch that block by
28 * its hash XXX, if not present will retry until it is present. This
29 * can occur when querying a pool of nodes that are mildly out of sync
30 * with each other.
31 */
32let NextId = 1;
33// For more info about the Real-time Event API see:
34// https://geth.ethereum.org/docs/rpc/pubsub
35export class WebSocketProvider extends JsonRpcProvider {
36 constructor(url, network) {
37 // This will be added in the future; please open an issue to expedite
38 if (network === "any") {
39 logger.throwError("WebSocketProvider does not support 'any' network yet", Logger.errors.UNSUPPORTED_OPERATION, {
40 operation: "network:any"
41 });
42 }
43 if (typeof (url) === "string") {
44 super(url, network);
45 }
46 else {
47 super("_websocket", network);
48 }
49 this._pollingInterval = -1;
50 this._wsReady = false;
51 if (typeof (url) === "string") {
52 defineReadOnly(this, "_websocket", new WebSocket(this.connection.url));
53 }
54 else {
55 defineReadOnly(this, "_websocket", url);
56 }
57 defineReadOnly(this, "_requests", {});
58 defineReadOnly(this, "_subs", {});
59 defineReadOnly(this, "_subIds", {});
60 defineReadOnly(this, "_detectNetwork", super.detectNetwork());
61 // Stall sending requests until the socket is open...
62 this.websocket.onopen = () => {
63 this._wsReady = true;
64 Object.keys(this._requests).forEach((id) => {
65 this.websocket.send(this._requests[id].payload);
66 });
67 };
68 this.websocket.onmessage = (messageEvent) => {
69 const data = messageEvent.data;
70 const result = JSON.parse(data);
71 if (result.id != null) {
72 const id = String(result.id);
73 const request = this._requests[id];
74 delete this._requests[id];
75 if (result.result !== undefined) {
76 request.callback(null, result.result);
77 this.emit("debug", {
78 action: "response",
79 request: JSON.parse(request.payload),
80 response: result.result,
81 provider: this
82 });
83 }
84 else {
85 let error = null;
86 if (result.error) {
87 error = new Error(result.error.message || "unknown error");
88 defineReadOnly(error, "code", result.error.code || null);
89 defineReadOnly(error, "response", data);
90 }
91 else {
92 error = new Error("unknown error");
93 }
94 request.callback(error, undefined);
95 this.emit("debug", {
96 action: "response",
97 error: error,
98 request: JSON.parse(request.payload),
99 provider: this
100 });
101 }
102 }
103 else if (result.method === "eth_subscription") {
104 // Subscription...
105 const sub = this._subs[result.params.subscription];
106 if (sub) {
107 //this.emit.apply(this, );
108 sub.processFunc(result.params.result);
109 }
110 }
111 else {
112 console.warn("this should not happen");
113 }
114 };
115 // This Provider does not actually poll, but we want to trigger
116 // poll events for things that depend on them (like stalling for
117 // block and transaction lookups)
118 const fauxPoll = setInterval(() => {
119 this.emit("poll");
120 }, 1000);
121 if (fauxPoll.unref) {
122 fauxPoll.unref();
123 }
124 }
125 // Cannot narrow the type of _websocket, as that is not backwards compatible
126 // so we add a getter and let the WebSocket be a public API.
127 get websocket() { return this._websocket; }
128 detectNetwork() {
129 return this._detectNetwork;
130 }
131 get pollingInterval() {
132 return 0;
133 }
134 resetEventsBlock(blockNumber) {
135 logger.throwError("cannot reset events block on WebSocketProvider", Logger.errors.UNSUPPORTED_OPERATION, {
136 operation: "resetEventBlock"
137 });
138 }
139 set pollingInterval(value) {
140 logger.throwError("cannot set polling interval on WebSocketProvider", Logger.errors.UNSUPPORTED_OPERATION, {
141 operation: "setPollingInterval"
142 });
143 }
144 poll() {
145 return __awaiter(this, void 0, void 0, function* () {
146 return null;
147 });
148 }
149 set polling(value) {
150 if (!value) {
151 return;
152 }
153 logger.throwError("cannot set polling on WebSocketProvider", Logger.errors.UNSUPPORTED_OPERATION, {
154 operation: "setPolling"
155 });
156 }
157 send(method, params) {
158 const rid = NextId++;
159 return new Promise((resolve, reject) => {
160 function callback(error, result) {
161 if (error) {
162 return reject(error);
163 }
164 return resolve(result);
165 }
166 const payload = JSON.stringify({
167 method: method,
168 params: params,
169 id: rid,
170 jsonrpc: "2.0"
171 });
172 this.emit("debug", {
173 action: "request",
174 request: JSON.parse(payload),
175 provider: this
176 });
177 this._requests[String(rid)] = { callback, payload };
178 if (this._wsReady) {
179 this.websocket.send(payload);
180 }
181 });
182 }
183 static defaultUrl() {
184 return "ws:/\/localhost:8546";
185 }
186 _subscribe(tag, param, processFunc) {
187 return __awaiter(this, void 0, void 0, function* () {
188 let subIdPromise = this._subIds[tag];
189 if (subIdPromise == null) {
190 subIdPromise = Promise.all(param).then((param) => {
191 return this.send("eth_subscribe", param);
192 });
193 this._subIds[tag] = subIdPromise;
194 }
195 const subId = yield subIdPromise;
196 this._subs[subId] = { tag, processFunc };
197 });
198 }
199 _startEvent(event) {
200 switch (event.type) {
201 case "block":
202 this._subscribe("block", ["newHeads"], (result) => {
203 const blockNumber = BigNumber.from(result.number).toNumber();
204 this._emitted.block = blockNumber;
205 this.emit("block", blockNumber);
206 });
207 break;
208 case "pending":
209 this._subscribe("pending", ["newPendingTransactions"], (result) => {
210 this.emit("pending", result);
211 });
212 break;
213 case "filter":
214 this._subscribe(event.tag, ["logs", this._getFilter(event.filter)], (result) => {
215 if (result.removed == null) {
216 result.removed = false;
217 }
218 this.emit(event.filter, this.formatter.filterLog(result));
219 });
220 break;
221 case "tx": {
222 const emitReceipt = (event) => {
223 const hash = event.hash;
224 this.getTransactionReceipt(hash).then((receipt) => {
225 if (!receipt) {
226 return;
227 }
228 this.emit(hash, receipt);
229 });
230 };
231 // In case it is already mined
232 emitReceipt(event);
233 // To keep things simple, we start up a single newHeads subscription
234 // to keep an eye out for transactions we are watching for.
235 // Starting a subscription for an event (i.e. "tx") that is already
236 // running is (basically) a nop.
237 this._subscribe("tx", ["newHeads"], (result) => {
238 this._events.filter((e) => (e.type === "tx")).forEach(emitReceipt);
239 });
240 break;
241 }
242 // Nothing is needed
243 case "debug":
244 case "poll":
245 case "willPoll":
246 case "didPoll":
247 case "error":
248 break;
249 default:
250 console.log("unhandled:", event);
251 break;
252 }
253 }
254 _stopEvent(event) {
255 let tag = event.tag;
256 if (event.type === "tx") {
257 // There are remaining transaction event listeners
258 if (this._events.filter((e) => (e.type === "tx")).length) {
259 return;
260 }
261 tag = "tx";
262 }
263 else if (this.listenerCount(event.event)) {
264 // There are remaining event listeners
265 return;
266 }
267 const subId = this._subIds[tag];
268 if (!subId) {
269 return;
270 }
271 delete this._subIds[tag];
272 subId.then((subId) => {
273 if (!this._subs[subId]) {
274 return;
275 }
276 delete this._subs[subId];
277 this.send("eth_unsubscribe", [subId]);
278 });
279 }
280 destroy() {
281 return __awaiter(this, void 0, void 0, function* () {
282 // Wait until we have connected before trying to disconnect
283 if (this.websocket.readyState === WebSocket.CONNECTING) {
284 yield (new Promise((resolve) => {
285 this.websocket.onopen = function () {
286 resolve(true);
287 };
288 this.websocket.onerror = function () {
289 resolve(false);
290 };
291 }));
292 }
293 // Hangup
294 // See: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes
295 this.websocket.close(1000);
296 });
297 }
298}
299//# sourceMappingURL=websocket-provider.js.map
\No newline at end of file