anchor-link
Advanced tools
Comparing version 1.0.1 to 1.0.2
@@ -1,2 +0,13 @@ | ||
/** Error that is thrown if a LinkPresenter calls the cancel callback. */ | ||
/** | ||
* Error codes. Accessible using the `code` property on errors thrown by [[Link]] and [[LinkSession]]. | ||
* - `E_DELIVERY`: Unable to request message to wallet. | ||
* - `E_TIMEOUT`: Request was delivered but user/wallet didn't respond in time. | ||
* - `E_CANCEL`: The [[LinkTransport]] canceled the request. | ||
* - `E_IDENTITY`: Identity proof failed to verify. | ||
*/ | ||
export declare type LinkErrorCode = 'E_DELIVERY' | 'E_TIMEOUT' | 'E_CANCEL' | 'E_IDENTITY'; | ||
/** | ||
* Error that is thrown if a [[LinkTransport]] cancels a request. | ||
* @internal | ||
*/ | ||
export declare class CancelError extends Error { | ||
@@ -6,3 +17,6 @@ code: string; | ||
} | ||
/** Error that is thrown if an identity request fails to verify. */ | ||
/** | ||
* Error that is thrown if an identity request fails to verify. | ||
* @internal | ||
*/ | ||
export declare class IdentityError extends Error { | ||
@@ -13,11 +27,8 @@ code: string; | ||
/** | ||
* Session error codes. | ||
* - E_DELIVERY: Unable to request message to wallet. | ||
* - E_TIMEOUT: Request was delivered but user/wallet didn't respond in time. | ||
* Error originating from a [[LinkSession]]. | ||
* @internal | ||
*/ | ||
export declare type SessionErrorCode = 'E_DELIVERY' | 'E_TIMEOUT'; | ||
/** Error that is thrown by session transport. */ | ||
export declare class SessionError extends Error { | ||
code: SessionErrorCode; | ||
constructor(reason: string, code: SessionErrorCode); | ||
code: 'E_DELIVERY' | 'E_TIMEOUT'; | ||
constructor(reason: string, code: 'E_DELIVERY' | 'E_TIMEOUT'); | ||
} |
@@ -1,2 +0,5 @@ | ||
/** Error that is thrown if a LinkPresenter calls the cancel callback. */ | ||
/** | ||
* Error that is thrown if a [[LinkTransport]] cancels a request. | ||
* @internal | ||
*/ | ||
export class CancelError extends Error { | ||
@@ -8,3 +11,6 @@ constructor(reason) { | ||
} | ||
/** Error that is thrown if an identity request fails to verify. */ | ||
/** | ||
* Error that is thrown if an identity request fails to verify. | ||
* @internal | ||
*/ | ||
export class IdentityError extends Error { | ||
@@ -16,3 +22,6 @@ constructor(reason) { | ||
} | ||
/** Error that is thrown by session transport. */ | ||
/** | ||
* Error originating from a [[LinkSession]]. | ||
* @internal | ||
*/ | ||
export class SessionError extends Error { | ||
@@ -19,0 +28,0 @@ constructor(reason, code) { |
@@ -6,1 +6,3 @@ export * from './link'; | ||
export * from './errors'; | ||
import { Link } from './link'; | ||
export default Link; |
@@ -15,25 +15,122 @@ 'use strict'; | ||
/** Error that is thrown if a LinkPresenter calls the cancel callback. */ | ||
class CancelError extends Error { | ||
constructor(reason) { | ||
super(`User canceled request ${reason ? '(' + reason + ')' : ''}`); | ||
this.code = 'E_CANCEL'; | ||
} | ||
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use | ||
this file except in compliance with the License. You may obtain a copy of the | ||
License at http://www.apache.org/licenses/LICENSE-2.0 | ||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED | ||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, | ||
MERCHANTABLITY OR NON-INFRINGEMENT. | ||
See the Apache Version 2.0 License for specific language governing permissions | ||
and limitations under the License. | ||
***************************************************************************** */ | ||
/* global Reflect, Promise */ | ||
var extendStatics = function(d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
function __extends(d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
} | ||
/** Error that is thrown if an identity request fails to verify. */ | ||
class IdentityError extends Error { | ||
constructor(reason) { | ||
super(`Unable to verify identity ${reason ? '(' + reason + ')' : ''}`); | ||
this.code = 'E_IDENTITY'; | ||
} | ||
var __assign = function() { | ||
__assign = Object.assign || function __assign(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
function __awaiter(thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
} | ||
/** Error that is thrown by session transport. */ | ||
class SessionError extends Error { | ||
constructor(reason, code) { | ||
super(reason); | ||
this.code = code; | ||
function __generator(thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
} | ||
const defaults = { | ||
/** | ||
* Error that is thrown if a [[LinkTransport]] cancels a request. | ||
* @internal | ||
*/ | ||
var CancelError = /** @class */ (function (_super) { | ||
__extends(CancelError, _super); | ||
function CancelError(reason) { | ||
var _this = _super.call(this, "User canceled request " + (reason ? '(' + reason + ')' : '')) || this; | ||
_this.code = 'E_CANCEL'; | ||
return _this; | ||
} | ||
return CancelError; | ||
}(Error)); | ||
/** | ||
* Error that is thrown if an identity request fails to verify. | ||
* @internal | ||
*/ | ||
var IdentityError = /** @class */ (function (_super) { | ||
__extends(IdentityError, _super); | ||
function IdentityError(reason) { | ||
var _this = _super.call(this, "Unable to verify identity " + (reason ? '(' + reason + ')' : '')) || this; | ||
_this.code = 'E_IDENTITY'; | ||
return _this; | ||
} | ||
return IdentityError; | ||
}(Error)); | ||
/** | ||
* Error originating from a [[LinkSession]]. | ||
* @internal | ||
*/ | ||
var SessionError = /** @class */ (function (_super) { | ||
__extends(SessionError, _super); | ||
function SessionError(reason, code) { | ||
var _this = _super.call(this, reason) || this; | ||
_this.code = code; | ||
return _this; | ||
} | ||
return SessionError; | ||
}(Error)); | ||
/** @internal */ | ||
var defaults = { | ||
chainId: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906', | ||
@@ -102,15 +199,24 @@ rpc: 'https://eos.greymass.com', | ||
const types = eosjs.Serialize.getTypesFromAbi(eosjs.Serialize.createInitialTypes(), linkAbi); | ||
/** @internal */ | ||
var types = eosjs.Serialize.getTypesFromAbi(eosjs.Serialize.createInitialTypes(), linkAbi); | ||
/** | ||
* Helper to ABI encode value. | ||
* @internal | ||
*/ | ||
function abiEncode(value, typeName) { | ||
let type = types.get(typeName); | ||
var type = types.get(typeName); | ||
if (!type) { | ||
throw new Error(`No such type: ${typeName}`); | ||
throw new Error("No such type: " + typeName); | ||
} | ||
let buf = new eosjs.Serialize.SerialBuffer(); | ||
var buf = new eosjs.Serialize.SerialBuffer(); | ||
type.serialize(buf, value); | ||
return buf.asUint8Array(); | ||
} | ||
/** | ||
* Encrypt a message using AES and shared secret derived from given keys. | ||
* @internal | ||
*/ | ||
function sealMessage(message, privateKey, publicKey) { | ||
const res = ecc.Aes.encrypt(privateKey, publicKey, message); | ||
const data = { | ||
var res = ecc.Aes.encrypt(privateKey, publicKey, message); | ||
var data = { | ||
from: ecc.privateToPublic(privateKey), | ||
@@ -123,3 +229,6 @@ nonce: res.nonce.toString(), | ||
} | ||
/** Ensure public key is in new PUB_ format. */ | ||
/** | ||
* Ensure public key is in new PUB_ format. | ||
* @internal | ||
*/ | ||
function normalizePublicKey(key) { | ||
@@ -131,3 +240,6 @@ if (key.startsWith('PUB_')) { | ||
} | ||
/** Return true if given public keys are equal. */ | ||
/** | ||
* Return true if given public keys are equal. | ||
* @internal | ||
*/ | ||
function publicKeyEqual(keyA, keyB) { | ||
@@ -141,5 +253,7 @@ return normalizePublicKey(keyA) === normalizePublicKey(keyB); | ||
*/ | ||
class LinkSession { | ||
var LinkSession = /** @class */ (function () { | ||
function LinkSession() { | ||
} | ||
/** Restore a previously serialized session. */ | ||
static restore(link, data) { | ||
LinkSession.restore = function (link, data) { | ||
switch (data.type) { | ||
@@ -153,42 +267,42 @@ case 'channel': | ||
} | ||
} | ||
} | ||
}; | ||
return LinkSession; | ||
}()); | ||
/** | ||
* Link session that pushes requests over a channel. | ||
* @internal | ||
*/ | ||
class LinkChannelSession extends LinkSession { | ||
constructor(link, data, metadata) { | ||
super(); | ||
this.type = 'channel'; | ||
this.timeout = 2 * 60 * 1000; // ms | ||
this.link = link; | ||
this.auth = data.auth; | ||
this.publicKey = data.publicKey; | ||
this.channel = data.channel; | ||
this.encrypt = (request) => { | ||
var LinkChannelSession = /** @class */ (function (_super) { | ||
__extends(LinkChannelSession, _super); | ||
function LinkChannelSession(link, data, metadata) { | ||
var _this = _super.call(this) || this; | ||
_this.type = 'channel'; | ||
_this.timeout = 2 * 60 * 1000; // ms | ||
_this.link = link; | ||
_this.auth = data.auth; | ||
_this.publicKey = data.publicKey; | ||
_this.channel = data.channel; | ||
_this.encrypt = function (request) { | ||
return sealMessage(request.encode(true, false), data.requestKey, data.channel.key); | ||
}; | ||
this.metadata = { | ||
...(metadata || {}), | ||
timeout: this.timeout, | ||
name: this.channel.name, | ||
}; | ||
this.serialize = () => ({ | ||
_this.metadata = __assign(__assign({}, (metadata || {})), { timeout: _this.timeout, name: _this.channel.name }); | ||
_this.serialize = function () { return ({ | ||
type: 'channel', | ||
data, | ||
metadata: this.metadata, | ||
}); | ||
data: data, | ||
metadata: _this.metadata, | ||
}); }; | ||
return _this; | ||
} | ||
onSuccess(request, result) { | ||
LinkChannelSession.prototype.onSuccess = function (request, result) { | ||
if (this.link.transport.onSuccess) { | ||
this.link.transport.onSuccess(request, result); | ||
} | ||
} | ||
onFailure(request, error) { | ||
}; | ||
LinkChannelSession.prototype.onFailure = function (request, error) { | ||
if (this.link.transport.onFailure) { | ||
this.link.transport.onFailure(request, error); | ||
} | ||
} | ||
onRequest(request, cancel) { | ||
const info = { | ||
}; | ||
LinkChannelSession.prototype.onRequest = function (request, cancel) { | ||
var info = { | ||
expiration: new Date(Date.now() + this.timeout).toISOString().slice(0, -1), | ||
@@ -199,3 +313,3 @@ }; | ||
} | ||
setTimeout(() => { | ||
setTimeout(function () { | ||
cancel(new SessionError('Wallet did not respond in time', 'E_TIMEOUT')); | ||
@@ -215,3 +329,3 @@ }, this.timeout + 500); | ||
}) | ||
.then((response) => { | ||
.then(function (response) { | ||
if (response.status !== 200) { | ||
@@ -221,41 +335,48 @@ cancel(new SessionError('Unable to push message', 'E_DELIVERY')); | ||
}) | ||
.catch((error) => { | ||
cancel(new SessionError(`Unable to reach link service (${error.message || String(error)})`, 'E_DELIVERY')); | ||
.catch(function (error) { | ||
cancel(new SessionError("Unable to reach link service (" + (error.message || String(error)) + ")", 'E_DELIVERY')); | ||
}); | ||
} | ||
makeSignatureProvider() { | ||
}; | ||
LinkChannelSession.prototype.makeSignatureProvider = function () { | ||
return this.link.makeSignatureProvider([this.publicKey], this); | ||
} | ||
makeAuthorityProvider() { | ||
}; | ||
LinkChannelSession.prototype.makeAuthorityProvider = function () { | ||
return this.link.makeAuthorityProvider(); | ||
} | ||
transact(args) { | ||
}; | ||
LinkChannelSession.prototype.transact = function (args) { | ||
return this.link.transact(args, this); | ||
}; | ||
return LinkChannelSession; | ||
}(LinkSession)); | ||
/** | ||
* Link session that sends every request over the transport. | ||
* @internal | ||
*/ | ||
var LinkFallbackSession = /** @class */ (function (_super) { | ||
__extends(LinkFallbackSession, _super); | ||
function LinkFallbackSession(link, data, metadata) { | ||
var _this = _super.call(this) || this; | ||
_this.type = 'fallback'; | ||
_this.link = link; | ||
_this.auth = data.auth; | ||
_this.publicKey = data.publicKey; | ||
_this.metadata = metadata || {}; | ||
_this.serialize = function () { return ({ | ||
type: _this.type, | ||
data: data, | ||
metadata: _this.metadata, | ||
}); }; | ||
return _this; | ||
} | ||
} | ||
class LinkFallbackSession extends LinkSession { | ||
constructor(link, data, metadata) { | ||
super(); | ||
this.type = 'fallback'; | ||
this.link = link; | ||
this.auth = data.auth; | ||
this.publicKey = data.publicKey; | ||
this.metadata = metadata || {}; | ||
this.serialize = () => ({ | ||
type: this.type, | ||
data, | ||
metadata: this.metadata, | ||
}); | ||
} | ||
onSuccess(request, result) { | ||
LinkFallbackSession.prototype.onSuccess = function (request, result) { | ||
if (this.link.transport.onSuccess) { | ||
this.link.transport.onSuccess(request, result); | ||
} | ||
} | ||
onFailure(request, error) { | ||
}; | ||
LinkFallbackSession.prototype.onFailure = function (request, error) { | ||
if (this.link.transport.onFailure) { | ||
this.link.transport.onFailure(request, error); | ||
} | ||
} | ||
onRequest(request, cancel) { | ||
}; | ||
LinkFallbackSession.prototype.onRequest = function (request, cancel) { | ||
if (this.link.transport.onSessionRequest) { | ||
@@ -267,18 +388,43 @@ this.link.transport.onSessionRequest(this, request, cancel); | ||
} | ||
} | ||
makeSignatureProvider() { | ||
}; | ||
LinkFallbackSession.prototype.makeSignatureProvider = function () { | ||
return this.link.makeSignatureProvider([this.publicKey], this); | ||
} | ||
makeAuthorityProvider() { | ||
}; | ||
LinkFallbackSession.prototype.makeAuthorityProvider = function () { | ||
return this.link.makeAuthorityProvider(); | ||
} | ||
transact(args) { | ||
}; | ||
LinkFallbackSession.prototype.transact = function (args) { | ||
return this.link.transact(args, this); | ||
} | ||
} | ||
}; | ||
return LinkFallbackSession; | ||
}(LinkSession)); | ||
const { fetch } = makeFetch(); | ||
class Link { | ||
constructor(options) { | ||
/** @internal */ | ||
var fetch = makeFetch().fetch; | ||
/** | ||
* Main class, also exposed as the default export of the library. | ||
* | ||
* Example: | ||
* | ||
* ```ts | ||
* import AnchorLink from 'anchor-link' | ||
* import ConsoleTransport from 'anchor-link-console-transport' | ||
* | ||
* const link = new AnchorLink({ | ||
* transport: new ConsoleTransport() | ||
* }) | ||
* | ||
* const result = await link.transact({actions: myActions, broadcast: true}) | ||
* ``` | ||
*/ | ||
var Link = /** @class */ (function () { | ||
/** Create a new link instance. */ | ||
function Link(options) { | ||
this.abiCache = new Map(); | ||
if (typeof options !== 'object') { | ||
throw new TypeError('Missing options object'); | ||
} | ||
if (!options.transport) { | ||
throw new TypeError('options.transport is required, see https://github.com/greymass/anchor-link#transports'); | ||
} | ||
if (options.rpc === undefined || typeof options.rpc === 'string') { | ||
@@ -297,104 +443,161 @@ this.rpc = new eosjs.JsonRpc(options.rpc || defaults.rpc, { fetch: fetch }); | ||
textEncoder: options.textEncoder || new TextEncoder(), | ||
zlib, | ||
zlib: zlib, | ||
}; | ||
} | ||
async getAbi(account) { | ||
let rv = this.abiCache.get(account); | ||
if (!rv) { | ||
rv = (await this.rpc.get_abi(account)).abi; | ||
if (rv) { | ||
this.abiCache.set(account, rv); | ||
} | ||
} | ||
return rv; | ||
} | ||
createCallbackUrl() { | ||
return `${this.serviceAddress}/${uuid.v4()}`; | ||
} | ||
async createRequest(args) { | ||
// generate unique callback url | ||
const request = await esr.SigningRequest.create({ | ||
...args, | ||
chainId: this.chainId, | ||
broadcast: false, | ||
callback: { | ||
url: this.createCallbackUrl(), | ||
background: true, | ||
}, | ||
}, this.requestOptions); | ||
return request; | ||
} | ||
async sendRequest(request, transport) { | ||
const t = transport || this.transport; | ||
try { | ||
const linkUrl = request.data.callback; | ||
if (!linkUrl.startsWith(this.serviceAddress)) { | ||
throw new Error('Request must have a link callback'); | ||
} | ||
if (request.data.flags !== 2) { | ||
throw new Error('Invalid request flags'); | ||
} | ||
// wait for callback or user cancel | ||
const ctx = {}; | ||
const socket = waitForCallback(linkUrl, ctx); | ||
const cancel = new Promise((resolve, reject) => { | ||
t.onRequest(request, (reason) => { | ||
if (ctx.cancel) { | ||
ctx.cancel(); | ||
} | ||
if (typeof reason === 'string') { | ||
reject(new CancelError(reason)); | ||
} | ||
else { | ||
reject(reason); | ||
} | ||
}); | ||
/** | ||
* Fetch the ABI for given account, cached. | ||
* @internal | ||
*/ | ||
Link.prototype.getAbi = function (account) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var rv; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
rv = this.abiCache.get(account); | ||
if (!!rv) return [3 /*break*/, 2]; | ||
return [4 /*yield*/, this.rpc.get_abi(account)]; | ||
case 1: | ||
rv = (_a.sent()).abi; | ||
if (rv) { | ||
this.abiCache.set(account, rv); | ||
} | ||
_a.label = 2; | ||
case 2: return [2 /*return*/, rv]; | ||
} | ||
}); | ||
const payload = await Promise.race([socket, cancel]); | ||
const signer = { | ||
actor: payload.sa, | ||
permission: payload.sp, | ||
}; | ||
const signatures = Object.keys(payload) | ||
.filter((key) => key.startsWith('sig') && key !== 'sig0') | ||
.map((key) => payload[key]); | ||
// recreate transaction from request response | ||
const resolved = await esr.ResolvedSigningRequest.fromPayload(payload, this.requestOptions); | ||
const { serializedTransaction, transaction } = resolved; | ||
const result = { | ||
request: resolved.request, | ||
serializedTransaction, | ||
transaction, | ||
signatures, | ||
payload, | ||
signer, | ||
}; | ||
if (t.onSuccess) { | ||
t.onSuccess(request, result); | ||
} | ||
return result; | ||
} | ||
catch (error) { | ||
if (t.onFailure) { | ||
t.onFailure(request, error); | ||
} | ||
throw error; | ||
} | ||
} | ||
async transact(args, transport) { | ||
const t = transport || this.transport; | ||
const request = await this.createRequest(args); | ||
const result = await this.sendRequest(request, t); | ||
// broadcast transaction if requested | ||
const broadcast = args.broadcast || false; | ||
if (broadcast) { | ||
const res = await this.rpc.push_transaction({ | ||
signatures: result.signatures, | ||
serializedTransaction: result.serializedTransaction, | ||
}); | ||
}; | ||
/** | ||
* Create a new unique buoy callback url. | ||
* @internal | ||
*/ | ||
Link.prototype.createCallbackUrl = function () { | ||
return this.serviceAddress + "/" + uuid.v4(); | ||
}; | ||
/** | ||
* Create a SigningRequest instance configured for this link. | ||
* @internal | ||
*/ | ||
Link.prototype.createRequest = function (args) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var request; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, esr.SigningRequest.create(__assign(__assign({}, args), { chainId: this.chainId, broadcast: false, callback: { | ||
url: this.createCallbackUrl(), | ||
background: true, | ||
} }), this.requestOptions)]; | ||
case 1: | ||
request = _a.sent(); | ||
return [2 /*return*/, request]; | ||
} | ||
}); | ||
result.processed = res.processed; | ||
} | ||
return result; | ||
} | ||
}); | ||
}; | ||
/** | ||
* Send a SigningRequest instance using this link. | ||
* @internal | ||
*/ | ||
Link.prototype.sendRequest = function (request, transport, broadcast) { | ||
if (broadcast === void 0) { broadcast = false; } | ||
return __awaiter(this, void 0, void 0, function () { | ||
var t, linkUrl, ctx_1, socket, cancel, payload_1, signer, signatures, resolved, serializedTransaction, transaction, result, res, error_1; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
t = transport || this.transport; | ||
_a.label = 1; | ||
case 1: | ||
_a.trys.push([1, 6, , 7]); | ||
linkUrl = request.data.callback; | ||
if (!linkUrl.startsWith(this.serviceAddress)) { | ||
throw new Error('Request must have a link callback'); | ||
} | ||
if (request.data.flags !== 2) { | ||
throw new Error('Invalid request flags'); | ||
} | ||
ctx_1 = {}; | ||
socket = waitForCallback(linkUrl, ctx_1); | ||
cancel = new Promise(function (resolve, reject) { | ||
t.onRequest(request, function (reason) { | ||
if (ctx_1.cancel) { | ||
ctx_1.cancel(); | ||
} | ||
if (typeof reason === 'string') { | ||
reject(new CancelError(reason)); | ||
} | ||
else { | ||
reject(reason); | ||
} | ||
}); | ||
}); | ||
return [4 /*yield*/, Promise.race([socket, cancel])]; | ||
case 2: | ||
payload_1 = _a.sent(); | ||
signer = { | ||
actor: payload_1.sa, | ||
permission: payload_1.sp, | ||
}; | ||
signatures = Object.keys(payload_1) | ||
.filter(function (key) { return key.startsWith('sig') && key !== 'sig0'; }) | ||
.map(function (key) { return payload_1[key]; }); | ||
return [4 /*yield*/, esr.ResolvedSigningRequest.fromPayload(payload_1, this.requestOptions)]; | ||
case 3: | ||
resolved = _a.sent(); | ||
serializedTransaction = resolved.serializedTransaction, transaction = resolved.transaction; | ||
result = { | ||
request: resolved.request, | ||
serializedTransaction: serializedTransaction, | ||
transaction: transaction, | ||
signatures: signatures, | ||
payload: payload_1, | ||
signer: signer, | ||
}; | ||
if (!broadcast) return [3 /*break*/, 5]; | ||
return [4 /*yield*/, this.rpc.push_transaction({ | ||
signatures: result.signatures, | ||
serializedTransaction: result.serializedTransaction, | ||
})]; | ||
case 4: | ||
res = _a.sent(); | ||
result.processed = res.processed; | ||
_a.label = 5; | ||
case 5: | ||
if (t.onSuccess) { | ||
t.onSuccess(request, result); | ||
} | ||
return [2 /*return*/, result]; | ||
case 6: | ||
error_1 = _a.sent(); | ||
if (t.onFailure) { | ||
t.onFailure(request, error_1); | ||
} | ||
throw error_1; | ||
case 7: return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** Sign and optionally broadcast a EOSIO transaction, action or actions. */ | ||
Link.prototype.transact = function (args, transport) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var t, broadcast, request, result; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
t = transport || this.transport; | ||
broadcast = args.broadcast || false; | ||
return [4 /*yield*/, this.createRequest(args)]; | ||
case 1: | ||
request = _a.sent(); | ||
return [4 /*yield*/, this.sendRequest(request, t, broadcast)]; | ||
case 2: | ||
result = _a.sent(); | ||
return [2 /*return*/, result]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Create a identity request. | ||
@@ -404,128 +607,198 @@ * @param requestPermission Optional request permission if the request is for a specific account or permission. | ||
*/ | ||
async identify(requestPermission, info) { | ||
const request = await this.createRequest({ | ||
identity: { permission: requestPermission || null }, | ||
info, | ||
Link.prototype.identify = function (requestPermission, info) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var request, res, message, signer, signerKey, account, permission, auth, keyAuth; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, this.createRequest({ | ||
identity: { permission: requestPermission || null }, | ||
info: info, | ||
})]; | ||
case 1: | ||
request = _a.sent(); | ||
return [4 /*yield*/, this.sendRequest(request)]; | ||
case 2: | ||
res = _a.sent(); | ||
if (!res.request.isIdentity()) { | ||
throw new IdentityError("Unexpected response"); | ||
} | ||
message = Buffer.concat([ | ||
Buffer.from(request.getChainId(), 'hex'), | ||
Buffer.from(res.serializedTransaction), | ||
Buffer.alloc(32), | ||
]); | ||
signer = res.signer; | ||
signerKey = ecc.recover(res.signatures[0], message); | ||
return [4 /*yield*/, this.rpc.get_account(signer.actor)]; | ||
case 3: | ||
account = _a.sent(); | ||
if (!account) { | ||
throw new IdentityError("Signature from unknown account: " + signer.actor); | ||
} | ||
permission = account.permissions.find(function (_a) { | ||
var perm_name = _a.perm_name; | ||
return perm_name === signer.permission; | ||
}); | ||
if (!permission) { | ||
throw new IdentityError(signer.actor + " signed for unknown permission: " + signer.permission); | ||
} | ||
auth = permission.required_auth; | ||
keyAuth = auth.keys.find(function (_a) { | ||
var key = _a.key; | ||
return publicKeyEqual(key, signerKey); | ||
}); | ||
if (!keyAuth) { | ||
throw new IdentityError(formatAuth(signer) + " has no key matching id signature"); | ||
} | ||
if (auth.threshold > keyAuth.weight) { | ||
throw new IdentityError(formatAuth(signer) + " signature does not reach auth threshold"); | ||
} | ||
if (requestPermission) { | ||
if ((requestPermission.actor !== esr.PlaceholderName && | ||
requestPermission.actor !== signer.actor) || | ||
(requestPermission.permission !== esr.PlaceholderPermission && | ||
requestPermission.permission !== signer.permission)) { | ||
throw new IdentityError("Unexpected identity proof from " + formatAuth(signer) + ", expected " + formatAuth(requestPermission) + " "); | ||
} | ||
} | ||
return [2 /*return*/, __assign(__assign({}, res), { account: account, | ||
signerKey: signerKey })]; | ||
} | ||
}); | ||
}); | ||
const res = await this.sendRequest(request); | ||
if (!res.request.isIdentity()) { | ||
throw new IdentityError(`Unexpected response`); | ||
} | ||
const message = Buffer.concat([ | ||
Buffer.from(request.getChainId(), 'hex'), | ||
Buffer.from(res.serializedTransaction), | ||
Buffer.alloc(32), | ||
]); | ||
const { signer } = res; | ||
const signerKey = ecc.recover(res.signatures[0], message); | ||
const account = await this.rpc.get_account(signer.actor); | ||
if (!account) { | ||
throw new IdentityError(`Signature from unknown account: ${signer.actor}`); | ||
} | ||
const permission = account.permissions.find(({ perm_name }) => perm_name === signer.permission); | ||
if (!permission) { | ||
throw new IdentityError(`${signer.actor} signed for unknown permission: ${signer.permission}`); | ||
} | ||
const auth = permission.required_auth; | ||
const keyAuth = auth.keys.find(({ key }) => publicKeyEqual(key, signerKey)); | ||
if (!keyAuth) { | ||
throw new IdentityError(`${formatAuth(signer)} has no key matching id signature`); | ||
} | ||
if (auth.threshold > keyAuth.weight) { | ||
throw new IdentityError(`${formatAuth(signer)} signature does not reach auth threshold`); | ||
} | ||
if (requestPermission) { | ||
if ((requestPermission.actor !== esr.PlaceholderName && | ||
requestPermission.actor !== signer.actor) || | ||
(requestPermission.permission !== esr.PlaceholderPermission && | ||
requestPermission.permission !== signer.permission)) { | ||
throw new IdentityError(`Unexpected identity proof from ${formatAuth(signer)}, expected ${formatAuth(requestPermission)} `); | ||
} | ||
} | ||
return { | ||
...res, | ||
account, | ||
signerKey, | ||
}; | ||
} | ||
}; | ||
/** | ||
* Login and create a persistent session. | ||
* @param identifier The session identifier, an EOSIO name (`[a-z1-5]{1,12}`). | ||
* Should be set to the contract account applicable. | ||
*/ | ||
async login(sessionName) { | ||
const privateKey = await ecc.randomKey(); | ||
const requestKey = ecc.privateToPublic(privateKey); | ||
const createInfo = { | ||
session_name: sessionName, | ||
request_key: requestKey, | ||
}; | ||
const res = await this.identify(undefined, { | ||
link: abiEncode(createInfo, 'link_create'), | ||
Link.prototype.login = function (identifier) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var privateKey, requestKey, createInfo, res, metadata, session; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, ecc.randomKey()]; | ||
case 1: | ||
privateKey = _a.sent(); | ||
requestKey = ecc.privateToPublic(privateKey); | ||
createInfo = { | ||
session_name: identifier, | ||
request_key: requestKey, | ||
}; | ||
return [4 /*yield*/, this.identify(undefined, { | ||
link: abiEncode(createInfo, 'link_create'), | ||
})]; | ||
case 2: | ||
res = _a.sent(); | ||
metadata = { sameDevice: res.request.getRawInfo()['return_path'] !== undefined }; | ||
if (res.payload.link_ch && res.payload.link_key && res.payload.link_name) { | ||
session = new LinkChannelSession(this, { | ||
auth: res.signer, | ||
publicKey: res.signerKey, | ||
channel: { | ||
url: res.payload.link_ch, | ||
key: res.payload.link_key, | ||
name: res.payload.link_name, | ||
}, | ||
requestKey: privateKey, | ||
}, metadata); | ||
} | ||
else { | ||
session = new LinkFallbackSession(this, { | ||
auth: res.signer, | ||
publicKey: res.signerKey, | ||
}, metadata); | ||
} | ||
return [2 /*return*/, __assign(__assign({}, res), { session: session })]; | ||
} | ||
}); | ||
}); | ||
const metadata = { sameDevice: res.request.getRawInfo()['return_path'] !== undefined }; | ||
let session; | ||
if (res.payload.link_ch && res.payload.link_key && res.payload.link_name) { | ||
session = new LinkChannelSession(this, { | ||
auth: res.signer, | ||
publicKey: res.signerKey, | ||
channel: { | ||
url: res.payload.link_ch, | ||
key: res.payload.link_key, | ||
name: res.payload.link_name, | ||
}, | ||
requestKey: privateKey, | ||
}, metadata); | ||
} | ||
else { | ||
session = new LinkFallbackSession(this, { | ||
auth: res.signer, | ||
publicKey: res.signerKey, | ||
}, metadata); | ||
} | ||
return { | ||
...res, | ||
session, | ||
}; | ||
} | ||
}; | ||
/** | ||
* Create an eosjs signature provider using this link. | ||
* Note that we don't know what keys are available so those have to be provided. | ||
* Restore previous session, see [[Link.login]] to create a new session. | ||
* | ||
* Example: | ||
* | ||
* ```ts | ||
* let session = await myLink.login('mycontract') | ||
* let data = session.serialize() | ||
* // a little longer than a few moments later... | ||
* let restored = myLink.restore(data) | ||
* let result = await restored.transact({action: myAction}) | ||
* ``` | ||
* | ||
* @param data The serialized session data obtained by calling [[LinkSession.serialize]]. | ||
**/ | ||
Link.prototype.restoreSession = function (data) { | ||
return LinkSession.restore(this, data); | ||
}; | ||
/** | ||
* Create an eosjs compatible signature provider using this link. | ||
* @param availableKeys Keys the created provider will claim to be able to sign for. | ||
* @param transport (internal) Transport override for this call. | ||
* @note We don't know what keys are available so those have to be provided, | ||
* to avoid this use [[LinkSession.makeSignatureProvider]] instead. Sessions can be created with [[Link.login]]. | ||
*/ | ||
makeSignatureProvider(availableKeys, transport) { | ||
Link.prototype.makeSignatureProvider = function (availableKeys, transport) { | ||
var _this = this; | ||
return { | ||
getAvailableKeys: async () => availableKeys, | ||
sign: async (args) => { | ||
const request = esr.SigningRequest.fromTransaction(args.chainId, args.serializedTransaction, this.requestOptions); | ||
request.setCallback(this.createCallbackUrl(), true); | ||
request.setBroadcast(false); | ||
const { signatures } = await this.sendRequest(request, transport); | ||
return { | ||
...args, | ||
signatures, | ||
}; | ||
}, | ||
getAvailableKeys: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { | ||
return [2 /*return*/, availableKeys]; | ||
}); }); }, | ||
sign: function (args) { return __awaiter(_this, void 0, void 0, function () { | ||
var request, signatures; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
request = esr.SigningRequest.fromTransaction(args.chainId, args.serializedTransaction, this.requestOptions); | ||
request.setCallback(this.createCallbackUrl(), true); | ||
request.setBroadcast(false); | ||
return [4 /*yield*/, this.sendRequest(request, transport)]; | ||
case 1: | ||
signatures = (_a.sent()).signatures; | ||
return [2 /*return*/, __assign(__assign({}, args), { signatures: signatures })]; | ||
} | ||
}); | ||
}); }, | ||
}; | ||
} | ||
}; | ||
/** | ||
* Create an eosjs authority provider using this link. | ||
* @note Uses the configured RPC Node's `/v1/chain/get_required_keys` API to resolve keys. | ||
*/ | ||
makeAuthorityProvider() { | ||
const { rpc } = this; | ||
Link.prototype.makeAuthorityProvider = function () { | ||
var rpc = this.rpc; | ||
return { | ||
async getRequiredKeys(args) { | ||
const { availableKeys, transaction } = args; | ||
const result = await rpc.fetch('/v1/chain/get_required_keys', { | ||
transaction, | ||
available_keys: availableKeys.map(normalizePublicKey), | ||
getRequiredKeys: function (args) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var availableKeys, transaction, result; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
availableKeys = args.availableKeys, transaction = args.transaction; | ||
return [4 /*yield*/, rpc.fetch('/v1/chain/get_required_keys', { | ||
transaction: transaction, | ||
available_keys: availableKeys.map(normalizePublicKey), | ||
})]; | ||
case 1: | ||
result = _a.sent(); | ||
return [2 /*return*/, result.required_keys.map(normalizePublicKey)]; | ||
} | ||
}); | ||
}); | ||
return result.required_keys.map(normalizePublicKey); | ||
}, | ||
}; | ||
} | ||
} | ||
}; | ||
return Link; | ||
}()); | ||
/** | ||
* Connect to a WebSocket channel wait for a message. | ||
* @internal | ||
*/ | ||
function waitForCallback(url, ctx) { | ||
return new Promise((resolve, reject) => { | ||
let active = true; | ||
let retries = 0; | ||
const socketUrl = url.replace(/^http/, 'ws'); | ||
const handleResponse = (response) => { | ||
return new Promise(function (resolve, reject) { | ||
var active = true; | ||
var retries = 0; | ||
var socketUrl = url.replace(/^http/, 'ws'); | ||
var handleResponse = function (response) { | ||
try { | ||
@@ -539,5 +812,5 @@ resolve(JSON.parse(response)); | ||
}; | ||
const connect = () => { | ||
const socket = new WebSocket(socketUrl); | ||
ctx.cancel = () => { | ||
var connect = function () { | ||
var socket = new WebSocket(socketUrl); | ||
ctx.cancel = function () { | ||
active = false; | ||
@@ -549,3 +822,3 @@ if (socket.readyState === WebSocket.OPEN || | ||
}; | ||
socket.onmessage = (event) => { | ||
socket.onmessage = function (event) { | ||
active = false; | ||
@@ -556,10 +829,10 @@ if (socket.readyState === WebSocket.OPEN) { | ||
if (typeof Blob !== 'undefined' && event.data instanceof Blob) { | ||
const reader = new FileReader(); | ||
reader.onload = () => { | ||
handleResponse(reader.result); | ||
var reader_1 = new FileReader(); | ||
reader_1.onload = function () { | ||
handleResponse(reader_1.result); | ||
}; | ||
reader.onerror = (error) => { | ||
reader_1.onerror = function (error) { | ||
reject(error); | ||
}; | ||
reader.readAsText(event.data); | ||
reader_1.readAsText(event.data); | ||
} | ||
@@ -575,7 +848,7 @@ else { | ||
}; | ||
socket.onopen = () => { | ||
socket.onopen = function () { | ||
retries = 0; | ||
}; | ||
socket.onerror = (error) => { }; | ||
socket.onclose = (close) => { | ||
socket.onerror = function (error) { }; | ||
socket.onclose = function (close) { | ||
if (active) { | ||
@@ -592,2 +865,3 @@ setTimeout(connect, backoff(retries++)); | ||
* https://i.imgur.com/IrUDcJp.png | ||
* @internal | ||
*/ | ||
@@ -597,5 +871,8 @@ function backoff(tries) { | ||
} | ||
/** Format a EOSIO permission level in the format `actor@permission` taking placeholders into consideration. */ | ||
/** | ||
* Format a EOSIO permission level in the format `actor@permission` taking placeholders into consideration. | ||
* @internal | ||
*/ | ||
function formatAuth(auth) { | ||
let { actor, permission } = auth; | ||
var actor = auth.actor, permission = auth.permission; | ||
if (actor === esr.PlaceholderName) { | ||
@@ -607,3 +884,3 @@ actor = '<any>'; | ||
} | ||
return `${actor}@${permission}`; | ||
return actor + "@" + permission; | ||
} | ||
@@ -618,2 +895,5 @@ | ||
exports.SessionError = SessionError; | ||
exports.default = Link; | ||
var _exports = exports; module.exports = _exports.default; for (var key in _exports) { module.exports[key] = _exports[key] }; | ||
//# sourceMappingURL=index.es5.js.map |
export * from './link'; | ||
export * from './link-session'; | ||
export * from './errors'; | ||
// default export is Link class for convenience | ||
import { Link } from './link'; | ||
export default Link; | ||
//# sourceMappingURL=index.js.map |
import { ChainName } from 'eosio-signing-request'; | ||
import { JsonRpc } from 'eosjs'; | ||
import { LinkTransport } from './link-transport'; | ||
/** | ||
* Available options when creating a new [[Link]] instance. | ||
*/ | ||
export interface LinkOptions { | ||
@@ -33,2 +36,3 @@ /** | ||
} | ||
/** @internal */ | ||
export declare const defaults: { | ||
@@ -35,0 +39,0 @@ chainId: string; |
@@ -0,1 +1,2 @@ | ||
/** @internal */ | ||
export const defaults = { | ||
@@ -2,0 +3,0 @@ chainId: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906', |
import { ApiInterfaces } from 'eosjs'; | ||
import { Link, TransactArgs } from './link'; | ||
import { Link, TransactArgs, TransactResult } from './link'; | ||
import { LinkTransport } from './link-transport'; | ||
@@ -29,7 +29,6 @@ /** | ||
/** | ||
* Transact using this session. | ||
* @see Link#transact | ||
* Transact using this session. See [[Link.transact]]. | ||
*/ | ||
abstract transact(args: TransactArgs): any; | ||
/** Returns a JSON-encodable object that can be passed to the constructor to recreate the session. */ | ||
abstract transact(args: TransactArgs): Promise<TransactResult>; | ||
/** Returns a JSON-encodable object that can be used recreate the session. */ | ||
abstract serialize(): SerializedLinkSession; | ||
@@ -39,3 +38,4 @@ /** Restore a previously serialized session. */ | ||
} | ||
interface SerializedLinkSession { | ||
/** @internal */ | ||
export interface SerializedLinkSession { | ||
type: string; | ||
@@ -55,2 +55,3 @@ metadata: { | ||
} | ||
/** @internal */ | ||
export interface LinkChannelSessionData { | ||
@@ -71,2 +72,3 @@ /** Authenticated user permission. */ | ||
* Link session that pushes requests over a channel. | ||
* @internal | ||
*/ | ||
@@ -92,4 +94,5 @@ export declare class LinkChannelSession extends LinkSession implements LinkTransport { | ||
makeAuthorityProvider(): ApiInterfaces.AuthorityProvider; | ||
transact(args: TransactArgs): Promise<import("./link").TransactResult>; | ||
transact(args: TransactArgs): Promise<TransactResult>; | ||
} | ||
/** @internal */ | ||
export interface LinkFallbackSessionData { | ||
@@ -102,2 +105,6 @@ auth: { | ||
} | ||
/** | ||
* Link session that sends every request over the transport. | ||
* @internal | ||
*/ | ||
export declare class LinkFallbackSession extends LinkSession implements LinkTransport { | ||
@@ -121,4 +128,4 @@ readonly link: Link; | ||
makeAuthorityProvider(): ApiInterfaces.AuthorityProvider; | ||
transact(args: TransactArgs): Promise<import("./link").TransactResult>; | ||
transact(args: TransactArgs): Promise<TransactResult>; | ||
} | ||
export {}; |
@@ -22,2 +22,3 @@ import { SessionError } from './errors'; | ||
* Link session that pushes requests over a channel. | ||
* @internal | ||
*/ | ||
@@ -101,2 +102,6 @@ export class LinkChannelSession extends LinkSession { | ||
} | ||
/** | ||
* Link session that sends every request over the transport. | ||
* @internal | ||
*/ | ||
export class LinkFallbackSession extends LinkSession { | ||
@@ -103,0 +108,0 @@ constructor(link, data, metadata) { |
@@ -17,5 +17,5 @@ import { SigningRequest } from 'eosio-signing-request'; | ||
/** Called if the request was successful. */ | ||
onSuccess?(request: SigningRequest, result: TransactResult): any; | ||
onSuccess?(request: SigningRequest, result: TransactResult): void; | ||
/** Called if the request failed. */ | ||
onFailure?(request: SigningRequest, error: Error): any; | ||
onFailure?(request: SigningRequest, error: Error): void; | ||
/** | ||
@@ -26,3 +26,3 @@ * Called when a session request is initiated. | ||
*/ | ||
onSessionRequest?(session: LinkSession, request: SigningRequest, cancel: (reason: string | Error) => void): any; | ||
onSessionRequest?(session: LinkSession, request: SigningRequest, cancel: (reason: string | Error) => void): void; | ||
} |
import * as esr from 'eosio-signing-request'; | ||
import { ApiInterfaces, JsonRpc } from 'eosjs'; | ||
import { LinkOptions } from './link-options'; | ||
import { LinkSession } from './link-session'; | ||
import { LinkSession, SerializedLinkSession } from './link-session'; | ||
import { LinkTransport } from './link-transport'; | ||
/** | ||
* Arguments accepted by the `Link::transact` method. | ||
* Arguments accepted by the [[Link.transact]] method. | ||
* Note that one of `action`, `actions` or `transaction` must be set. | ||
@@ -24,3 +24,3 @@ */ | ||
/** | ||
* The result of a `Link::transact` call. | ||
* The result of a [[Link.transact]] call. | ||
*/ | ||
@@ -45,5 +45,24 @@ export interface TransactResult { | ||
} | ||
/** | ||
* Main class, also exposed as the default export of the library. | ||
* | ||
* Example: | ||
* | ||
* ```ts | ||
* import AnchorLink from 'anchor-link' | ||
* import ConsoleTransport from 'anchor-link-console-transport' | ||
* | ||
* const link = new AnchorLink({ | ||
* transport: new ConsoleTransport() | ||
* }) | ||
* | ||
* const result = await link.transact({actions: myActions, broadcast: true}) | ||
* ``` | ||
*/ | ||
export declare class Link implements esr.AbiProvider { | ||
/** The eosjs RPC instance used to communicate with the EOSIO node. */ | ||
readonly rpc: JsonRpc; | ||
/** Transport used to deliver requests to the user wallet. */ | ||
readonly transport: LinkTransport; | ||
/** EOSIO ChainID for which requests are valid. */ | ||
readonly chainId: string | esr.ChainName; | ||
@@ -53,7 +72,25 @@ private serviceAddress; | ||
private abiCache; | ||
/** Create a new link instance. */ | ||
constructor(options: LinkOptions); | ||
/** | ||
* Fetch the ABI for given account, cached. | ||
* @internal | ||
*/ | ||
getAbi(account: string): Promise<any>; | ||
/** | ||
* Create a new unique buoy callback url. | ||
* @internal | ||
*/ | ||
createCallbackUrl(): string; | ||
/** | ||
* Create a SigningRequest instance configured for this link. | ||
* @internal | ||
*/ | ||
createRequest(args: esr.SigningRequestCreateArguments): Promise<esr.SigningRequest>; | ||
sendRequest(request: esr.SigningRequest, transport?: LinkTransport): Promise<TransactResult>; | ||
/** | ||
* Send a SigningRequest instance using this link. | ||
* @internal | ||
*/ | ||
sendRequest(request: esr.SigningRequest, transport?: LinkTransport, broadcast?: boolean): Promise<TransactResult>; | ||
/** Sign and optionally broadcast a EOSIO transaction, action or actions. */ | ||
transact(args: TransactArgs, transport?: LinkTransport): Promise<TransactResult>; | ||
@@ -89,4 +126,6 @@ /** | ||
* Login and create a persistent session. | ||
* @param identifier The session identifier, an EOSIO name (`[a-z1-5]{1,12}`). | ||
* Should be set to the contract account applicable. | ||
*/ | ||
login(sessionName: string): Promise<{ | ||
login(identifier: string): Promise<{ | ||
session: LinkSession; | ||
@@ -113,4 +152,23 @@ account: any; | ||
/** | ||
* Create an eosjs signature provider using this link. | ||
* Note that we don't know what keys are available so those have to be provided. | ||
* Restore previous session, see [[Link.login]] to create a new session. | ||
* | ||
* Example: | ||
* | ||
* ```ts | ||
* let session = await myLink.login('mycontract') | ||
* let data = session.serialize() | ||
* // a little longer than a few moments later... | ||
* let restored = myLink.restore(data) | ||
* let result = await restored.transact({action: myAction}) | ||
* ``` | ||
* | ||
* @param data The serialized session data obtained by calling [[LinkSession.serialize]]. | ||
**/ | ||
restoreSession(data: SerializedLinkSession): LinkSession; | ||
/** | ||
* Create an eosjs compatible signature provider using this link. | ||
* @param availableKeys Keys the created provider will claim to be able to sign for. | ||
* @param transport (internal) Transport override for this call. | ||
* @note We don't know what keys are available so those have to be provided, | ||
* to avoid this use [[LinkSession.makeSignatureProvider]] instead. Sessions can be created with [[Link.login]]. | ||
*/ | ||
@@ -120,4 +178,5 @@ makeSignatureProvider(availableKeys: string[], transport?: LinkTransport): ApiInterfaces.SignatureProvider; | ||
* Create an eosjs authority provider using this link. | ||
* @note Uses the configured RPC Node's `/v1/chain/get_required_keys` API to resolve keys. | ||
*/ | ||
makeAuthorityProvider(): ApiInterfaces.AuthorityProvider; | ||
} |
108
lib/link.js
@@ -10,8 +10,32 @@ import * as esr from 'eosio-signing-request'; | ||
import { defaults } from './link-options'; | ||
import { LinkChannelSession, LinkFallbackSession } from './link-session'; | ||
import { LinkChannelSession, LinkFallbackSession, LinkSession, } from './link-session'; | ||
import { abiEncode, normalizePublicKey, publicKeyEqual } from './utils'; | ||
const { fetch } = makeFetch(); | ||
/** @internal */ | ||
const fetch = makeFetch().fetch; | ||
/** | ||
* Main class, also exposed as the default export of the library. | ||
* | ||
* Example: | ||
* | ||
* ```ts | ||
* import AnchorLink from 'anchor-link' | ||
* import ConsoleTransport from 'anchor-link-console-transport' | ||
* | ||
* const link = new AnchorLink({ | ||
* transport: new ConsoleTransport() | ||
* }) | ||
* | ||
* const result = await link.transact({actions: myActions, broadcast: true}) | ||
* ``` | ||
*/ | ||
export class Link { | ||
/** Create a new link instance. */ | ||
constructor(options) { | ||
this.abiCache = new Map(); | ||
if (typeof options !== 'object') { | ||
throw new TypeError('Missing options object'); | ||
} | ||
if (!options.transport) { | ||
throw new TypeError('options.transport is required, see https://github.com/greymass/anchor-link#transports'); | ||
} | ||
if (options.rpc === undefined || typeof options.rpc === 'string') { | ||
@@ -33,2 +57,6 @@ this.rpc = new JsonRpc(options.rpc || defaults.rpc, { fetch: fetch }); | ||
} | ||
/** | ||
* Fetch the ABI for given account, cached. | ||
* @internal | ||
*/ | ||
async getAbi(account) { | ||
@@ -44,5 +72,13 @@ let rv = this.abiCache.get(account); | ||
} | ||
/** | ||
* Create a new unique buoy callback url. | ||
* @internal | ||
*/ | ||
createCallbackUrl() { | ||
return `${this.serviceAddress}/${uuid()}`; | ||
} | ||
/** | ||
* Create a SigningRequest instance configured for this link. | ||
* @internal | ||
*/ | ||
async createRequest(args) { | ||
@@ -61,3 +97,7 @@ // generate unique callback url | ||
} | ||
async sendRequest(request, transport) { | ||
/** | ||
* Send a SigningRequest instance using this link. | ||
* @internal | ||
*/ | ||
async sendRequest(request, transport, broadcast = false) { | ||
const t = transport || this.transport; | ||
@@ -107,2 +147,9 @@ try { | ||
}; | ||
if (broadcast) { | ||
const res = await this.rpc.push_transaction({ | ||
signatures: result.signatures, | ||
serializedTransaction: result.serializedTransaction, | ||
}); | ||
result.processed = res.processed; | ||
} | ||
if (t.onSuccess) { | ||
@@ -120,15 +167,8 @@ t.onSuccess(request, result); | ||
} | ||
/** Sign and optionally broadcast a EOSIO transaction, action or actions. */ | ||
async transact(args, transport) { | ||
const t = transport || this.transport; | ||
const broadcast = args.broadcast || false; | ||
const request = await this.createRequest(args); | ||
const result = await this.sendRequest(request, t); | ||
// broadcast transaction if requested | ||
const broadcast = args.broadcast || false; | ||
if (broadcast) { | ||
const res = await this.rpc.push_transaction({ | ||
signatures: result.signatures, | ||
serializedTransaction: result.serializedTransaction, | ||
}); | ||
result.processed = res.processed; | ||
} | ||
const result = await this.sendRequest(request, t, broadcast); | ||
return result; | ||
@@ -189,8 +229,10 @@ } | ||
* Login and create a persistent session. | ||
* @param identifier The session identifier, an EOSIO name (`[a-z1-5]{1,12}`). | ||
* Should be set to the contract account applicable. | ||
*/ | ||
async login(sessionName) { | ||
async login(identifier) { | ||
const privateKey = await ecc.randomKey(); | ||
const requestKey = ecc.privateToPublic(privateKey); | ||
const createInfo = { | ||
session_name: sessionName, | ||
session_name: identifier, | ||
request_key: requestKey, | ||
@@ -227,4 +269,25 @@ }; | ||
/** | ||
* Create an eosjs signature provider using this link. | ||
* Note that we don't know what keys are available so those have to be provided. | ||
* Restore previous session, see [[Link.login]] to create a new session. | ||
* | ||
* Example: | ||
* | ||
* ```ts | ||
* let session = await myLink.login('mycontract') | ||
* let data = session.serialize() | ||
* // a little longer than a few moments later... | ||
* let restored = myLink.restore(data) | ||
* let result = await restored.transact({action: myAction}) | ||
* ``` | ||
* | ||
* @param data The serialized session data obtained by calling [[LinkSession.serialize]]. | ||
**/ | ||
restoreSession(data) { | ||
return LinkSession.restore(this, data); | ||
} | ||
/** | ||
* Create an eosjs compatible signature provider using this link. | ||
* @param availableKeys Keys the created provider will claim to be able to sign for. | ||
* @param transport (internal) Transport override for this call. | ||
* @note We don't know what keys are available so those have to be provided, | ||
* to avoid this use [[LinkSession.makeSignatureProvider]] instead. Sessions can be created with [[Link.login]]. | ||
*/ | ||
@@ -248,2 +311,3 @@ makeSignatureProvider(availableKeys, transport) { | ||
* Create an eosjs authority provider using this link. | ||
* @note Uses the configured RPC Node's `/v1/chain/get_required_keys` API to resolve keys. | ||
*/ | ||
@@ -264,2 +328,6 @@ makeAuthorityProvider() { | ||
} | ||
/** | ||
* Connect to a WebSocket channel wait for a message. | ||
* @internal | ||
*/ | ||
function waitForCallback(url, ctx) { | ||
@@ -328,2 +396,3 @@ return new Promise((resolve, reject) => { | ||
* https://i.imgur.com/IrUDcJp.png | ||
* @internal | ||
*/ | ||
@@ -333,3 +402,6 @@ function backoff(tries) { | ||
} | ||
/** Format a EOSIO permission level in the format `actor@permission` taking placeholders into consideration. */ | ||
/** | ||
* Format a EOSIO permission level in the format `actor@permission` taking placeholders into consideration. | ||
* @internal | ||
*/ | ||
function formatAuth(auth) { | ||
@@ -336,0 +408,0 @@ let { actor, permission } = auth; |
import { Bytes } from './link-abi'; | ||
/** | ||
* Helper to ABI encode value. | ||
* @internal | ||
*/ | ||
export declare function abiEncode(value: any, typeName: string): Uint8Array; | ||
/** | ||
* Helper to ABI decode data. | ||
* @internal | ||
*/ | ||
export declare function abiDecode<ResultType = any>(data: Bytes, typeName: string): ResultType; | ||
/** | ||
* Encrypt a message using AES and shared secret derived from given keys. | ||
* @internal | ||
*/ | ||
export declare function sealMessage(message: string, privateKey: string, publicKey: string): Uint8Array; | ||
/** Ensure public key is in new PUB_ format. */ | ||
/** | ||
* Ensure public key is in new PUB_ format. | ||
* @internal | ||
*/ | ||
export declare function normalizePublicKey(key: string): string; | ||
/** Return true if given public keys are equal. */ | ||
/** | ||
* Return true if given public keys are equal. | ||
* @internal | ||
*/ | ||
export declare function publicKeyEqual(keyA: string, keyB: string): boolean; |
import { Numeric, Serialize } from 'eosjs'; | ||
import * as ecc from 'eosjs-ecc'; | ||
import linkAbi from './link-abi-data'; | ||
/** @internal */ | ||
const types = Serialize.getTypesFromAbi(Serialize.createInitialTypes(), linkAbi); | ||
/** | ||
* Helper to ABI encode value. | ||
* @internal | ||
*/ | ||
export function abiEncode(value, typeName) { | ||
@@ -14,2 +19,6 @@ let type = types.get(typeName); | ||
} | ||
/** | ||
* Helper to ABI decode data. | ||
* @internal | ||
*/ | ||
export function abiDecode(data, typeName) { | ||
@@ -31,2 +40,6 @@ let type = types.get(typeName); | ||
} | ||
/** | ||
* Encrypt a message using AES and shared secret derived from given keys. | ||
* @internal | ||
*/ | ||
export function sealMessage(message, privateKey, publicKey) { | ||
@@ -42,3 +55,6 @@ const res = ecc.Aes.encrypt(privateKey, publicKey, message); | ||
} | ||
/** Ensure public key is in new PUB_ format. */ | ||
/** | ||
* Ensure public key is in new PUB_ format. | ||
* @internal | ||
*/ | ||
export function normalizePublicKey(key) { | ||
@@ -50,3 +66,6 @@ if (key.startsWith('PUB_')) { | ||
} | ||
/** Return true if given public keys are equal. */ | ||
/** | ||
* Return true if given public keys are equal. | ||
* @internal | ||
*/ | ||
export function publicKeyEqual(keyA, keyB) { | ||
@@ -53,0 +72,0 @@ return normalizePublicKey(keyA) === normalizePublicKey(keyB); |
{ | ||
"name": "anchor-link", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"description": "Library for authenticating and signing transactions using the Anchor Link protocol", | ||
@@ -51,2 +51,3 @@ "license": "MIT", | ||
"eosio-abi2ts": "^1.2.2", | ||
"gh-pages": "^2.2.0", | ||
"prettier": "^2.0.2", | ||
@@ -61,4 +62,5 @@ "rollup": "^2.3.2", | ||
"tslint-plugin-prettier": "^2.3.0", | ||
"typedoc": "^0.17.4", | ||
"typescript": "^3.8.3" | ||
} | ||
} |
102
README.md
@@ -1,3 +0,103 @@ | ||
# anchor-link | ||
# Anchor Link [](https://www.npmjs.com/package/anchor-link)  | ||
Persistent, fast and secure signature provider for EOSIO chains built on top of [EOSIO Signing Requests (EEP-7)](https://github.com/greymass/eosio-signing-request) | ||
Key features: | ||
- End to end encryption | ||
- Cross device signing | ||
- Persistent sessions | ||
- Open standard | ||
Take it for a spin: [Anchor Link Demo](#todo) | ||
Resources: | ||
- [API Documentation](https://greymass.github.io/anchor-link) | ||
- [Protocol specification](#todo) | ||
- [Usage examples](./examples) | ||
- [Developer chat](#todo) | ||
## Protocol | ||
The Anchor Link protocol uses EEP-7 identity requests to establish a channel to compatible wallets using an untrusted HTTP POST to WebSocket forwarder (see [buoy node.js](https://github.com/greymass/buoy-nodejs) and [buoy golang](https://github.com/greymass/buoy-golang)). | ||
A session key and unique channel URL is generated by the client which is attached to the identity request and sent to the wallet (see [transports](#transports)). The wallet signs the identity proof and sends it back along with its own channel URL and session key. Subsequent signature requests can now be encrypted to a shared secret derived from the two keys and pushed directly to the wallet channel. | ||
[📘 Full Protocol specification](./docs/protocol.md) | ||
## Installation | ||
The `anchor-link` package is distributed both as a module on [npm](https://www.npmjs.com/package/anchor-link) and a standalone bundle on [unpkg](http://unpkg.com/anchor-link). | ||
### Browser using a bundler (recommended) | ||
Install Anchor Link and a [transport](#transports): | ||
``` | ||
yarn add anchor-link anchor-link-browser-transport | ||
# or | ||
npm install --save anchor-link anchor-link-browser-transport | ||
``` | ||
Import them into your project: | ||
```js | ||
import AnchorLink from 'anchor-link' | ||
import AnchorLinkBrowserTransport from 'anchor-link-browser-transport' | ||
``` | ||
Jump to [basic usage](#basic-usage). | ||
### Browser using a pre-built bundle | ||
Include the scripts in your `<head>` tag. | ||
```html | ||
<script src="https://unpkg.com/anchor-link"></script> | ||
<script src="https://unpkg.com/anchor-link-browser-transport"></script> | ||
``` | ||
`AnchorLink` and `AnchorLinkBrowserTransport` are now available in the global scope of your document. | ||
### Using node.js | ||
Using node.js or bundler (e.g. webpack) **recommended** | ||
``` | ||
yarn add anchor-link | ||
``` | ||
## Transports | ||
Transports in Anchor Link are responsible for getting signature requests to the users wallet when establishing a session or when using anchor link without logging in. | ||
Available transports: | ||
Package | Description | ||
---------| --------------- | ||
[anchor-link-browser-transport](https://github.com/greymass/anchor-link-browser-transport) | Browser overlay that generates QR codes or triggers local URI handler if available | ||
[anchor-link-console-transport](https://github.com/greymass/anchor-link-console-transport) | Transport that prints ASCII QR codes and esr:// links to the JavaScript console | ||
See the [`LinkTransport` documentation](https://greymass.github.io/anchor-link/interfaces/linktransport.html) for details on how to implement custom transports. | ||
## Installation | ||
Take it for a spin | ||
Download [Anchor](https://greymass.com/en/projects) | ||
Persistent signing sessions for [Anchor]* | ||
Persistent Session library for ESR | ||
EOSIO | ||
Example usage: | ||
@@ -4,0 +104,0 @@ |
@@ -1,2 +0,14 @@ | ||
/** Error that is thrown if a LinkPresenter calls the cancel callback. */ | ||
/** | ||
* Error codes. Accessible using the `code` property on errors thrown by [[Link]] and [[LinkSession]]. | ||
* - `E_DELIVERY`: Unable to request message to wallet. | ||
* - `E_TIMEOUT`: Request was delivered but user/wallet didn't respond in time. | ||
* - `E_CANCEL`: The [[LinkTransport]] canceled the request. | ||
* - `E_IDENTITY`: Identity proof failed to verify. | ||
*/ | ||
export type LinkErrorCode = 'E_DELIVERY' | 'E_TIMEOUT' | 'E_CANCEL' | 'E_IDENTITY' | ||
/** | ||
* Error that is thrown if a [[LinkTransport]] cancels a request. | ||
* @internal | ||
*/ | ||
export class CancelError extends Error { | ||
@@ -9,3 +21,6 @@ public code = 'E_CANCEL' | ||
/** Error that is thrown if an identity request fails to verify. */ | ||
/** | ||
* Error that is thrown if an identity request fails to verify. | ||
* @internal | ||
*/ | ||
export class IdentityError extends Error { | ||
@@ -19,12 +34,8 @@ public code = 'E_IDENTITY' | ||
/** | ||
* Session error codes. | ||
* - E_DELIVERY: Unable to request message to wallet. | ||
* - E_TIMEOUT: Request was delivered but user/wallet didn't respond in time. | ||
* Error originating from a [[LinkSession]]. | ||
* @internal | ||
*/ | ||
export type SessionErrorCode = 'E_DELIVERY' | 'E_TIMEOUT' | ||
/** Error that is thrown by session transport. */ | ||
export class SessionError extends Error { | ||
public code: SessionErrorCode | ||
constructor(reason: string, code: SessionErrorCode) { | ||
public code: 'E_DELIVERY' | 'E_TIMEOUT' | ||
constructor(reason: string, code: 'E_DELIVERY' | 'E_TIMEOUT') { | ||
super(reason) | ||
@@ -31,0 +42,0 @@ this.code = code |
@@ -6,1 +6,5 @@ export * from './link' | ||
export * from './errors' | ||
// default export is Link class for convenience | ||
import {Link} from './link' | ||
export default Link |
@@ -5,2 +5,5 @@ import {ChainName} from 'eosio-signing-request' | ||
/** | ||
* Available options when creating a new [[Link]] instance. | ||
*/ | ||
export interface LinkOptions { | ||
@@ -36,2 +39,3 @@ /** | ||
/** @internal */ | ||
export const defaults = { | ||
@@ -38,0 +42,0 @@ chainId: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906', |
@@ -5,3 +5,3 @@ import {SigningRequest} from 'eosio-signing-request' | ||
import {SessionError} from './errors' | ||
import {Link, TransactArgs} from './link' | ||
import {Link, TransactArgs, TransactResult} from './link' | ||
import {LinkInfo} from './link-abi' | ||
@@ -34,7 +34,6 @@ import {LinkTransport} from './link-transport' | ||
/** | ||
* Transact using this session. | ||
* @see Link#transact | ||
* Transact using this session. See [[Link.transact]]. | ||
*/ | ||
abstract transact(args: TransactArgs) | ||
/** Returns a JSON-encodable object that can be passed to the constructor to recreate the session. */ | ||
abstract transact(args: TransactArgs): Promise<TransactResult> | ||
/** Returns a JSON-encodable object that can be used recreate the session. */ | ||
abstract serialize(): SerializedLinkSession | ||
@@ -54,3 +53,4 @@ /** Restore a previously serialized session. */ | ||
interface SerializedLinkSession { | ||
/** @internal */ | ||
export interface SerializedLinkSession { | ||
type: string | ||
@@ -70,2 +70,3 @@ metadata: {[key: string]: any} | ||
/** @internal */ | ||
export interface LinkChannelSessionData { | ||
@@ -87,2 +88,3 @@ /** Authenticated user permission. */ | ||
* Link session that pushes requests over a channel. | ||
* @internal | ||
*/ | ||
@@ -188,2 +190,3 @@ export class LinkChannelSession extends LinkSession implements LinkTransport { | ||
/** @internal */ | ||
export interface LinkFallbackSessionData { | ||
@@ -197,2 +200,6 @@ auth: { | ||
/** | ||
* Link session that sends every request over the transport. | ||
* @internal | ||
*/ | ||
export class LinkFallbackSession extends LinkSession implements LinkTransport { | ||
@@ -199,0 +206,0 @@ readonly link: Link |
@@ -19,5 +19,5 @@ import {SigningRequest} from 'eosio-signing-request' | ||
/** Called if the request was successful. */ | ||
onSuccess?(request: SigningRequest, result: TransactResult) | ||
onSuccess?(request: SigningRequest, result: TransactResult): void | ||
/** Called if the request failed. */ | ||
onFailure?(request: SigningRequest, error: Error) | ||
onFailure?(request: SigningRequest, error: Error): void | ||
/** | ||
@@ -32,3 +32,3 @@ * Called when a session request is initiated. | ||
cancel: (reason: string | Error) => void | ||
) | ||
): void | ||
} |
127
src/link.ts
@@ -12,10 +12,16 @@ import * as esr from 'eosio-signing-request' | ||
import {defaults, LinkOptions} from './link-options' | ||
import {LinkChannelSession, LinkFallbackSession, LinkSession} from './link-session' | ||
import { | ||
LinkChannelSession, | ||
LinkFallbackSession, | ||
LinkSession, | ||
SerializedLinkSession, | ||
} from './link-session' | ||
import {LinkTransport} from './link-transport' | ||
import {abiEncode, normalizePublicKey, publicKeyEqual} from './utils' | ||
const {fetch} = makeFetch() | ||
/** @internal */ | ||
const fetch = makeFetch().fetch | ||
/** | ||
* Arguments accepted by the `Link::transact` method. | ||
* Arguments accepted by the [[Link.transact]] method. | ||
* Note that one of `action`, `actions` or `transaction` must be set. | ||
@@ -38,3 +44,3 @@ */ | ||
/** | ||
* The result of a `Link::transact` call. | ||
* The result of a [[Link.transact]] call. | ||
*/ | ||
@@ -58,5 +64,24 @@ export interface TransactResult { | ||
/** | ||
* Main class, also exposed as the default export of the library. | ||
* | ||
* Example: | ||
* | ||
* ```ts | ||
* import AnchorLink from 'anchor-link' | ||
* import ConsoleTransport from 'anchor-link-console-transport' | ||
* | ||
* const link = new AnchorLink({ | ||
* transport: new ConsoleTransport() | ||
* }) | ||
* | ||
* const result = await link.transact({actions: myActions, broadcast: true}) | ||
* ``` | ||
*/ | ||
export class Link implements esr.AbiProvider { | ||
/** The eosjs RPC instance used to communicate with the EOSIO node. */ | ||
public readonly rpc: JsonRpc | ||
/** Transport used to deliver requests to the user wallet. */ | ||
public readonly transport: LinkTransport | ||
/** EOSIO ChainID for which requests are valid. */ | ||
public readonly chainId: string | esr.ChainName | ||
@@ -68,3 +93,12 @@ | ||
/** Create a new link instance. */ | ||
constructor(options: LinkOptions) { | ||
if (typeof options !== 'object') { | ||
throw new TypeError('Missing options object') | ||
} | ||
if (!options.transport) { | ||
throw new TypeError( | ||
'options.transport is required, see https://github.com/greymass/anchor-link#transports' | ||
) | ||
} | ||
if (options.rpc === undefined || typeof options.rpc === 'string') { | ||
@@ -86,2 +120,6 @@ this.rpc = new JsonRpc(options.rpc || defaults.rpc, {fetch: fetch as any}) | ||
/** | ||
* Fetch the ABI for given account, cached. | ||
* @internal | ||
*/ | ||
public async getAbi(account: string) { | ||
@@ -98,2 +136,6 @@ let rv = this.abiCache.get(account) | ||
/** | ||
* Create a new unique buoy callback url. | ||
* @internal | ||
*/ | ||
public createCallbackUrl() { | ||
@@ -103,2 +145,6 @@ return `${this.serviceAddress}/${uuid()}` | ||
/** | ||
* Create a SigningRequest instance configured for this link. | ||
* @internal | ||
*/ | ||
public async createRequest(args: esr.SigningRequestCreateArguments) { | ||
@@ -121,3 +167,11 @@ // generate unique callback url | ||
public async sendRequest(request: esr.SigningRequest, transport?: LinkTransport) { | ||
/** | ||
* Send a SigningRequest instance using this link. | ||
* @internal | ||
*/ | ||
public async sendRequest( | ||
request: esr.SigningRequest, | ||
transport?: LinkTransport, | ||
broadcast = false | ||
) { | ||
const t = transport || this.transport | ||
@@ -169,2 +223,9 @@ try { | ||
} | ||
if (broadcast) { | ||
const res = await this.rpc.push_transaction({ | ||
signatures: result.signatures, | ||
serializedTransaction: result.serializedTransaction, | ||
}) | ||
result.processed = res.processed | ||
} | ||
if (t.onSuccess) { | ||
@@ -182,15 +243,8 @@ t.onSuccess(request, result) | ||
/** Sign and optionally broadcast a EOSIO transaction, action or actions. */ | ||
public async transact(args: TransactArgs, transport?: LinkTransport): Promise<TransactResult> { | ||
const t = transport || this.transport | ||
const broadcast = args.broadcast || false | ||
const request = await this.createRequest(args) | ||
const result = await this.sendRequest(request, t) | ||
// broadcast transaction if requested | ||
const broadcast = args.broadcast || false | ||
if (broadcast) { | ||
const res = await this.rpc.push_transaction({ | ||
signatures: result.signatures, | ||
serializedTransaction: result.serializedTransaction, | ||
}) | ||
result.processed = res.processed | ||
} | ||
const result = await this.sendRequest(request, t, broadcast) | ||
return result | ||
@@ -266,8 +320,10 @@ } | ||
* Login and create a persistent session. | ||
* @param identifier The session identifier, an EOSIO name (`[a-z1-5]{1,12}`). | ||
* Should be set to the contract account applicable. | ||
*/ | ||
public async login(sessionName: string) { | ||
public async login(identifier: string) { | ||
const privateKey = await ecc.randomKey() | ||
const requestKey = ecc.privateToPublic(privateKey) | ||
const createInfo: LinkCreate = { | ||
session_name: sessionName, | ||
session_name: identifier, | ||
request_key: requestKey, | ||
@@ -312,4 +368,26 @@ } | ||
/** | ||
* Create an eosjs signature provider using this link. | ||
* Note that we don't know what keys are available so those have to be provided. | ||
* Restore previous session, see [[Link.login]] to create a new session. | ||
* | ||
* Example: | ||
* | ||
* ```ts | ||
* let session = await myLink.login('mycontract') | ||
* let data = session.serialize() | ||
* // a little longer than a few moments later... | ||
* let restored = myLink.restore(data) | ||
* let result = await restored.transact({action: myAction}) | ||
* ``` | ||
* | ||
* @param data The serialized session data obtained by calling [[LinkSession.serialize]]. | ||
**/ | ||
public restoreSession(data: SerializedLinkSession) { | ||
return LinkSession.restore(this, data) | ||
} | ||
/** | ||
* Create an eosjs compatible signature provider using this link. | ||
* @param availableKeys Keys the created provider will claim to be able to sign for. | ||
* @param transport (internal) Transport override for this call. | ||
* @note We don't know what keys are available so those have to be provided, | ||
* to avoid this use [[LinkSession.makeSignatureProvider]] instead. Sessions can be created with [[Link.login]]. | ||
*/ | ||
@@ -341,2 +419,3 @@ public makeSignatureProvider( | ||
* Create an eosjs authority provider using this link. | ||
* @note Uses the configured RPC Node's `/v1/chain/get_required_keys` API to resolve keys. | ||
*/ | ||
@@ -358,2 +437,6 @@ public makeAuthorityProvider(): ApiInterfaces.AuthorityProvider { | ||
/** | ||
* Connect to a WebSocket channel wait for a message. | ||
* @internal | ||
*/ | ||
function waitForCallback(url: string, ctx: {cancel?: () => void}) { | ||
@@ -422,2 +505,3 @@ return new Promise<esr.CallbackPayload>((resolve, reject) => { | ||
* https://i.imgur.com/IrUDcJp.png | ||
* @internal | ||
*/ | ||
@@ -428,3 +512,6 @@ function backoff(tries: number): number { | ||
/** Format a EOSIO permission level in the format `actor@permission` taking placeholders into consideration. */ | ||
/** | ||
* Format a EOSIO permission level in the format `actor@permission` taking placeholders into consideration. | ||
* @internal | ||
*/ | ||
function formatAuth(auth: esr.abi.PermissionLevel): string { | ||
@@ -431,0 +518,0 @@ let {actor, permission} = auth |
@@ -7,4 +7,9 @@ import {Numeric, Serialize} from 'eosjs' | ||
/** @internal */ | ||
const types = Serialize.getTypesFromAbi(Serialize.createInitialTypes(), linkAbi) | ||
/** | ||
* Helper to ABI encode value. | ||
* @internal | ||
*/ | ||
export function abiEncode(value: any, typeName: string): Uint8Array { | ||
@@ -20,2 +25,6 @@ let type = types.get(typeName) | ||
/** | ||
* Helper to ABI decode data. | ||
* @internal | ||
*/ | ||
export function abiDecode<ResultType = any>(data: Bytes, typeName: string): ResultType { | ||
@@ -37,2 +46,6 @@ let type = types.get(typeName) | ||
/** | ||
* Encrypt a message using AES and shared secret derived from given keys. | ||
* @internal | ||
*/ | ||
export function sealMessage(message: string, privateKey: string, publicKey: string) { | ||
@@ -49,3 +62,6 @@ const res = ecc.Aes.encrypt(privateKey, publicKey, message) | ||
/** Ensure public key is in new PUB_ format. */ | ||
/** | ||
* Ensure public key is in new PUB_ format. | ||
* @internal | ||
*/ | ||
export function normalizePublicKey(key: string) { | ||
@@ -58,5 +74,8 @@ if (key.startsWith('PUB_')) { | ||
/** Return true if given public keys are equal. */ | ||
/** | ||
* Return true if given public keys are equal. | ||
* @internal | ||
*/ | ||
export function publicKeyEqual(keyA: string, keyB: string) { | ||
return normalizePublicKey(keyA) === normalizePublicKey(keyB) | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
1851804
4334
140
21
1