Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use socket API #70

Merged
merged 12 commits into from
Mar 15, 2018
Merged

Use socket API #70

merged 12 commits into from
Mar 15, 2018

Conversation

lukechilds
Copy link
Member

This uses the socket API by default for all commands.

This isn't a particularly neat solution but it was all James could implement last minute.

First we have to ping getendpoint which will start the ws server. Then we connect to it and listed to all messages, currently it sends us all P2P traffic.

If we send an HTTP command with a non zero queueid then it will be queued internally by mm. mm will then return the command response along with it's queueid over the ws when it's been processed.

The way this is implemented is completely transparent, functionality should be exactly the same in ws mode or HTTP mode, all the matching up of HTTP responses or socket responses is abstracted away.

Just opening this PR for your feedback on this implementation. It's not ready to be merged yet, there are still bugs in mm that are stopping it from working.

@lukechilds
Copy link
Member Author

Current blocking mm issue: jl777/SuperNET#659

@lukechilds
Copy link
Member Author

These issues are also worth a read to get an idea of the socket functionality:

jl777/SuperNET#643
jl777/SuperNET#645

@sindresorhus sindresorhus changed the title WIP Use socket API [WIP] Use socket API Mar 13, 2018

_parseData(data) {
return new Promise(resolve => {
const reader = new FileReader();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a short comment here on why FileReader is being used? It's not clear to me.

Copy link
Member Author

@lukechilds lukechilds Mar 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WebSocket data we get is binary, I need to use FileReader to get a string from the blob and then parse it as JSON. I'll add some comments to explain this 👍.

Could alternatively use:

fetch(URL.createObjectURL(blob)).then(res => res.json());

which makes it a (tiny) bit more clear what's going on but adds a huge amount of noise to the network tab in dev tools.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I prefer the FileReader approach.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just found this module: https://www.npmjs.com/package/read-blob

Do you think a comment is still necessary if I'm using that descriptive function name?

(I'll still is that module either way)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's clear enough without the comment if you use that module.

Copy link
Member Author

@lukechilds lukechilds Mar 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's much more readable now: 5f27a66

const json = await readBlob.text(event.data);
const data = JSON.parse(json);

this._ws = new WebSocket(endpoint, ['pair.sp.nanomsg.org']);
this.connected = new Promise(resolve => this._ws.addEventListener('open', resolve));

const ee = new Emittery();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just do this as a class field:

_ee = new Emittery();

You can only do this one though as it seems class fields cannot make use of previous class fields, at least with our Babel transform.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally I would agree with you but I think in this case it seems more confusing doing this a class field as it's referenced inside the constructor. It also doesn't reduce code at all.

constructor(endpoint) {
  const ee = new Emittery();
  this._ee = ee;
  this.on = ee.on.bind(ee);
  this.off = ee.off.bind(ee);
  this.once = ee.once.bind(ee);
}

becomes

_ee = new Emittery();

constructor(endpoint) {
  const ee = this._ee;
  this.on = ee.on.bind(ee);
  this.off = ee.off.bind(ee);
  this.once = ee.once.bind(ee);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking from a Swift perspective again, where using this is optional.


async _handleMessage(event) {
const data = await this._parseData(event.data);
const queueid = Number(data.result.queueid);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you coercing this to a number? I thought the returned queueid is already a number.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't trust the responses to always be a number. However, we're only doing a size comparison so it shouldn't matter if it's a string anyway, I'll remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the case, I would rather throw a TypeError if it's not a number, so it doesn't silently pass.

this.off = ee.off.bind(ee);
this.once = ee.once.bind(ee);

this._ws.addEventListener('message', this._handleMessage.bind(this));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you make _handleMessage a class field, you don't have to bind it.

if (!(endpoint && seedPhrase)) {
throw new Error('The `endpoint` and `seedPhrase` options are required');
}

this.endpoint = endpoint;
this.token = sha256().update(seedPhrase).digest('hex');
this.socket = false;
this.currentQueueid = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know the returned queueid is lowercase, but let's use camelCase ourselves => currentQueueId

@lukechilds
Copy link
Member Author

@sindresorhus Are you able to re-review this?

I've implemented the changes you requested and resolved all merge requests.

I've also worked through the mm issues that were blocking this with James and they are now resolved.

@sindresorhus sindresorhus changed the title [WIP] Use socket API Use socket API Mar 15, 2018
@sindresorhus sindresorhus merged commit d1cd3a4 into master Mar 15, 2018
@sindresorhus
Copy link
Contributor

Looks good :)

@sindresorhus sindresorhus deleted the use-socket-api branch March 15, 2018 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants