mirror of
https://github.com/EvernodeXRPL/hpcore.git
synced 2026-04-29 15:37:59 +00:00
Contract and client library improvements. (#184)
* Added tty check to contract libs. * Javascript browser-native client. * Removed hex encoding in user json outputs. * Updated file contract for new contract library.
This commit is contained in:
294
examples/browser_client/hp-browser-client-lib.js
Normal file
294
examples/browser_client/hp-browser-client-lib.js
Normal file
@@ -0,0 +1,294 @@
|
||||
window.HotPocket = (() => {
|
||||
|
||||
const protocols = {
|
||||
json: "json"
|
||||
}
|
||||
Object.freeze(protocols);
|
||||
|
||||
const events = {
|
||||
disconnect: "disconnect",
|
||||
contractOutput: "contractOutput",
|
||||
contractReadResponse: "contractReadResponse"
|
||||
}
|
||||
Object.freeze(events);
|
||||
|
||||
const fromHexString = hexString =>
|
||||
new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
||||
|
||||
const toHexString = bytes =>
|
||||
bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
|
||||
|
||||
const KeyGenerator = {
|
||||
generate: function (privateKeyHex = null) {
|
||||
|
||||
if (!privateKeyHex) {
|
||||
const keys = sodium.crypto_sign_keypair();
|
||||
return {
|
||||
privateKey: keys.privateKey,
|
||||
publicKey: keys.publicKey
|
||||
}
|
||||
}
|
||||
else {
|
||||
const binPrivateKey = fromHexString(privateKeyHex);
|
||||
return {
|
||||
privateKey: Uint8Array.from(binPrivateKey),
|
||||
publicKey: Uint8Array.from(binPrivateKey.slice(32))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function EventEmitter() {
|
||||
const registrations = {};
|
||||
|
||||
this.on = (eventName, listener) => {
|
||||
if (!registrations[eventName])
|
||||
registrations[eventName] = [];
|
||||
registrations[eventName].push(listener);
|
||||
}
|
||||
|
||||
this.emit = (eventName, value) => {
|
||||
if (registrations[eventName])
|
||||
registrations[eventName].forEach(listener => listener(value));
|
||||
}
|
||||
}
|
||||
|
||||
HotPocketClient = function HotPocketClient(contractId, server, keys) {
|
||||
|
||||
let ws = null;
|
||||
const protocol = protocols.json; // We only support json in browser.
|
||||
const msgHelper = new MessageHelper(keys, protocol);
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
let handshakeResolver = null;
|
||||
let statResponseResolvers = [];
|
||||
let contractInputResolvers = {};
|
||||
|
||||
this.connect = function () {
|
||||
return new Promise(resolve => {
|
||||
|
||||
handshakeResolver = resolve;
|
||||
|
||||
ws = new WebSocket(server);
|
||||
|
||||
ws.addEventListener("close", () => {
|
||||
// If there are any ongoing resolvers resolve them with error output.
|
||||
|
||||
handshakeResolver && handshakeResolver(false);
|
||||
handshakeResolver = null;
|
||||
|
||||
statResponseResolvers.forEach(resolver => resolver(null));
|
||||
statResponseResolvers = [];
|
||||
|
||||
Object.values(contractInputResolvers).forEach(resolver => resolver(null));
|
||||
contractInputResolvers = {};
|
||||
|
||||
emitter.emit(events.disconnect);
|
||||
});
|
||||
|
||||
ws.onmessage = async (rcvd) => {
|
||||
|
||||
msg = await rcvd.data.text();
|
||||
|
||||
try {
|
||||
m = msgHelper.deserializeMessage(msg);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log("Exception deserializing: ");
|
||||
console.log(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m.type == 'handshake_challenge') {
|
||||
// Check whether contract id is matching if specified.
|
||||
if (contractId && m.contract_id != contractId) {
|
||||
console.error("Contract id mismatch.")
|
||||
ws.close();
|
||||
}
|
||||
|
||||
// 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)
|
||||
handshakeResolver && handshakeResolver(true);
|
||||
handshakeResolver = null;
|
||||
}, 100);
|
||||
}
|
||||
else if (m.type == 'contract_read_response') {
|
||||
emitter.emit(events.contractReadResponse, msgHelper.deserializeOutput(m.content));
|
||||
}
|
||||
else if (m.type == 'contract_input_status') {
|
||||
const sigKey = (typeof m.input_sig === "string") ? m.input_sig : m.input_sig.toString("hex");
|
||||
const resolver = contractInputResolvers[sigKey];
|
||||
if (resolver) {
|
||||
if (m.status == "accepted")
|
||||
resolver("ok");
|
||||
else
|
||||
resolver(m.reason);
|
||||
delete contractInputResolvers[sigKey];
|
||||
}
|
||||
}
|
||||
else if (m.type == 'contract_output') {
|
||||
emitter.emit(events.contractOutput, msgHelper.deserializeOutput(m.content));
|
||||
}
|
||||
else if (m.type == "stat_response") {
|
||||
statResponseResolvers.forEach(resolver => {
|
||||
resolver({
|
||||
lcl: m.lcl,
|
||||
lclSeqNo: m.lcl_seqno
|
||||
});
|
||||
})
|
||||
statResponseResolvers = [];
|
||||
}
|
||||
else {
|
||||
console.log("Received unrecognized message: type:" + m.type);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.on = function (event, listener) {
|
||||
emitter.on(event, listener);
|
||||
}
|
||||
|
||||
this.close = function () {
|
||||
return new Promise(resolve => {
|
||||
try {
|
||||
ws.addEventListener("close", resolve);
|
||||
ws.close();
|
||||
} catch (error) {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.getStatus = function () {
|
||||
const p = new Promise(resolve => {
|
||||
statResponseResolvers.push(resolve);
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
this.sendContractInput = async function (input, nonce = null, maxLclOffset = null) {
|
||||
|
||||
if (!maxLclOffset)
|
||||
maxLclOffset = 10;
|
||||
|
||||
if (!nonce)
|
||||
nonce = (new Date()).getTime().toString();
|
||||
else
|
||||
nonce = nonce.toString();
|
||||
|
||||
// Acquire the current lcl and add the specified offset.
|
||||
const stat = await this.getStatus();
|
||||
if (!stat)
|
||||
return new Promise(resolve => resolve("ledger_status_error"));
|
||||
const maxLclSeqNo = stat.lclSeqNo + maxLclOffset;
|
||||
|
||||
const msg = msgHelper.createContractInput(input, nonce, maxLclSeqNo);
|
||||
const sigKey = (typeof msg.sig === "string") ? msg.sig : msg.sig.toString("hex");
|
||||
const p = new Promise(resolve => {
|
||||
contractInputResolvers[sigKey] = resolve;
|
||||
});
|
||||
|
||||
ws.send(msgHelper.serializeObject(msg));
|
||||
return p;
|
||||
}
|
||||
|
||||
this.sendContractReadRequest = function (request) {
|
||||
const msg = msgHelper.createReadRequest(request);
|
||||
ws.send(msgHelper.serializeObject(msg));
|
||||
}
|
||||
}
|
||||
|
||||
function MessageHelper(keys, protocol) {
|
||||
|
||||
this.binaryEncode = function (data) {
|
||||
return toHexString(data);
|
||||
}
|
||||
|
||||
this.serializeObject = function (obj) {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
this.deserializeMessage = function (m) {
|
||||
return JSON.parse(m);
|
||||
}
|
||||
|
||||
this.serializeInput = function (input) {
|
||||
return (typeof input === 'string' || input instanceof String) ? input : input.toString();
|
||||
}
|
||||
|
||||
this.deserializeOutput = function (content) {
|
||||
return content;
|
||||
}
|
||||
|
||||
this.createHandshakeResponse = function (challenge) {
|
||||
// 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 {
|
||||
type: "handshake_response",
|
||||
challenge: challenge,
|
||||
sig: toHexString(sigBytes),
|
||||
pubkey: "ed" + toHexString(keys.publicKey),
|
||||
protocol: protocol
|
||||
}
|
||||
}
|
||||
|
||||
this.createContractInput = function (input, nonce, maxLclSeqNo) {
|
||||
|
||||
if (input.length == 0)
|
||||
return null;
|
||||
|
||||
const inpContainer = {
|
||||
input: this.serializeInput(input),
|
||||
nonce: nonce,
|
||||
max_lcl_seqno: maxLclSeqNo
|
||||
}
|
||||
|
||||
const serlializedInpContainer = this.serializeObject(inpContainer);
|
||||
const sigBytes = sodium.crypto_sign_detached(serlializedInpContainer, keys.privateKey);
|
||||
|
||||
const signedInpContainer = {
|
||||
type: "contract_input",
|
||||
input_container: serlializedInpContainer,
|
||||
sig: this.binaryEncode(sigBytes)
|
||||
}
|
||||
|
||||
return signedInpContainer;
|
||||
}
|
||||
|
||||
this.createReadRequest = function (request) {
|
||||
|
||||
if (request.length == 0)
|
||||
return null;
|
||||
|
||||
return {
|
||||
type: "contract_read_request",
|
||||
content: this.serializeInput(request)
|
||||
}
|
||||
}
|
||||
|
||||
this.createStatusRequest = function () {
|
||||
return { type: 'stat' };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
KeyGenerator: KeyGenerator,
|
||||
Client: HotPocketClient,
|
||||
events: events,
|
||||
}
|
||||
})();
|
||||
16
examples/browser_client/test.html
Normal file
16
examples/browser_client/test.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>HotPocket test page</title>
|
||||
<script src="hp-browser-client-lib.js"></script>
|
||||
<script src="test.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/libsodium-wrappers/0.5.4/sodium.min.js"
|
||||
integrity="sha512-oRfU7aik4u7f0dPAKgOyA4+bb/YRGfAaD5RA4Z3Mb2ycPcGDs+k8qAnDNd7ouruoqlIHSuGVaTTlEs91Gvd37A=="
|
||||
crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
HotPocket browser client test page.
|
||||
</body>
|
||||
|
||||
</html>
|
||||
33
examples/browser_client/test.js
Normal file
33
examples/browser_client/test.js
Normal file
@@ -0,0 +1,33 @@
|
||||
window.sodium = {
|
||||
onload: async function () {
|
||||
const keys = HotPocket.KeyGenerator.generate(); // Can provide existing hex private key as parameter as well.
|
||||
const hpc = new HotPocket.Client(null, "wss://localhost:8081", keys);
|
||||
|
||||
if (!await hpc.connect()) {
|
||||
console.log('Connection failed.');
|
||||
return;
|
||||
}
|
||||
console.log('HotPocket Connected.');
|
||||
|
||||
// This will get fired if HP server disconnects unexpectedly.
|
||||
hpc.on(HotPocket.events.disconnect, () => {
|
||||
console.log('Server disconnected');
|
||||
})
|
||||
|
||||
// This will get fired when contract sends an output.
|
||||
hpc.on(HotPocket.events.contractOutput, (output) => {
|
||||
console.log("Contract output>> " + output);
|
||||
})
|
||||
|
||||
// This will get fired when contract sends a read response.
|
||||
hpc.on(HotPocket.events.contractReadResponse, (response) => {
|
||||
console.log("Contract read response>> " + response);
|
||||
})
|
||||
|
||||
hpc.sendContractReadRequest("Hello");
|
||||
hpc.sendContractInput("World!")
|
||||
|
||||
// When we need to close HP connection:
|
||||
// hpc.close();
|
||||
}
|
||||
};
|
||||
@@ -88,7 +88,12 @@ void process_user_message(const struct hp_user *user, const void *buf, const uin
|
||||
char tsbuf[st.st_size];
|
||||
if (read(fd, tsbuf, st.st_size) > 0)
|
||||
{
|
||||
hp_write_user_msg(user, tsbuf, st.st_size);
|
||||
for (int i = 0; i < st.st_size; i++)
|
||||
{
|
||||
if (tsbuf[i] == '\n' || tsbuf[i] == 0)
|
||||
tsbuf[i] = ' ';
|
||||
}
|
||||
hp_write_user_msg(user, tsbuf, st.st_size - 1);
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
|
||||
@@ -19,41 +19,51 @@
|
||||
#define HP_KEY_SIZE 64
|
||||
#define HP_HASH_SIZE 64
|
||||
|
||||
#define __HP_ASSIGN_STRING(dest, elem) \
|
||||
if (elem->value->type == json_type_string) \
|
||||
{ \
|
||||
const struct json_string_s *value = (struct json_string_s *)elem->value->payload; \
|
||||
memcpy(dest, value->string, sizeof(dest)); \
|
||||
#define __HP_ASSIGN_STRING(dest, elem) \
|
||||
{ \
|
||||
if (elem->value->type == json_type_string) \
|
||||
{ \
|
||||
const struct json_string_s *value = (struct json_string_s *)elem->value->payload; \
|
||||
memcpy(dest, value->string, sizeof(dest)); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define __HP_ASSIGN_UINT64(dest, elem) \
|
||||
if (elem->value->type == json_type_number) \
|
||||
{ \
|
||||
const struct json_number_s *value = (struct json_number_s *)elem->value->payload; \
|
||||
dest = strtoull(value->number, NULL, 0); \
|
||||
#define __HP_ASSIGN_UINT64(dest, elem) \
|
||||
{ \
|
||||
if (elem->value->type == json_type_number) \
|
||||
{ \
|
||||
const struct json_number_s *value = (struct json_number_s *)elem->value->payload; \
|
||||
dest = strtoull(value->number, NULL, 0); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define __HP_ASSIGN_INT(dest, elem) \
|
||||
if (elem->value->type == json_type_number) \
|
||||
{ \
|
||||
const struct json_number_s *value = (struct json_number_s *)elem->value->payload; \
|
||||
dest = atoi(value->number); \
|
||||
#define __HP_ASSIGN_INT(dest, elem) \
|
||||
{ \
|
||||
if (elem->value->type == json_type_number) \
|
||||
{ \
|
||||
const struct json_number_s *value = (struct json_number_s *)elem->value->payload; \
|
||||
dest = atoi(value->number); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define __HP_ASSIGN_BOOL(dest, elem) \
|
||||
if (elem->value->type == json_type_true) \
|
||||
dest = true; \
|
||||
else if (elem->value->type == json_type_false) \
|
||||
dest = false;
|
||||
#define __HP_ASSIGN_BOOL(dest, elem) \
|
||||
{ \
|
||||
if (elem->value->type == json_type_true) \
|
||||
dest = true; \
|
||||
else if (elem->value->type == json_type_false) \
|
||||
dest = false; \
|
||||
}
|
||||
|
||||
#define __HP_FROM_BE(buf, pos) \
|
||||
((uint8_t)buf[pos + 0] << 24 | (uint8_t)buf[pos + 1] << 16 | (uint8_t)buf[pos + 2] << 8 | (uint8_t)buf[pos + 3])
|
||||
|
||||
#define __HP_TO_BE(num, buf, pos) \
|
||||
buf[pos] = num >> 24; \
|
||||
buf[1 + pos] = num >> 16; \
|
||||
buf[2 + pos] = num >> 8; \
|
||||
buf[3 + pos] = num;
|
||||
{ \
|
||||
buf[pos] = num >> 24; \
|
||||
buf[1 + pos] = num >> 16; \
|
||||
buf[2 + pos] = num >> 8; \
|
||||
buf[3 + pos] = num; \
|
||||
}
|
||||
|
||||
struct hp_user_input
|
||||
{
|
||||
@@ -134,6 +144,13 @@ int hp_init_contract()
|
||||
if (__hpc.cctx)
|
||||
return -1; // Already initialized.
|
||||
|
||||
// Check whether we are running from terminal and produce warning.
|
||||
if (isatty(STDIN_FILENO) == 1)
|
||||
{
|
||||
fprintf(stderr, "Error: Hot Pocket smart contracts must be executed via Hot Pocket.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
const size_t len = read(STDIN_FILENO, buf, sizeof(buf));
|
||||
if (len == -1)
|
||||
|
||||
3
examples/nodejs_client/.gitignore
vendored
3
examples/nodejs_client/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
node_modules
|
||||
node_modules
|
||||
dist
|
||||
@@ -3,7 +3,7 @@ const readline = require('readline');
|
||||
const { exit } = require('process');
|
||||
const bson = require('bson');
|
||||
var path = require("path");
|
||||
const HotPocket = require('./hp-client-lib');
|
||||
const HotPocket = require('./hp-node-client-lib');
|
||||
|
||||
async function main() {
|
||||
|
||||
|
||||
@@ -103,8 +103,7 @@ function HotPocketClient(contractId, server, keys, protocol = protocols.json) {
|
||||
|
||||
if (m.type == 'handshake_challenge') {
|
||||
// Check whether contract id is matching if specified.
|
||||
if (contractId && m.contract_id != contractId)
|
||||
{
|
||||
if (contractId && m.contract_id != contractId) {
|
||||
console.error("Contract id mismatch.")
|
||||
ws.close();
|
||||
}
|
||||
@@ -121,8 +120,7 @@ function HotPocketClient(contractId, server, keys, protocol = protocols.json) {
|
||||
}, 100);
|
||||
}
|
||||
else if (m.type == 'contract_read_response') {
|
||||
const decoded = msgHelper.binaryDecode(m.content);
|
||||
emitter.emit(events.contractReadResponse, decoded);
|
||||
emitter.emit(events.contractReadResponse, msgHelper.deserializeOutput(m.content));
|
||||
}
|
||||
else if (m.type == 'contract_input_status') {
|
||||
const sigKey = (typeof m.input_sig === "string") ? m.input_sig : m.input_sig.toString("hex");
|
||||
@@ -136,8 +134,7 @@ function HotPocketClient(contractId, server, keys, protocol = protocols.json) {
|
||||
}
|
||||
}
|
||||
else if (m.type == 'contract_output') {
|
||||
const decoded = msgHelper.binaryDecode(m.content);
|
||||
emitter.emit(events.contractOutput, decoded);
|
||||
emitter.emit(events.contractOutput, msgHelper.deserializeOutput(m.content));
|
||||
}
|
||||
else if (m.type == "stat_response") {
|
||||
statResponseResolvers.forEach(resolver => {
|
||||
@@ -224,10 +221,6 @@ function MessageHelper(keys, protocol) {
|
||||
return protocol == protocols.json ? buffer.toString("hex") : buffer;
|
||||
}
|
||||
|
||||
this.binaryDecode = function (content) {
|
||||
return (protocol == protocols.json) ? Buffer.from(content, "hex") : content.buffer;
|
||||
}
|
||||
|
||||
this.serializeObject = function (obj) {
|
||||
return protocol == protocols.json ? JSON.stringify(obj) : bson.serialize(obj);
|
||||
}
|
||||
@@ -242,6 +235,10 @@ function MessageHelper(keys, protocol) {
|
||||
Buffer.isBuffer(input) ? input : Buffer.from(input);
|
||||
}
|
||||
|
||||
this.deserializeOutput = function (content) {
|
||||
return (protocol == protocols.json) ? content : content.buffer;
|
||||
}
|
||||
|
||||
this.createHandshakeResponse = function (challenge) {
|
||||
// For handshake response encoding Hot Pocket always uses json.
|
||||
// Handshake response will specify the protocol to use for subsequent messages.
|
||||
@@ -1,4 +1,4 @@
|
||||
const { HotPocketClient, HotPocketKeyGenerator, HotPocketEvents } = require('./hp-client-lib');
|
||||
const { HotPocketClient, HotPocketKeyGenerator, HotPocketEvents } = require('./hp-node-client-lib');
|
||||
|
||||
async function main() {
|
||||
|
||||
|
||||
2469
examples/nodejs_client/package-lock.json
generated
2469
examples/nodejs_client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"scripts": {
|
||||
"buildnode": "browserify --node -p tinyify hp-node-client-lib.js -o dist/hp-node-client-lib.js",
|
||||
"buildbrowser": "browserify -p tinyify hp-node-client-lib.js -o dist/hp-browsercompat-client-lib.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"libsodium-wrappers": "0.7.6",
|
||||
"ws": "7.1.2",
|
||||
@@ -6,5 +10,9 @@
|
||||
"bson": "4.0.4",
|
||||
"utf-8-validate": "5.0.2",
|
||||
"bufferutil": "4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "16.5.2",
|
||||
"tinyify": "3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const readline = require('readline');
|
||||
const { exit } = require('process');
|
||||
const HotPocket = require('./hp-client-lib');
|
||||
const HotPocket = require('./hp-node-client-lib');
|
||||
|
||||
async function main() {
|
||||
const keys = await HotPocket.KeyGenerator.generate();
|
||||
@@ -28,12 +28,12 @@ async function main() {
|
||||
|
||||
// This will get fired when contract sends an output.
|
||||
hpc.on(HotPocket.events.contractOutput, (output) => {
|
||||
console.log("Contract output>> " + Buffer.from(output, "hex"));
|
||||
console.log("Contract output>> " + output);
|
||||
})
|
||||
|
||||
// This will get fired when contract sends a read response.
|
||||
hpc.on(HotPocket.events.contractReadResponse, (response) => {
|
||||
console.log("Contract read response>> " + Buffer.from(response, "hex"));
|
||||
console.log("Contract read response>> " + response);
|
||||
})
|
||||
|
||||
// On ctrl + c we should close HP connection gracefully.
|
||||
|
||||
@@ -15,7 +15,7 @@ const echoContract = async (ctx) => {
|
||||
for (const user of ctx.users.list()) {
|
||||
|
||||
// This user's pubkey can be accessed from 'user.pubKey'
|
||||
|
||||
|
||||
for (const input of user.inputs) {
|
||||
|
||||
inputHandlers.push(new Promise(async (resolve) => {
|
||||
@@ -23,10 +23,11 @@ const echoContract = async (ctx) => {
|
||||
const buf = await ctx.users.read(input);
|
||||
const msg = buf.toString();
|
||||
|
||||
if (msg == "ts")
|
||||
await user.send(fs.readFileSync("exects.txt"));
|
||||
else
|
||||
await user.send("Echoing: " + msg);
|
||||
const output = (msg == "ts") ? fs.readFileSync("exects.txt").toString() : ("Echoing: " + msg);
|
||||
|
||||
// Stringify to escape JSON characters and remove surrounding double quotes.
|
||||
const stringified = JSON.stringify(output);
|
||||
await user.send(stringified.substr(1, stringified.length - 2));
|
||||
|
||||
resolve();
|
||||
}));
|
||||
|
||||
@@ -3,72 +3,77 @@ const fs = require('fs');
|
||||
const bson = require('bson');
|
||||
|
||||
const fileContract = async (ctx) => {
|
||||
await ctx.users.onMessage(async (user, buf) => {
|
||||
const msg = bson.deserialize(buf);
|
||||
|
||||
if (msg.type == "upload") {
|
||||
if (fs.existsSync(msg.fileName)) {
|
||||
await user.send(bson.serialize({
|
||||
type: "uploadResult",
|
||||
status: "already_exists",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
for (const user of ctx.users.list()) {
|
||||
|
||||
for (const input of user.inputs) {
|
||||
const buf = await ctx.users.read(input);
|
||||
const msg = bson.deserialize(buf);
|
||||
|
||||
if (msg.type == "upload") {
|
||||
if (fs.existsSync(msg.fileName)) {
|
||||
await user.send(bson.serialize({
|
||||
type: "uploadResult",
|
||||
status: "already_exists",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
else if (msg.content.length > 10 * 1024 * 1024) { // 10MB
|
||||
await user.send(bson.serialize({
|
||||
type: "uploadResult",
|
||||
status: "too_large",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
else {
|
||||
|
||||
// Save the file.
|
||||
fs.writeFileSync(msg.fileName, msg.content.buffer);
|
||||
|
||||
await user.send(bson.serialize({
|
||||
type: "uploadResult",
|
||||
status: "ok",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
}
|
||||
else if (msg.content.length > 10 * 1024 * 1024) { // 10MB
|
||||
await user.send(bson.serialize({
|
||||
type: "uploadResult",
|
||||
status: "too_large",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
else if (msg.type == "delete") {
|
||||
if (fs.existsSync(msg.fileName)) {
|
||||
fs.unlinkSync(msg.fileName);
|
||||
await user.send(bson.serialize({
|
||||
type: "deleteResult",
|
||||
status: "ok",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
else {
|
||||
await user.send(bson.serialize({
|
||||
type: "deleteResult",
|
||||
status: "not_found",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
// Save the file.
|
||||
fs.writeFileSync(msg.fileName, msg.content.buffer);
|
||||
|
||||
await user.send(bson.serialize({
|
||||
type: "uploadResult",
|
||||
status: "ok",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
else if (msg.type == "download") {
|
||||
if (fs.existsSync(msg.fileName)) {
|
||||
const fileContent = fs.readFileSync(msg.fileName);
|
||||
await user.send(bson.serialize({
|
||||
type: "downloadResult",
|
||||
status: "ok",
|
||||
fileName: msg.fileName,
|
||||
content: fileContent
|
||||
}));
|
||||
}
|
||||
else {
|
||||
await user.send(bson.serialize({
|
||||
type: "downloadResult",
|
||||
status: "not_found",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (msg.type == "delete") {
|
||||
if (fs.existsSync(msg.fileName)) {
|
||||
fs.unlinkSync(msg.fileName);
|
||||
await user.send(bson.serialize({
|
||||
type: "deleteResult",
|
||||
status: "ok",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
else {
|
||||
await user.send(bson.serialize({
|
||||
type: "deleteResult",
|
||||
status: "not_found",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
}
|
||||
else if (msg.type == "download") {
|
||||
if (fs.existsSync(msg.fileName)) {
|
||||
const fileContent = fs.readFileSync(msg.fileName);
|
||||
await user.send(bson.serialize({
|
||||
type: "downloadResult",
|
||||
status: "ok",
|
||||
fileName: msg.fileName,
|
||||
content: fileContent
|
||||
}));
|
||||
}
|
||||
else {
|
||||
await user.send(bson.serialize({
|
||||
type: "downloadResult",
|
||||
status: "not_found",
|
||||
fileName: msg.fileName
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const hpc = new HotPocketContract();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const fs = require('fs');
|
||||
const tty = require('tty');
|
||||
require('process');
|
||||
|
||||
const MAX_SEQ_PACKET_SIZE = 128 * 1024;
|
||||
const CONTROL_MESSAGE = {
|
||||
@@ -14,14 +16,20 @@ class HotPocketContract {
|
||||
init(contractFunc) {
|
||||
|
||||
if (this.#controlChannel) // Already initialized.
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (tty.isatty(process.stdin.fd)) {
|
||||
console.error("Error: Hot Pocket smart contracts must be executed via Hot Pocket.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse HotPocket args.
|
||||
const argsJson = fs.readFileSync(0, 'utf8');
|
||||
const argsJson = fs.readFileSync(process.stdin.fd, 'utf8');
|
||||
const hpargs = JSON.parse(argsJson);
|
||||
|
||||
this.#controlChannel = new ControlChannel(hpargs.controlfd);
|
||||
this.#executeContract(hpargs, contractFunc);
|
||||
return true;
|
||||
}
|
||||
|
||||
#executeContract = (hpargs, contractFunc) => {
|
||||
|
||||
@@ -140,18 +140,12 @@ namespace msg::usrmsg::json
|
||||
* Message format:
|
||||
* {
|
||||
* "type": "contract_read_response",
|
||||
* "content": "<hex encoded contract output>"
|
||||
* "content": "<response string>"
|
||||
* }
|
||||
* @param content The contract binary output content to be put in the message.
|
||||
*/
|
||||
void create_contract_read_response_container(std::vector<uint8_t> &msg, std::string_view content)
|
||||
{
|
||||
std::string contenthex;
|
||||
util::bin2hex(
|
||||
contenthex,
|
||||
reinterpret_cast<const unsigned char *>(content.data()),
|
||||
content.length());
|
||||
|
||||
msg.reserve(256);
|
||||
msg += "{\"";
|
||||
msg += msg::usrmsg::FLD_TYPE;
|
||||
@@ -160,7 +154,7 @@ namespace msg::usrmsg::json
|
||||
msg += SEP_COMMA;
|
||||
msg += msg::usrmsg::FLD_CONTENT;
|
||||
msg += SEP_COLON;
|
||||
msg += contenthex;
|
||||
msg += content;
|
||||
msg += "\"}";
|
||||
}
|
||||
|
||||
@@ -172,18 +166,12 @@ namespace msg::usrmsg::json
|
||||
* "type": "contract_output",
|
||||
* "lcl": "<lcl id>"
|
||||
* "lcl_seqno": <integer>,
|
||||
* "content": "<hex encoded contract output>"
|
||||
* "content": "<contract output string>"
|
||||
* }
|
||||
* @param content The contract binary output content to be put in the message.
|
||||
*/
|
||||
void create_contract_output_container(std::vector<uint8_t> &msg, std::string_view content, const uint64_t lcl_seq_no, std::string_view lcl)
|
||||
{
|
||||
std::string contenthex;
|
||||
util::bin2hex(
|
||||
contenthex,
|
||||
reinterpret_cast<const unsigned char *>(content.data()),
|
||||
content.length());
|
||||
|
||||
msg.reserve(256);
|
||||
msg += "{\"";
|
||||
msg += msg::usrmsg::FLD_TYPE;
|
||||
@@ -200,7 +188,7 @@ namespace msg::usrmsg::json
|
||||
msg += SEP_COMMA_NOQUOTE;
|
||||
msg += msg::usrmsg::FLD_CONTENT;
|
||||
msg += SEP_COLON;
|
||||
msg += contenthex;
|
||||
msg += content;
|
||||
msg += "\"}";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user