anchor-link
Advanced tools
Comparing version 0.1.0 to 0.2.0
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
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); | ||
}; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** 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'; | ||
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)); | ||
exports.CancelError = CancelError; | ||
/** 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 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)); | ||
exports.IdentityError = IdentityError; | ||
/** Error that is thrown by session transport. */ | ||
class SessionError extends Error { | ||
constructor(reason, code) { | ||
super(reason); | ||
this.code = code; | ||
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)); | ||
exports.SessionError = SessionError; | ||
//# sourceMappingURL=errors.js.map |
@@ -9,1 +9,2 @@ "use strict"; | ||
__export(require("./errors")); | ||
//# sourceMappingURL=index.js.map |
@@ -6,2 +6,6 @@ import { ChainName } from 'eosio-signing-request'; | ||
/** | ||
* Link transport responsible for presenting signing requests to user, required. | ||
*/ | ||
transport: LinkTransport; | ||
/** | ||
* ChainID or esr chain name alias for which the link is valid. | ||
@@ -17,3 +21,3 @@ * Defaults to EOS (aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906). | ||
/** | ||
* URL to link service. | ||
* URL to link callback service. | ||
* Defaults to https://cb.anchor.link. | ||
@@ -23,6 +27,2 @@ */ | ||
/** | ||
* Link transport, defaults to a console transport if omitted. | ||
*/ | ||
transport?: LinkTransport; | ||
/** | ||
* Text encoder, only needed in old browsers or if used in node.js versions prior to v13. | ||
@@ -29,0 +29,0 @@ */ |
@@ -8,1 +8,2 @@ "use strict"; | ||
}; | ||
//# sourceMappingURL=link-options.js.map |
@@ -1,6 +0,39 @@ | ||
/// <reference types="node" /> | ||
import { ApiInterfaces } from 'eosjs'; | ||
import { EventEmitter } from 'events'; | ||
import { Link, TransactArgs } from './link'; | ||
export interface LinkSessionData { | ||
import { LinkTransport } from './link-transport'; | ||
/** | ||
* Type describing a link session that can create a eosjs compatible | ||
* signature provider and transact for a specific auth. | ||
*/ | ||
export declare abstract class LinkSession { | ||
/** The underlying link instance used by the session. */ | ||
abstract link: Link; | ||
/** The public key the session can sign for. */ | ||
abstract publicKey: string; | ||
/** The EOSIO auth (a.k.a. permission level) the session can sign for. */ | ||
abstract auth: { | ||
actor: string; | ||
permission: string; | ||
}; | ||
/** Creates a eosjs compatible signature provider that can sign for the session public key. */ | ||
abstract makeSignatureProvider(): ApiInterfaces.SignatureProvider; | ||
/** | ||
* 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 serialize(): any; | ||
/** Restore a previously serialized session. */ | ||
static restore(link: Link, data: any): LinkSession; | ||
} | ||
interface ChannelInfo { | ||
/** Public key requests are encrypted to. */ | ||
key: string; | ||
/** The wallet given channel name, usually the device name. */ | ||
name: string; | ||
/** The channel push url. */ | ||
url: string; | ||
} | ||
export interface LinkChannelSessionData { | ||
/** Authenticated user permission. */ | ||
@@ -13,15 +46,11 @@ auth: { | ||
publicKey: string; | ||
/** The Wallet channel. */ | ||
channel: string; | ||
/** The public key used to encrypt requests. */ | ||
channelKey: string; | ||
/** The sequence number the channel is on. */ | ||
channelSequence: number; | ||
/** The private key used to sign requests */ | ||
privateKey: string; | ||
/** The wallet channel url. */ | ||
channel: ChannelInfo; | ||
/** The private request key. */ | ||
requestKey: string; | ||
} | ||
/** | ||
* Link session, emits 'info' event when sequence number advances. | ||
* Link session that pushes requests over a channel. | ||
*/ | ||
export declare class LinkSession extends EventEmitter { | ||
export declare class LinkChannelSession extends LinkSession implements LinkTransport { | ||
readonly link: Link; | ||
@@ -33,8 +62,32 @@ readonly auth: { | ||
readonly publicKey: string; | ||
private transport; | ||
private exporter; | ||
constructor(link: Link, data: LinkSessionData); | ||
export(): LinkSessionData; | ||
serialize: () => LinkChannelSessionData; | ||
private channel; | ||
private timeout; | ||
private encrypt; | ||
constructor(link: Link, data: LinkChannelSessionData); | ||
onSuccess(request: any, result: any): void; | ||
onFailure(request: any, error: any): void; | ||
onRequest(request: any, cancel: any): void; | ||
makeSignatureProvider(): ApiInterfaces.SignatureProvider; | ||
transact(args: TransactArgs): Promise<import("./link").TransactResult>; | ||
} | ||
export interface LinkFallbackSessionData { | ||
auth: { | ||
actor: string; | ||
permission: string; | ||
}; | ||
publicKey: string; | ||
} | ||
export declare class LinkFallbackSession extends LinkSession { | ||
readonly link: Link; | ||
readonly auth: { | ||
actor: string; | ||
permission: string; | ||
}; | ||
readonly publicKey: string; | ||
serialize: () => LinkFallbackSessionData; | ||
constructor(link: Link, data: LinkFallbackSessionData); | ||
transact(args: TransactArgs): Promise<import("./link").TransactResult>; | ||
makeSignatureProvider(): ApiInterfaces.SignatureProvider; | ||
} | ||
export {}; |
"use strict"; | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||
result["default"] = mod; | ||
return result; | ||
var __extends = (this && this.__extends) || (function () { | ||
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); | ||
}; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(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); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const ecc = __importStar(require("eosjs-ecc")); | ||
const events_1 = require("events"); | ||
const errors_1 = require("./errors"); | ||
const utils_1 = require("./utils"); | ||
function sealMessage(message, privateKey, publicKey) { | ||
const res = ecc.Aes.encrypt(privateKey, publicKey, message); | ||
const data = { | ||
from: ecc.privateToPublic(privateKey), | ||
nonce: res.nonce.toString(), | ||
ciphertext: res.message, | ||
checksum: res.checksum, | ||
var errors_1 = require("./errors"); | ||
var utils_1 = require("./utils"); | ||
/** | ||
* Type describing a link session that can create a eosjs compatible | ||
* signature provider and transact for a specific auth. | ||
*/ | ||
var LinkSession = /** @class */ (function () { | ||
function LinkSession() { | ||
} | ||
/** Restore a previously serialized session. */ | ||
LinkSession.restore = function (link, data) { | ||
switch (data.type) { | ||
case 'channel': | ||
return new LinkChannelSession(link, data); | ||
case 'fallback': | ||
return new LinkFallbackSession(link, data); | ||
default: | ||
throw new Error('Unable to restore, session data invalid'); | ||
} | ||
}; | ||
return utils_1.abiEncode(data, 'sealed_message'); | ||
} | ||
return LinkSession; | ||
}()); | ||
exports.LinkSession = LinkSession; | ||
/** | ||
* Link session, emits 'info' event when sequence number advances. | ||
* Link session that pushes requests over a channel. | ||
*/ | ||
class LinkSession extends events_1.EventEmitter { | ||
constructor(link, data) { | ||
super(); | ||
this.link = link; | ||
this.auth = data.auth; | ||
this.publicKey = data.publicKey; | ||
// private key never leaves closure unless exported explicitly | ||
let seq = data.channelSequence; | ||
this.exporter = () => ({ ...data, channelSequence: seq }); | ||
const { privateKey, channel, channelKey } = data; | ||
const signatureProvider = { | ||
sign(message) { | ||
const signature = ecc.signHash(message, privateKey); | ||
return { signer: '', signature }; | ||
}, | ||
var LinkChannelSession = /** @class */ (function (_super) { | ||
__extends(LinkChannelSession, _super); | ||
function LinkChannelSession(link, data) { | ||
var _this = _super.call(this) || this; | ||
_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 utils_1.sealMessage(request.encode(true, false), data.requestKey, data.channel.key); | ||
}; | ||
this.transport = { | ||
onSuccess: (request, result) => { | ||
if (this.link.transport.onSuccess) { | ||
this.link.transport.onSuccess(request, result); | ||
} | ||
_this.serialize = function () { return (__assign({ type: 'channel' }, data)); }; | ||
return _this; | ||
} | ||
LinkChannelSession.prototype.onSuccess = function (request, result) { | ||
if (this.link.transport.onSuccess) { | ||
this.link.transport.onSuccess(request, result); | ||
} | ||
}; | ||
LinkChannelSession.prototype.onFailure = function (request, error) { | ||
if (this.link.transport.onFailure) { | ||
this.link.transport.onFailure(request, error); | ||
} | ||
}; | ||
LinkChannelSession.prototype.onRequest = function (request, cancel) { | ||
var info = { | ||
expiration: new Date(Date.now() + this.timeout).toISOString().slice(0, -1), | ||
}; | ||
if (this.link.transport.onSessionRequest) { | ||
this.link.transport.onSessionRequest(this, request, this.timeout, this.channel.name, cancel); | ||
} | ||
setTimeout(function () { | ||
cancel(new errors_1.SessionError('Wallet did not respond in time', 'E_TIMEOUT')); | ||
}, this.timeout + 500); | ||
request.data.info.push({ | ||
key: 'link', | ||
value: utils_1.abiEncode(info, 'link_info'), | ||
}); | ||
this.link.rpc | ||
.fetchBuiltin(this.channel.url, { | ||
method: 'POST', | ||
headers: { | ||
'X-Buoy-Wait': (this.timeout / 1000).toFixed(0), | ||
}, | ||
onFailure: (request, error) => { | ||
if (this.link.transport.onFailure) { | ||
this.link.transport.onFailure(request, error); | ||
} | ||
}, | ||
onRequest: (request, cancel) => { | ||
seq++; | ||
let info = { seq }; | ||
this.emit('info', info); | ||
request.data.info.push({ key: 'link', value: utils_1.abiEncode(info, 'link_info') }); | ||
request.sign(signatureProvider); | ||
this.link.rpc | ||
.fetchBuiltin(channel, { | ||
method: 'POST', | ||
headers: { | ||
'X-Buoy-Wait': '60', | ||
}, | ||
body: sealMessage(request.encode(), privateKey, channelKey), | ||
}) | ||
.then((response) => { | ||
if (response.status !== 200) { | ||
cancel(new errors_1.SessionError('Unable to push message', 'E_DELIVERY')); | ||
} | ||
else { | ||
setTimeout(() => { | ||
cancel(new errors_1.SessionError('Wallet did not respond in time', 'E_TIMEOUT')); | ||
}, 30 * 1000); | ||
} | ||
}) | ||
.catch((error) => { | ||
cancel(new errors_1.SessionError(`Unable to reach link service (${error.message || String(error)})`, 'E_DELIVERY')); | ||
}); | ||
}, | ||
}; | ||
body: this.encrypt(request), | ||
}) | ||
.then(function (response) { | ||
if (response.status !== 200) { | ||
cancel(new errors_1.SessionError('Unable to push message', 'E_DELIVERY')); | ||
} | ||
else { | ||
// request delivered | ||
} | ||
}) | ||
.catch(function (error) { | ||
cancel(new errors_1.SessionError("Unable to reach link service (" + (error.message || String(error)) + ")", 'E_DELIVERY')); | ||
}); | ||
}; | ||
LinkChannelSession.prototype.makeSignatureProvider = function () { | ||
return this.link.makeSignatureProvider([this.publicKey], this); | ||
}; | ||
LinkChannelSession.prototype.transact = function (args) { | ||
return this.link.transact(args, this); | ||
}; | ||
return LinkChannelSession; | ||
}(LinkSession)); | ||
exports.LinkChannelSession = LinkChannelSession; | ||
var LinkFallbackSession = /** @class */ (function (_super) { | ||
__extends(LinkFallbackSession, _super); | ||
function LinkFallbackSession(link, data) { | ||
var _this = _super.call(this) || this; | ||
_this.link = link; | ||
_this.auth = data.auth; | ||
_this.publicKey = data.publicKey; | ||
_this.serialize = function () { return (__assign({ type: 'fallback' }, data)); }; | ||
return _this; | ||
} | ||
export() { | ||
return this.exporter(); | ||
} | ||
makeSignatureProvider() { | ||
return this.link.makeSignatureProvider([this.publicKey], this.transport); | ||
} | ||
transact(args) { | ||
return this.link.transact(args, this.transport); | ||
} | ||
} | ||
exports.LinkSession = LinkSession; | ||
LinkFallbackSession.prototype.transact = function (args) { | ||
return this.link.transact(args); | ||
}; | ||
LinkFallbackSession.prototype.makeSignatureProvider = function () { | ||
return this.link.makeSignatureProvider([this.publicKey]); | ||
}; | ||
return LinkFallbackSession; | ||
}(LinkSession)); | ||
exports.LinkFallbackSession = LinkFallbackSession; | ||
//# sourceMappingURL=link-session.js.map |
import { SigningRequest } from 'eosio-signing-request'; | ||
import { TransactResult } from './link'; | ||
import { LinkSession } from './link-session'; | ||
/** | ||
@@ -19,9 +20,10 @@ * Protocol link transports need to implement. | ||
onFailure?(request: SigningRequest, error: Error): any; | ||
/** | ||
* Called when a session request is initiated. | ||
* @param session Session where the request originated. | ||
* @param request Signing request that will be sent over the session. | ||
* @param timeout Number of milliseconds until session request expires. | ||
* @param device Display name of linked device. | ||
*/ | ||
onSessionRequest?(session: LinkSession, request: SigningRequest, timeout: number, device: string, cancel: (reason: string | Error) => void): any; | ||
} | ||
/** | ||
* A signing request presenter that writes requests | ||
* as URI strings and ASCII qr codes to console.log. | ||
*/ | ||
export declare class ConsoleTransport implements LinkTransport { | ||
onRequest(request: SigningRequest): void; | ||
} |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const qrcode_terminal_1 = __importDefault(require("qrcode-terminal")); | ||
/** | ||
* A signing request presenter that writes requests | ||
* as URI strings and ASCII qr codes to console.log. | ||
*/ | ||
class ConsoleTransport { | ||
onRequest(request) { | ||
const uri = request.encode(); | ||
console.log(`Signing request\n${uri}`); | ||
qrcode_terminal_1.default.setErrorLevel('L'); | ||
qrcode_terminal_1.default.generate(uri, { small: true }, (code) => { | ||
console.log(code); | ||
}); | ||
} | ||
} | ||
exports.ConsoleTransport = ConsoleTransport; | ||
//# sourceMappingURL=link-transport.js.map |
@@ -51,3 +51,3 @@ import * as esr from 'eosio-signing-request'; | ||
private abiCache; | ||
constructor(options?: LinkOptions); | ||
constructor(options: LinkOptions); | ||
getAbi(account: string): Promise<any>; | ||
@@ -54,0 +54,0 @@ createCallbackUrl(): string; |
542
lib/link.js
"use strict"; | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(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); | ||
}; | ||
var __awaiter = (this && this.__awaiter) || function (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()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (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 }; | ||
} | ||
}; | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
@@ -13,17 +60,16 @@ if (mod && mod.__esModule) return mod; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const esr = __importStar(require("eosio-signing-request")); | ||
const eosjs_1 = require("eosjs"); | ||
const ecc = __importStar(require("eosjs-ecc")); | ||
const fetch_ponyfill_1 = __importDefault(require("fetch-ponyfill")); | ||
const zlib = __importStar(require("pako")); | ||
const v4_1 = __importDefault(require("uuid/v4")); | ||
const ws_1 = __importDefault(require("ws")); | ||
const errors_1 = require("./errors"); | ||
const link_options_1 = require("./link-options"); | ||
const link_session_1 = require("./link-session"); | ||
const link_transport_1 = require("./link-transport"); | ||
const utils_1 = require("./utils"); | ||
const { fetch } = fetch_ponyfill_1.default(); | ||
class Link { | ||
constructor(options = {}) { | ||
var esr = __importStar(require("eosio-signing-request")); | ||
var eosjs_1 = require("eosjs"); | ||
var ecc = __importStar(require("eosjs-ecc")); | ||
var fetch_ponyfill_1 = __importDefault(require("fetch-ponyfill")); | ||
var zlib = __importStar(require("pako")); | ||
var v4_1 = __importDefault(require("uuid/v4")); | ||
var ws_1 = __importDefault(require("ws")); | ||
var errors_1 = require("./errors"); | ||
var link_options_1 = require("./link-options"); | ||
var link_session_1 = require("./link-session"); | ||
var utils_1 = require("./utils"); | ||
var fetch = fetch_ponyfill_1.default().fetch; | ||
var Link = /** @class */ (function () { | ||
function Link(options) { | ||
this.abiCache = new Map(); | ||
@@ -38,3 +84,3 @@ if (options.rpc === undefined || typeof options.rpc === 'string') { | ||
this.serviceAddress = (options.service || link_options_1.defaults.service).trim().replace(/\/$/, ''); | ||
this.transport = options.transport || new link_transport_1.ConsoleTransport(); | ||
this.transport = options.transport; | ||
this.requestOptions = { | ||
@@ -44,103 +90,143 @@ abiProvider: this, | ||
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}/${v4_1.default()}`; | ||
} | ||
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 errors_1.CancelError(reason)); | ||
} | ||
else { | ||
reject(reason); | ||
} | ||
}); | ||
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, | ||
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, | ||
}); | ||
}; | ||
Link.prototype.createCallbackUrl = function () { | ||
return this.serviceAddress + "/" + v4_1.default(); | ||
}; | ||
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; | ||
} | ||
}); | ||
}; | ||
Link.prototype.sendRequest = function (request, transport) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var t, linkUrl, ctx_1, socket, cancel, payload_1, signer, signatures, resolved, serializedTransaction, transaction, result, 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, 4, , 5]); | ||
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 errors_1.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: request, | ||
serializedTransaction: serializedTransaction, | ||
transaction: transaction, | ||
signatures: signatures, | ||
payload: payload_1, | ||
signer: signer, | ||
}; | ||
if (t.onSuccess) { | ||
t.onSuccess(request, result); | ||
} | ||
return [2 /*return*/, result]; | ||
case 4: | ||
error_1 = _a.sent(); | ||
if (t.onFailure) { | ||
t.onFailure(request, error_1); | ||
} | ||
throw error_1; | ||
case 5: return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
Link.prototype.transact = function (args, transport) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var t, request, result, broadcast, res; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
t = transport || this.transport; | ||
return [4 /*yield*/, this.createRequest(args)]; | ||
case 1: | ||
request = _a.sent(); | ||
return [4 /*yield*/, this.sendRequest(request, t) | ||
// broadcast transaction if requested | ||
]; | ||
case 2: | ||
result = _a.sent(); | ||
broadcast = args.broadcast || false; | ||
if (!broadcast) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, this.rpc.push_transaction({ | ||
signatures: result.signatures, | ||
serializedTransaction: result.serializedTransaction, | ||
})]; | ||
case 3: | ||
res = _a.sent(); | ||
result.processed = res.processed; | ||
_a.label = 4; | ||
case 4: return [2 /*return*/, result]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** | ||
@@ -151,72 +237,100 @@ * Create a identity request. | ||
*/ | ||
async identify(requestPermission, info) { | ||
const request = await this.createRequest({ | ||
identity: { permission: requestPermission }, | ||
info, | ||
Link.prototype.identify = function (requestPermission, info) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var request, res, serializedTransaction, 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 }, | ||
info: info, | ||
})]; | ||
case 1: | ||
request = _a.sent(); | ||
return [4 /*yield*/, this.sendRequest(request)]; | ||
case 2: | ||
res = _a.sent(); | ||
return [4 /*yield*/, esr.ResolvedSigningRequest.fromPayload(res.payload, this.requestOptions)]; | ||
case 3: | ||
serializedTransaction = (_a.sent()).serializedTransaction; | ||
message = Buffer.concat([ | ||
Buffer.from(request.getChainId(), 'hex'), | ||
Buffer.from(serializedTransaction), | ||
Buffer.alloc(32), | ||
]); | ||
signer = res.signer; | ||
signerKey = ecc.recover(res.signatures[0], message); | ||
return [4 /*yield*/, this.rpc.get_account(signer.actor)]; | ||
case 4: | ||
account = _a.sent(); | ||
if (!account) { | ||
throw new errors_1.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 errors_1.IdentityError(signer.actor + " signed for unknown permission: " + signer.permission); | ||
} | ||
auth = permission.required_auth; | ||
keyAuth = auth.keys.find(function (_a) { | ||
var key = _a.key; | ||
return key === signerKey; | ||
}); | ||
if (!keyAuth) { | ||
throw new errors_1.IdentityError(signer.actor + "@" + signer.permission + " has no key matching id signature"); | ||
} | ||
if (auth.threshold > keyAuth.weight) { | ||
throw new errors_1.IdentityError(signer.actor + "@" + signer.permission + " signature does not reach auth threshold"); | ||
} | ||
return [2 /*return*/, __assign(__assign({}, res), { account: account, | ||
signerKey: signerKey })]; | ||
} | ||
}); | ||
}); | ||
const res = await this.sendRequest(request); | ||
const { serializedTransaction } = await esr.ResolvedSigningRequest.fromPayload(res.payload, this.requestOptions); | ||
const message = Buffer.concat([ | ||
Buffer.from(request.getChainId(), 'hex'), | ||
Buffer.from(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 errors_1.IdentityError(`Signature from unknown account: ${signer.actor}`); | ||
} | ||
const permission = account.permissions.find(({ perm_name }) => perm_name === signer.permission); | ||
if (!permission) { | ||
throw new errors_1.IdentityError(`${signer.actor} signed for unknown permission: ${signer.permission}`); | ||
} | ||
const auth = permission.required_auth; | ||
const keyAuth = auth.keys.find(({ key }) => key === signerKey); | ||
if (!keyAuth) { | ||
throw new errors_1.IdentityError(`${signer.actor}@${signer.permission} has no key matching id signature`); | ||
} | ||
if (auth.threshold > keyAuth.weight) { | ||
throw new errors_1.IdentityError(`${signer.actor}@${signer.permission} signature does not reach auth threshold`); | ||
} | ||
return { | ||
...res, | ||
account, | ||
signerKey, | ||
}; | ||
} | ||
}; | ||
/** | ||
* Login and create a persistent session. | ||
*/ | ||
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: utils_1.abiEncode(createInfo, 'link_create'), | ||
Link.prototype.login = function (sessionName) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var privateKey, requestKey, createInfo, res, 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: sessionName, | ||
request_key: requestKey, | ||
}; | ||
return [4 /*yield*/, this.identify(undefined, { | ||
link: utils_1.abiEncode(createInfo, 'link_create'), | ||
})]; | ||
case 2: | ||
res = _a.sent(); | ||
if (res.payload.link_ch && res.payload.link_key && res.payload.link_name) { | ||
session = new link_session_1.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, | ||
}); | ||
} | ||
else { | ||
session = new link_session_1.LinkFallbackSession(this, { | ||
auth: res.signer, | ||
publicKey: res.signerKey, | ||
}); | ||
} | ||
return [2 /*return*/, __assign(__assign({}, res), { session: session })]; | ||
} | ||
}); | ||
}); | ||
let session; | ||
if (res.payload.link_ch && res.payload.link_key) { | ||
let data = { | ||
auth: res.signer, | ||
publicKey: res.signerKey, | ||
channel: res.payload.link_ch, | ||
channelKey: res.payload.link_key, | ||
channelSequence: 0, | ||
privateKey, | ||
}; | ||
session = new link_session_1.LinkSession(this, data); | ||
} | ||
else { | ||
/// todo fallback session using current transport | ||
throw new Error('User wallet not link compatible'); | ||
} | ||
return { | ||
...res, | ||
session, | ||
}; | ||
} | ||
}; | ||
/** | ||
@@ -226,25 +340,34 @@ * Create an eosjs signature provider using this link. | ||
*/ | ||
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 })]; | ||
} | ||
}); | ||
}); }, | ||
}; | ||
} | ||
} | ||
}; | ||
return Link; | ||
}()); | ||
exports.Link = Link; | ||
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 { | ||
@@ -258,5 +381,5 @@ resolve(JSON.parse(response)); | ||
}; | ||
const connect = () => { | ||
const socket = new ws_1.default(socketUrl); | ||
ctx.cancel = () => { | ||
var connect = function () { | ||
var socket = new ws_1.default(socketUrl); | ||
ctx.cancel = function () { | ||
active = false; | ||
@@ -268,3 +391,3 @@ if (socket.readyState === ws_1.default.OPEN || | ||
}; | ||
socket.onmessage = (event) => { | ||
socket.onmessage = function (event) { | ||
active = false; | ||
@@ -275,10 +398,10 @@ if (socket.readyState === ws_1.default.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); | ||
} | ||
@@ -294,7 +417,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) { | ||
@@ -315,1 +438,2 @@ setTimeout(connect, backoff(retries++)); | ||
} | ||
//# sourceMappingURL=link.js.map |
import { Bytes } from './link-abi'; | ||
export declare function abiEncode(value: any, typeName: string): Uint8Array; | ||
export declare function abiDecode<T = any>(data: Bytes, typeName: string): T; | ||
export declare function abiDecode<ResultType = any>(data: Bytes, typeName: string): ResultType; | ||
export declare function sealMessage(message: string, privateKey: string, publicKey: string): Uint8Array; |
@@ -9,12 +9,16 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const eosjs_1 = require("eosjs"); | ||
const linkAbi = __importStar(require("./link-abi.json")); | ||
const types = eosjs_1.Serialize.getTypesFromAbi(eosjs_1.Serialize.createInitialTypes(), linkAbi); | ||
var eosjs_1 = require("eosjs"); | ||
var ecc = __importStar(require("eosjs-ecc")); | ||
var link_abi_data_1 = __importDefault(require("./link-abi-data")); | ||
var types = eosjs_1.Serialize.getTypesFromAbi(eosjs_1.Serialize.createInitialTypes(), link_abi_data_1.default); | ||
function abiEncode(value, typeName) { | ||
let type = types.get(typeName); | ||
var type = types.get(typeName); | ||
if (!type) { | ||
throw new Error(`No such type: ${type}`); | ||
throw new Error("No such type: " + typeName); | ||
} | ||
let buf = new eosjs_1.Serialize.SerialBuffer(); | ||
var buf = new eosjs_1.Serialize.SerialBuffer(); | ||
type.serialize(buf, value); | ||
@@ -25,5 +29,5 @@ return buf.asUint8Array(); | ||
function abiDecode(data, typeName) { | ||
let type = types.get(typeName); | ||
var type = types.get(typeName); | ||
if (!type) { | ||
throw new Error(`No such type: ${type}`); | ||
throw new Error("No such type: " + typeName); | ||
} | ||
@@ -36,3 +40,3 @@ if (typeof data === 'string') { | ||
} | ||
let buf = new eosjs_1.Serialize.SerialBuffer({ | ||
var buf = new eosjs_1.Serialize.SerialBuffer({ | ||
array: data, | ||
@@ -43,1 +47,13 @@ }); | ||
exports.abiDecode = abiDecode; | ||
function sealMessage(message, privateKey, publicKey) { | ||
var res = ecc.Aes.encrypt(privateKey, publicKey, message); | ||
var data = { | ||
from: ecc.privateToPublic(privateKey), | ||
nonce: res.nonce.toString(), | ||
ciphertext: res.message, | ||
checksum: res.checksum, | ||
}; | ||
return abiEncode(data, 'sealed_message'); | ||
} | ||
exports.sealMessage = sealMessage; | ||
//# sourceMappingURL=utils.js.map |
{ | ||
"name": "anchor-link", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Library for authenticating and signing transactions using the Anchor Link protocol", | ||
@@ -14,2 +14,14 @@ "license": "MIT", | ||
}, | ||
"directories": { | ||
"lib": "lib" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/greymass/anchor-link.git" | ||
}, | ||
"author": "", | ||
"bugs": { | ||
"url": "https://github.com/greymass/anchor-link/issues" | ||
}, | ||
"homepage": "https://github.com/greymass/anchor-link#readme", | ||
"files": [ | ||
@@ -20,3 +32,3 @@ "lib/*", | ||
"dependencies": { | ||
"eosio-signing-request": "1.0.0", | ||
"eosio-signing-request": "1.0.1", | ||
"eosjs": "^20.0.0", | ||
@@ -31,6 +43,7 @@ "eosjs-ecc": "^4.0.7", | ||
"devDependencies": { | ||
"@types/node": "^12.12.21", | ||
"@types/node": "^13.1.7", | ||
"@types/pako": "^1.0.1", | ||
"@types/uuid": "^3.4.6", | ||
"@types/ws": "^6.0.4", | ||
"@types/ws": "^7.2.0", | ||
"eosio-abi2ts": "^1.2.2", | ||
"prettier": "^1.19.1", | ||
@@ -42,15 +55,3 @@ "ts-node": "^8.5.3", | ||
"typescript": "^3.7.4" | ||
}, | ||
"directories": { | ||
"lib": "lib" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/greymass/anchor-link.git" | ||
}, | ||
"author": "", | ||
"bugs": { | ||
"url": "https://github.com/greymass/anchor-link/issues" | ||
}, | ||
"homepage": "https://github.com/greymass/anchor-link#readme" | ||
} | ||
} | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
64733
31
1097
11
3
+ Addedeosio-signing-request@1.0.1(transitive)
- Removedeosio-signing-request@1.0.0(transitive)
Updatedeosio-signing-request@1.0.1