websocket-provider.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. "use strict";
  2. var __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. };
  11. import { BigNumber } from "@ethersproject/bignumber";
  12. import { defineReadOnly } from "@ethersproject/properties";
  13. import { JsonRpcProvider } from "./json-rpc-provider";
  14. import { WebSocket } from "./ws";
  15. import { Logger } from "@ethersproject/logger";
  16. import { version } from "./_version";
  17. const 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. */
  32. let NextId = 1;
  33. // For more info about the Real-time Event API see:
  34. // https://geth.ethereum.org/docs/rpc/pubsub
  35. export 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