|
|
|
@ -1,17 +1,28 @@ |
|
|
|
|
// TODO: implement Websocket Provider
|
|
|
|
|
import { w3cwebsocket as W3CWebsocket } from 'websocket'; |
|
|
|
|
import { BaseSocket } from './baseSocket'; |
|
|
|
|
import { isWs } from '@harmony/utils'; |
|
|
|
|
import { |
|
|
|
|
BaseSocket, |
|
|
|
|
SocketConnection, |
|
|
|
|
SocketState, |
|
|
|
|
// EmittType,
|
|
|
|
|
} from './baseSocket'; |
|
|
|
|
import { isWs, isObject, isArray } from '@harmony/utils'; |
|
|
|
|
import { JsonRpc } from '../rpcMethod/rpcbuilder'; |
|
|
|
|
import { composeMiddleware } from '../rpcMethod/net'; |
|
|
|
|
import { RPCRequestPayload } from '../types'; |
|
|
|
|
|
|
|
|
|
class WSProvider extends BaseSocket { |
|
|
|
|
get connected() { |
|
|
|
|
return this.connection.readyState === this.connection.OPEN; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
url: string; |
|
|
|
|
subscriptions: any = {}; |
|
|
|
|
options: any = {}; |
|
|
|
|
subscriptions: any; |
|
|
|
|
options: any; |
|
|
|
|
connection: W3CWebsocket | WebSocket; |
|
|
|
|
jsonRpc: JsonRpc; |
|
|
|
|
on: any; |
|
|
|
|
|
|
|
|
|
// ws: w3cwebsocket;
|
|
|
|
|
constructor(url: string, options: any = {}) { |
|
|
|
|
super(url); |
|
|
|
@ -22,6 +33,14 @@ class WSProvider extends BaseSocket { |
|
|
|
|
this.options = options; |
|
|
|
|
this.connection = this.createWebsocketProvider(this.url, this.options); |
|
|
|
|
this.jsonRpc = new JsonRpc(); |
|
|
|
|
this.subscriptions = {}; |
|
|
|
|
this.registerEventListeners(); |
|
|
|
|
this.on = this.emitter.on.bind(this); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
onData(handler: any) { |
|
|
|
|
this.emitter.on('data', handler); |
|
|
|
|
return this; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
createWebsocketProvider(url: string, options: any = {}) { |
|
|
|
@ -51,10 +70,6 @@ class WSProvider extends BaseSocket { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
get connected() { |
|
|
|
|
return this.connection.readyState === this.connection.OPEN; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
isConnecting() { |
|
|
|
|
return this.connection.readyState === this.connection.CONNECTING; |
|
|
|
|
} |
|
|
|
@ -63,23 +78,181 @@ class WSProvider extends BaseSocket { |
|
|
|
|
const [tReq, tRes] = this.getMiddleware(payload.method); |
|
|
|
|
const reqMiddleware = composeMiddleware(...tReq); |
|
|
|
|
const resMiddleware = composeMiddleware(...tRes); |
|
|
|
|
try { |
|
|
|
|
this.connection.send(reqMiddleware(JSON.stringify(payload))); |
|
|
|
|
} catch (error) { |
|
|
|
|
throw error; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
this.connection.send(reqMiddleware(JSON.stringify(payload))); |
|
|
|
|
this.connection.onmessage = (msg: MessageEvent) => { |
|
|
|
|
if (msg && msg.data) { |
|
|
|
|
let result; |
|
|
|
|
try { |
|
|
|
|
result = JSON.parse(msg.data); |
|
|
|
|
resolve(resMiddleware(result)); |
|
|
|
|
} catch (error) { |
|
|
|
|
reject(error); |
|
|
|
|
} |
|
|
|
|
this.emitter.on(`${payload.id}`, (data) => { |
|
|
|
|
resolve(resMiddleware(data)); |
|
|
|
|
this.removeEventListener(`${payload.id}`); |
|
|
|
|
}); |
|
|
|
|
this.emitter.on('connect', () => { |
|
|
|
|
this.send(payload) |
|
|
|
|
.then(resolve) |
|
|
|
|
.catch(reject); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async subscribe(payload: RPCRequestPayload<any[]>) { |
|
|
|
|
const response = await this.send(payload); |
|
|
|
|
const responseValidateResult = this.validate(response); |
|
|
|
|
if (responseValidateResult instanceof Error) { |
|
|
|
|
throw responseValidateResult; |
|
|
|
|
} |
|
|
|
|
this.subscriptions[response.result] = { |
|
|
|
|
id: response.result, |
|
|
|
|
subscribeMethod: payload.method, |
|
|
|
|
parameters: payload.params, |
|
|
|
|
payload, |
|
|
|
|
}; |
|
|
|
|
return response.result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async unsubscribe(payload: RPCRequestPayload<any[]>) { |
|
|
|
|
const subscriptionId = payload.params[0]; |
|
|
|
|
if (this.hasSubscription(subscriptionId)) { |
|
|
|
|
return this.send(payload).then((response) => { |
|
|
|
|
if (response) { |
|
|
|
|
this.removeEventListener(this.getSubscriptionEvent(subscriptionId)); |
|
|
|
|
|
|
|
|
|
delete this.subscriptions[subscriptionId]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return response; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return Promise.reject( |
|
|
|
|
new Error( |
|
|
|
|
`Provider error: Subscription with ID ${subscriptionId} does not exist.`, |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async clearSubscriptions(unsubscribeMethod: string) { |
|
|
|
|
const unsubscribePromises: Array<Promise<any>> = []; |
|
|
|
|
|
|
|
|
|
Object.keys(this.subscriptions).forEach((key) => { |
|
|
|
|
this.removeEventListener(key); |
|
|
|
|
unsubscribePromises.push( |
|
|
|
|
this.unsubscribe( |
|
|
|
|
this.jsonRpc.toPayload(unsubscribeMethod, this.subscriptions[key].id), |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const results = await Promise.all(unsubscribePromises); |
|
|
|
|
if (results.includes(false)) { |
|
|
|
|
throw new Error( |
|
|
|
|
`Could not unsubscribe all subscriptions: ${JSON.stringify(results)}`, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
registerEventListeners() { |
|
|
|
|
this.connection.onmessage = this.onMessage.bind(this); |
|
|
|
|
this.connection.onopen = this.onReady.bind(this); |
|
|
|
|
this.connection.onopen = this.onConnect.bind(this); |
|
|
|
|
this.connection.onclose = this.onClose.bind(this); |
|
|
|
|
this.connection.onerror = this.onError.bind(this); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
onMessage(msg: MessageEvent) { |
|
|
|
|
if (msg && msg.data) { |
|
|
|
|
let result; |
|
|
|
|
let event; |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
result = isObject(msg.data) ? msg.data : JSON.parse(msg.data); |
|
|
|
|
|
|
|
|
|
if (isArray(result)) { |
|
|
|
|
event = result[0].id; |
|
|
|
|
} |
|
|
|
|
if (typeof result.id === 'undefined') { |
|
|
|
|
event = |
|
|
|
|
this.getSubscriptionEvent(result.params.subscription) || |
|
|
|
|
result.params.subscription; |
|
|
|
|
// result = result.params;
|
|
|
|
|
} else { |
|
|
|
|
reject('provider error'); |
|
|
|
|
event = result.id; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
} catch (error) { |
|
|
|
|
throw error; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.emitter.emit(SocketState.SOCKET_MESSAGE, result); |
|
|
|
|
this.emitter.emit(`${event}`, result); |
|
|
|
|
} else { |
|
|
|
|
throw new Error('provider error'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
async onConnect() { |
|
|
|
|
if (!this.subscriptions) { |
|
|
|
|
this.subscriptions = {}; |
|
|
|
|
} |
|
|
|
|
const subscriptionKeys = Object.keys(this.subscriptions); |
|
|
|
|
|
|
|
|
|
if (subscriptionKeys.length > 0) { |
|
|
|
|
for (const key of subscriptionKeys) { |
|
|
|
|
const subscriptionId: any = await this.subscribe( |
|
|
|
|
this.subscriptions[key].payload, |
|
|
|
|
); |
|
|
|
|
delete this.subscriptions[subscriptionId]; |
|
|
|
|
this.subscriptions[key].id = subscriptionId; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.emitter.emit(SocketState.SOCKET_CONNECT); |
|
|
|
|
this.emitter.emit(SocketConnection.CONNECT); |
|
|
|
|
} |
|
|
|
|
getSubscriptionEvent(subscriptionId: any) { |
|
|
|
|
if (this.subscriptions[subscriptionId]) { |
|
|
|
|
return subscriptionId; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let event; |
|
|
|
|
Object.keys(this.subscriptions).forEach((key) => { |
|
|
|
|
if (this.subscriptions[key].id === subscriptionId) { |
|
|
|
|
event = key; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
return event; |
|
|
|
|
} |
|
|
|
|
hasSubscription(subscriptionId: string) { |
|
|
|
|
return typeof this.getSubscriptionEvent(subscriptionId) !== 'undefined'; |
|
|
|
|
} |
|
|
|
|
validate(response: any, payload?: any) { |
|
|
|
|
if (isObject(response)) { |
|
|
|
|
if (response.error) { |
|
|
|
|
if (response.error instanceof Error) { |
|
|
|
|
return new Error(`Node error: ${response.error.message}`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return new Error(`Node error: ${JSON.stringify(response.error)}`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (payload && response.id !== payload.id) { |
|
|
|
|
return new Error( |
|
|
|
|
`Validation error: Invalid JSON-RPC response ID (request: ${ |
|
|
|
|
payload.id |
|
|
|
|
} / response: ${response.id})`,
|
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (response.result === undefined) { |
|
|
|
|
return new Error('Validation error: Undefined JSON-RPC result'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return new Error('Validation error: Response should be of type Object'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|