Revamped NodeJS library examples. (#151)

This commit is contained in:
Ravin Perera
2020-11-16 17:08:47 +05:30
committed by GitHub
parent 645f0023a0
commit 0098c3ddab
10 changed files with 487 additions and 283 deletions

View File

@@ -1,35 +1,21 @@
const fs = require('fs');
const readline = require('readline');
const sodium = require('libsodium-wrappers');
const { exit } = require('process');
const { HotPocketClient, HotPocketProtocols, HotPocketEvents } = require('./hp-client-lib');
const bson = require('bson');
var path = require("path");
const { HotPocketClient, HotPocketKeyGenerator, HotPocketEvents } = require('./hp-client-lib');
async function main() {
await sodium.ready;
const keys = await HotPocketKeyGenerator.generate();
let keys = {};
const key_file = '.hp_client_keys';
if (!fs.existsSync(key_file)) {
keys = sodium.crypto_sign_keypair();
keys.privateKey = sodium.to_hex(keys.privateKey)
keys.publicKey = sodium.to_hex(keys.publicKey)
fs.writeFileSync(key_file, JSON.stringify(keys))
} else {
keys = JSON.parse(fs.readFileSync(key_file))
keys.privateKey = Uint8Array.from(Buffer.from(keys.privateKey, 'hex'))
keys.publicKey = Uint8Array.from(Buffer.from(keys.publicKey, 'hex'))
}
const pkhex = 'ed' + Buffer.from(keys.publicKey).toString('hex');
const pkhex = Buffer.from(keys.publicKey).toString('hex');
console.log('My public key is: ' + pkhex);
let server = 'wss://localhost:8080'
if (process.argv.length == 3) server = 'wss://localhost:' + process.argv[2]
if (process.argv.length == 4) server = 'wss://' + process.argv[2] + ':' + process.argv[3]
const hpc = new HotPocketClient(server, HotPocketProtocols.BSON, keys);
const hpc = new HotPocketClient(server, keys);
// Establish HotPocket connection.
if (!await hpc.connect()) {

View File

@@ -1,8 +1,11 @@
const ws_api = require('ws');
const WebSocket = require('isomorphic-ws');
const sodium = require('libsodium-wrappers');
const EventEmitter = require('events');
const bson = require('bson');
// Whether we are in NodeJS or Browser.
const isNodeJS = (typeof window === 'undefined');
const protocols = {
JSON: "json",
BSON: "bson"
@@ -16,17 +19,35 @@ const events = {
}
Object.freeze(events);
function HotPocketClient(server, protocol, keys) {
const HotPocketKeyGenerator = {
generate: async function (privateKeyHex = null) {
await sodium.ready;
if (protocol != protocols.JSON && protocol != protocols.BSON)
throw new Error("Protocol: 'json' or 'bson' expected.");
if (!privateKeyHex) {
const keys = sodium.crypto_sign_keypair();
return {
privateKey: keys.privateKey,
publicKey: keys.publicKey
}
}
else {
const binPrivateKey = Buffer.from(privateKeyHex, "hex");
return {
privateKey: Uint8Array.from(binPrivateKey),
publicKey: Uint8Array.from(binPrivateKey.slice(32))
}
}
},
}
function HotPocketClient(server, keys, protocol = protocols.BSON) {
let ws = null;
const msgHelper = new MessageHelper(keys, protocol);
const emitter = new EventEmitter();
let handshakeResolver = null;
let statResponseResolver = null;
let statResponseResolvers = [];
let contractInputResolvers = {};
this.connect = function () {
@@ -34,31 +55,47 @@ function HotPocketClient(server, protocol, keys) {
handshakeResolver = resolve;
ws = new ws_api(server, {
rejectUnauthorized: false
})
if (isNodeJS) {
ws = new WebSocket(server, {
rejectUnauthorized: false
})
}
else {
ws = new WebSocket(server);
}
ws.on('close', () => {
ws.onclose = () => {
// If there are any ongoing resolvers resolve them with error output.
handshakeResolver && handshakeResolver(false);
handshakeResolver = null;
statResponseResolver && statResponseResolver(null);
statResponseResolver = null;
statResponseResolvers.forEach(resolver => resolver(null));
statResponseResolvers = [];
Object.values(contractInputResolvers).forEach(resolver => resolver(null));
contractInputResolvers = {};
emitter.emit(events.disconnect);
});
};
ws.onmessage = async (rcvd) => {
if (isNodeJS) {
msg = rcvd.data;
}
else {
msg = (handshakeResolver || protocol == protocols.JSON) ?
await rcvd.data.text() :
Buffer.from(await rcvd.data.arrayBuffer());
}
ws.on('message', (msg) => {
try {
// Use JSON if we are still in handshake phase.
m = handshakeResolver ? JSON.parse(msg) : msgHelper.deserializeMessage(msg);
} catch (e) {
console.log(e);
console.log("Exception deserializing: ");
console.log(msg)
return;
@@ -68,7 +105,7 @@ function HotPocketClient(server, protocol, keys) {
// sign the challenge and send back the response
const response = msgHelper.createHandshakeResponse(m.challenge);
ws.send(JSON.stringify(response));
setTimeout(() => {
// If we are still connected, report handshaking as successful.
// (If websocket disconnects, handshakeResolver will be null)
@@ -96,16 +133,18 @@ function HotPocketClient(server, protocol, keys) {
emitter.emit(events.contractOutput, decoded);
}
else if (m.type == "stat_response") {
statResponseResolver && statResponseResolver({
lcl: m.lcl,
lclSeqNo: m.lcl_seqno
});
statResponseResolver = null;
statResponseResolvers.forEach(resolver => {
resolver({
lcl: m.lcl,
lclSeqNo: m.lcl_seqno
});
})
statResponseResolvers = [];
}
else {
console.log("Received unrecognized message: type:" + m.type);
}
});
}
});
};
@@ -116,7 +155,7 @@ function HotPocketClient(server, protocol, keys) {
this.close = function () {
return new Promise(resolve => {
try {
ws.removeAllListeners("close");
ws.onclose = resolve;
ws.on("close", resolve);
ws.close();
} catch (error) {
@@ -126,12 +165,16 @@ function HotPocketClient(server, protocol, keys) {
}
this.getStatus = function () {
const msg = msgHelper.createStatusRequest();
const p = new Promise(resolve => {
statResponseResolver = resolve;
statResponseResolvers.push(resolve);
});
ws.send(msgHelper.serializeObject(msg));
// If this is the only awaiting stat request, then send an actual stat request.
// Otherwise simply wait for the previously sent request.
if (statResponseResolvers.length == 1) {
const msg = msgHelper.createStatusRequest();
ws.send(msgHelper.serializeObject(msg));
}
return p;
}
@@ -146,7 +189,7 @@ function HotPocketClient(server, protocol, keys) {
// Acquire the current lcl and add the specified offset.
const stat = await this.getStatus();
if (!stat)
return new Promise(resolve => resolve(null));
return new Promise(resolve => resolve("ledger_status_error"));
const maxLclSeqNo = stat.lclSeqNo + maxLclOffset;
const msg = msgHelper.createContractInput(input, nonce, maxLclSeqNo);
@@ -185,7 +228,7 @@ function MessageHelper(keys, protocol) {
}
this.createHandshakeResponse = function (challenge) {
// For handshake response encoding we Hot Pocket always use json.
// For handshake response encoding Hot Pocket always uses json.
// Handshake response will specify the protocol to use for subsequent messages.
const sigBytes = sodium.crypto_sign_detached(challenge, keys.privateKey);
return {
@@ -236,8 +279,17 @@ function MessageHelper(keys, protocol) {
}
}
module.exports = {
HotPocketClient,
HotPocketProtocols: protocols,
HotPocketEvents: events
};
if (isNodeJS) {
module.exports = {
HotPocketKeyGenerator,
HotPocketClient,
HotPocketEvents: events
};
}
else {
window.HotPocket = {
KeyGenerator: HotPocketKeyGenerator,
Client: HotPocketClient,
Events: events
}
}

View File

@@ -30,11 +30,24 @@
"ieee754": "^1.1.4"
}
},
"bufferutil": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz",
"integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==",
"requires": {
"node-gyp-build": "~3.7.0"
}
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"isomorphic-ws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="
},
"libsodium": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.6.tgz",
@@ -53,6 +66,19 @@
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"node-gyp-build": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz",
"integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w=="
},
"utf-8-validate": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.2.tgz",
"integrity": "sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==",
"requires": {
"node-gyp-build": "~3.7.0"
}
},
"ws": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz",

View File

@@ -2,6 +2,9 @@
"dependencies": {
"libsodium-wrappers": "0.7.6",
"ws": "7.1.2",
"bson": "4.0.4"
"isomorphic-ws": "4.0.1",
"bson": "4.0.4",
"utf-8-validate": "5.0.2",
"bufferutil": "4.0.1"
}
}

View File

@@ -1,33 +1,19 @@
const fs = require('fs');
const readline = require('readline');
const sodium = require('libsodium-wrappers');
const { exit } = require('process');
const { HotPocketClient, HotPocketProtocols, HotPocketEvents } = require('./hp-client-lib');
const { HotPocketClient, HotPocketKeyGenerator, HotPocketEvents } = require('./hp-client-lib');
async function main() {
await sodium.ready;
const keys = await HotPocketKeyGenerator.generate();
let keys = {};
const key_file = '.hp_client_keys';
if (!fs.existsSync(key_file)) {
keys = sodium.crypto_sign_keypair();
keys.privateKey = sodium.to_hex(keys.privateKey)
keys.publicKey = sodium.to_hex(keys.publicKey)
fs.writeFileSync(key_file, JSON.stringify(keys))
} else {
keys = JSON.parse(fs.readFileSync(key_file))
keys.privateKey = Uint8Array.from(Buffer.from(keys.privateKey, 'hex'))
keys.publicKey = Uint8Array.from(Buffer.from(keys.publicKey, 'hex'))
}
const pkhex = 'ed' + Buffer.from(keys.publicKey).toString('hex');
const pkhex = Buffer.from(keys.publicKey).toString('hex');
console.log('My public key is: ' + pkhex);
let server = 'wss://localhost:8080'
if (process.argv.length == 3) server = 'wss://localhost:' + process.argv[2]
if (process.argv.length == 4) server = 'wss://' + process.argv[2] + ':' + process.argv[3]
const hpc = new HotPocketClient(server, HotPocketProtocols.JSON, keys);
const hpc = new HotPocketClient(server, keys);
// Establish HotPocket connection.
if (!await hpc.connect()) {
@@ -68,13 +54,15 @@ async function main() {
const input_pump = () => {
rl.question('', (inp) => {
if (inp.startsWith("read "))
hpc.sendContractReadRequest(inp.substr(5))
else {
hpc.sendContractInput(inp).then(submissionStatus => {
if (submissionStatus && submissionStatus != "ok")
console.log("Input submission failed. reason: " + submissionStatus);
});
if (inp.length > 0) {
if (inp.startsWith("read "))
hpc.sendContractReadRequest(inp.substr(5))
else {
hpc.sendContractInput(inp).then(submissionStatus => {
if (submissionStatus && submissionStatus != "ok")
console.log("Input submission failed. reason: " + submissionStatus);
});
}
}
input_pump();