madeline-ton
Version:
Pure JS client-side implementation of the Telegram TON blockchain protocol
313 lines (255 loc) • 10.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _stream = _interopRequireDefault(require("../TL/stream"));
var _tools = require("../tools");
var _random = require("../crypto-sync/random");
var ADNL =
/*#__PURE__*/
function () {
function ADNL() {
(0, _classCallCheck2["default"])(this, ADNL);
(0, _defineProperty2["default"])(this, "allRead", 0);
(0, _defineProperty2["default"])(this, "toRead", 4);
(0, _defineProperty2["default"])(this, "buffers", []);
(0, _defineProperty2["default"])(this, "offset", 0);
(0, _defineProperty2["default"])(this, "bufferOffset", 0);
(0, _defineProperty2["default"])(this, "cBufferIndex", 0);
}
(0, _createClass2["default"])(ADNL, [{
key: "connect",
/**
*
* @param {Object} ctx Custom connection context
*/
value: function () {
var _connect = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee(ctx) {
var _this$crypto,
_this$crypto2,
_this = this;
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
this.crypto = ctx.crypto;
_context.next = 3;
return (_this$crypto = this.crypto).getCtr.apply(_this$crypto, (0, _toConsumableArray2["default"])(ctx.encrypt));
case 3:
this.encrypt = _context.sent;
_context.next = 6;
return (_this$crypto2 = this.crypto).getCtr.apply(_this$crypto2, (0, _toConsumableArray2["default"])(ctx.decrypt));
case 6:
this.decrypt = _context.sent;
this.previous = Promise.resolve();
_context.next = 10;
return new Promise(function (resolve, reject) {
_this.socket = new WebSocket(ctx.uri);
_this.socket.binaryType = "arraybuffer";
_this.socket.onmessage = function (message) {
message = message.data;
var idx = _this.cBufferIndex++;
_this.cBufferIndex %= 0xFFFFFFFF;
var previous = _this.previous;
_this.previous = _this.decrypt.process(new Uint8Array(message)).then(function (result) {
_this.buffers[idx] = new Uint8Array(result);
_this.allRead += result.byteLength;
return previous;
}).then(function () {
//console.log("Got payload with length " + message.byteLength)
if (_this.allRead >= _this.toRead && !_this.isRunning) {
_this.process();
}
}); // Here we have the nasty detail that a protocol-unaware websocket proxy might send out unproperly framed data
// All websocket proxies in this case are protocol-unaware, since MITM is NOT possible due to ECC
};
_this.socket.onopen = resolve;
_this.socket.onerror = reject;
_this.socket.onclose = _this.close.bind(_this);
});
case 10:
this.socket.onerror = function (e) {
console.log("Websocket error: ", e);
_this.close();
};
return _context.abrupt("return", this.socket.send(ctx.init));
case 12:
case "end":
return _context.stop();
}
}
}, _callee, this);
}));
function connect(_x) {
return _connect.apply(this, arguments);
}
return connect;
}()
/**
* Read N bytes from the decrypted buffer(s)
*/
}, {
key: "read",
value: function read(length) {
var left = this.buffers[this.bufferOffset].byteLength - this.offset;
if (left > length) {
var _buffer = this.buffers[this.bufferOffset].slice(this.offset, this.offset + length);
this.offset += length;
return _buffer.buffer;
}
if (left === length) {
var _buffer2 = this.buffers[this.bufferOffset];
if (this.offset) {
_buffer2 = _buffer2.slice(this.offset);
}
delete this.buffers[this.bufferOffset++];
this.offset = 0;
return _buffer2.buffer;
}
var buffer = this.buffers[this.bufferOffset];
if (this.offset) {
buffer = buffer.slice(this.offset);
}
delete this.buffers[this.bufferOffset++];
this.offset = 0;
return (0, _tools.bufferConcat)(buffer, new Uint8Array(this.read(length - buffer.byteLength))).buffer;
}
}, {
key: "process",
value: function () {
var _process = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee2() {
var toRead, message, sha;
return _regenerator["default"].wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
this.isRunning = true;
if (!(this.toRead === 4)) {
_context2.next = 7;
break;
}
// Read length and possibly more
this.allRead -= this.toRead;
this.toRead = new Uint32Array(this.read(this.toRead))[0]; // Forget about exotic endiannesses for now
if (!(this.allRead < this.toRead)) {
_context2.next = 7;
break;
}
this.isRunning = false; //console.log(`Not enough decrypted data to read actual packet, suspending (need ${this.toRead}, have ${this.allRead})`)
return _context2.abrupt("return");
case 7:
this.allRead -= this.toRead;
toRead = this.toRead - 32;
this.toRead = 4;
message = new Uint32Array(this.read(toRead));
_context2.t0 = Uint32Array;
_context2.next = 14;
return this.crypto.sha256(message);
case 14:
_context2.t1 = _context2.sent;
sha = new _context2.t0(_context2.t1);
if ((0, _tools.bufferViewEqual)(sha, new Uint32Array(this.read(32)))) {
_context2.next = 18;
break;
}
throw new Error('SHA256 mismatch!');
case 18:
if (message.length === 8) {
console.log("OK, got empty message!");
} else {
this.onMessage(new _stream["default"](message.slice(8).buffer));
}
if (this.allRead >= this.toRead) {
this.process();
}
this.isRunning = false;
case 21:
case "end":
return _context2.stop();
}
}
}, _callee2, this);
}));
function process() {
return _process.apply(this, arguments);
}
return process;
}()
}, {
key: "write",
value: function () {
var _write = (0, _asyncToGenerator2["default"])(
/*#__PURE__*/
_regenerator["default"].mark(function _callee3(payload) {
var _this2 = this;
return _regenerator["default"].wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
payload.pos = 0;
payload.writeUnsignedInt(payload.bBuf.length - 4);
payload.writeUnsignedInts((0, _random.fastRandom)(new Uint32Array(8)));
payload.pos = payload.uBuf.length - 8;
_context3.t0 = payload;
_context3.t1 = Uint32Array;
_context3.next = 8;
return this.crypto.sha256(payload.uBuf.slice(1, payload.pos));
case 8:
_context3.t2 = _context3.sent;
_context3.t3 = new _context3.t1(_context3.t2);
_context3.t0.writeUnsignedInts.call(_context3.t0, _context3.t3);
payload = payload.bBuf;
return _context3.abrupt("return", this.encrypt.process(payload).then(function (payload) {
return _this2.socket.send(payload);
}));
case 13:
case "end":
return _context3.stop();
}
}
}, _callee3, this);
}));
function write(_x2) {
return _write.apply(this, arguments);
}
return write;
}()
}, {
key: "getBuffer",
value: function getBuffer(length) {
var s = new _stream["default"](new Uint32Array(17 + (length || 0)));
s.pos += 9;
return s;
}
}, {
key: "close",
value: function close() {
console.log("Closing socket!");
if (this.socket) {
this.socket.close();
this.socket = undefined;
this.onClose();
}
}
}, {
key: "isHttp",
value: function isHttp() {
return false;
}
}]);
return ADNL;
}();
var _default = ADNL;
exports["default"] = _default;