@nimiq/accounts-client
Advanced tools
Comparing version 0.1.3 to 0.2.0
@@ -1,2 +0,2 @@ | ||
import { RedirectRpcClient, PostMessageRpcClient, RandomUtils, UrlRpcEncoder, ReceiveOnlyPostMessageRpcClient } from '@nimiq/rpc'; | ||
import { PostMessageRpcClient, RedirectRpcClient } from '@nimiq/rpc'; | ||
@@ -43,8 +43,7 @@ class RequestBehavior { | ||
const state = Object.assign({}, this._localState, { __command: command }); | ||
console.log('state', state); | ||
client.callAndSaveLocalState(this._returnUrl, JSON.stringify(state), command, ...args); | ||
client.callAndSaveLocalState(this._returnUrl, state, command, ...args); | ||
} | ||
} | ||
class UrlEncodedPopupBehavior extends RequestBehavior { | ||
constructor(options = UrlEncodedPopupBehavior.DEFAULT_OPTIONS) { | ||
class PopupRequestBehavior extends RequestBehavior { | ||
constructor(options = PopupRequestBehavior.DEFAULT_OPTIONS) { | ||
super(BehaviorType.POPUP); | ||
@@ -55,17 +54,14 @@ this._options = options; | ||
const origin = RequestBehavior.getAllowedOrigin(endpoint); | ||
const id = RandomUtils.generateRandomId(); | ||
const url = UrlRpcEncoder.prepareRedirectInvocation(endpoint, id, '<postMessage>', command, args); | ||
const popup = this.createPopup(url); | ||
const client = new ReceiveOnlyPostMessageRpcClient(popup, origin, id); | ||
const popup = this.createPopup(endpoint); | ||
const client = new PostMessageRpcClient(popup, origin); | ||
await client.init(); | ||
try { | ||
const result = await client.listenFor(command); | ||
client.close(); | ||
popup.close(); | ||
return result; | ||
return await client.callAndPersist(command, ...args); | ||
} | ||
catch (e) { | ||
throw e; | ||
} | ||
finally { | ||
client.close(); | ||
popup.close(); | ||
throw e; | ||
} | ||
@@ -81,3 +77,3 @@ } | ||
} | ||
UrlEncodedPopupBehavior.DEFAULT_OPTIONS = ''; | ||
PopupRequestBehavior.DEFAULT_OPTIONS = ''; | ||
class IFrameRequestBehavior extends RequestBehavior { | ||
@@ -120,6 +116,2 @@ constructor() { | ||
/** | ||
* Sorted by context and alphabetically | ||
*/ | ||
var RequestType; | ||
@@ -136,5 +128,5 @@ (function (RequestType) { | ||
RequestType["EXPORT"] = "export"; | ||
RequestType["CHANGE_PASSPHRASE"] = "change-passphrase"; | ||
RequestType["CHANGE_PASSWORD"] = "change-password"; | ||
RequestType["LOGOUT"] = "logout"; | ||
RequestType["ADD_ACCOUNT"] = "add-account"; | ||
RequestType["ADD_ADDRESS"] = "add-address"; | ||
RequestType["RENAME"] = "rename"; | ||
@@ -147,3 +139,3 @@ RequestType["CHOOSE_ADDRESS"] = "choose-address"; | ||
this._endpoint = endpoint; | ||
this._defaultBehavior = defaultBehavior || new UrlEncodedPopupBehavior(`left=${window.innerWidth / 2 - 500},top=50,width=1000,height=900,location=yes,dependent=yes`); | ||
this._defaultBehavior = defaultBehavior || new PopupRequestBehavior(`left=${window.innerWidth / 2 - 400},top=75,width=800,height=850,location=yes,dependent=yes`); | ||
this._iframeBehavior = new IFrameRequestBehavior(); | ||
@@ -153,2 +145,15 @@ // Check for RPC results in the URL | ||
} | ||
static get DEFAULT_ENDPOINT() { | ||
const originArray = location.origin.split('.'); | ||
originArray.shift(); | ||
const tld = originArray.join('.'); | ||
switch (tld) { | ||
case 'nimiq.com': | ||
return 'https://accounts.nimiq.com'; | ||
case 'nimiq-testnet.com': | ||
return 'https://accounts.nimiq-testnet.com'; | ||
default: | ||
return 'http://localhost:8080'; | ||
} | ||
} | ||
checkRedirectResponse() { | ||
@@ -160,3 +165,3 @@ return this._redirectClient.init(); | ||
// State is always an object containing at least the __command property | ||
(result, rpcId, state) => resolve(result, JSON.parse(state)), (error, rpcId, state) => reject && reject(error, JSON.parse(state))); | ||
(result, rpcId, state) => resolve(result, state), (error, rpcId, state) => reject && reject(error, state)); | ||
} | ||
@@ -187,7 +192,7 @@ onboard(request, requestBehavior = this._defaultBehavior) { | ||
} | ||
changePassphrase(request, requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.CHANGE_PASSPHRASE, [request]); | ||
changePassword(request, requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.CHANGE_PASSWORD, [request]); | ||
} | ||
addAccount(request, requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.ADD_ACCOUNT, [request]); | ||
addAddress(request, requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.ADD_ADDRESS, [request]); | ||
} | ||
@@ -201,3 +206,3 @@ rename(request, requestBehavior = this._defaultBehavior) { | ||
migrate(requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.MIGRATE, [{}]); | ||
return this._request(requestBehavior, RequestType.MIGRATE, [{ appName: 'Accounts Client' }]); | ||
} | ||
@@ -218,6 +223,3 @@ /** | ||
AccountsClient.RedirectRequestBehavior = RedirectRequestBehavior; | ||
AccountsClient.DEFAULT_ENDPOINT = window.location.origin === 'https://safe-next.nimiq.com' ? 'https://accounts.nimiq.com' | ||
: window.location.origin === 'https://safe-next.nimiq-testnet.com' ? 'https://accounts.nimiq-testnet.com' | ||
: 'http://localhost:8080'; | ||
export default AccountsClient; |
@@ -5,3 +5,3 @@ (function (global, factory) { | ||
(global.AccountsClient = factory(global.rpc)); | ||
}(this, (function (Rpc) { 'use strict'; | ||
}(this, (function (rpc) { 'use strict'; | ||
@@ -45,11 +45,10 @@ class RequestBehavior { | ||
const origin = RequestBehavior.getAllowedOrigin(endpoint); | ||
const client = new Rpc.RedirectRpcClient(endpoint, origin); | ||
const client = new rpc.RedirectRpcClient(endpoint, origin); | ||
await client.init(); | ||
const state = Object.assign({}, this._localState, { __command: command }); | ||
console.log('state', state); | ||
client.callAndSaveLocalState(this._returnUrl, JSON.stringify(state), command, ...args); | ||
client.callAndSaveLocalState(this._returnUrl, state, command, ...args); | ||
} | ||
} | ||
class UrlEncodedPopupBehavior extends RequestBehavior { | ||
constructor(options = UrlEncodedPopupBehavior.DEFAULT_OPTIONS) { | ||
class PopupRequestBehavior extends RequestBehavior { | ||
constructor(options = PopupRequestBehavior.DEFAULT_OPTIONS) { | ||
super(BehaviorType.POPUP); | ||
@@ -60,17 +59,14 @@ this._options = options; | ||
const origin = RequestBehavior.getAllowedOrigin(endpoint); | ||
const id = Rpc.RandomUtils.generateRandomId(); | ||
const url = Rpc.UrlRpcEncoder.prepareRedirectInvocation(endpoint, id, '<postMessage>', command, args); | ||
const popup = this.createPopup(url); | ||
const client = new Rpc.ReceiveOnlyPostMessageRpcClient(popup, origin, id); | ||
const popup = this.createPopup(endpoint); | ||
const client = new rpc.PostMessageRpcClient(popup, origin); | ||
await client.init(); | ||
try { | ||
const result = await client.listenFor(command); | ||
client.close(); | ||
popup.close(); | ||
return result; | ||
return await client.callAndPersist(command, ...args); | ||
} | ||
catch (e) { | ||
throw e; | ||
} | ||
finally { | ||
client.close(); | ||
popup.close(); | ||
throw e; | ||
} | ||
@@ -86,3 +82,3 @@ } | ||
} | ||
UrlEncodedPopupBehavior.DEFAULT_OPTIONS = ''; | ||
PopupRequestBehavior.DEFAULT_OPTIONS = ''; | ||
class IFrameRequestBehavior extends RequestBehavior { | ||
@@ -106,3 +102,3 @@ constructor() { | ||
if (!this._client) { | ||
this._client = new Rpc.PostMessageRpcClient(this._iframe.contentWindow, origin); | ||
this._client = new rpc.PostMessageRpcClient(this._iframe.contentWindow, origin); | ||
await this._client.init(); | ||
@@ -126,6 +122,2 @@ } | ||
/** | ||
* Sorted by context and alphabetically | ||
*/ | ||
var RequestType; | ||
@@ -142,5 +134,5 @@ (function (RequestType) { | ||
RequestType["EXPORT"] = "export"; | ||
RequestType["CHANGE_PASSPHRASE"] = "change-passphrase"; | ||
RequestType["CHANGE_PASSWORD"] = "change-password"; | ||
RequestType["LOGOUT"] = "logout"; | ||
RequestType["ADD_ACCOUNT"] = "add-account"; | ||
RequestType["ADD_ADDRESS"] = "add-address"; | ||
RequestType["RENAME"] = "rename"; | ||
@@ -153,7 +145,20 @@ RequestType["CHOOSE_ADDRESS"] = "choose-address"; | ||
this._endpoint = endpoint; | ||
this._defaultBehavior = defaultBehavior || new UrlEncodedPopupBehavior(`left=${window.innerWidth / 2 - 500},top=50,width=1000,height=900,location=yes,dependent=yes`); | ||
this._defaultBehavior = defaultBehavior || new PopupRequestBehavior(`left=${window.innerWidth / 2 - 400},top=75,width=800,height=850,location=yes,dependent=yes`); | ||
this._iframeBehavior = new IFrameRequestBehavior(); | ||
// Check for RPC results in the URL | ||
this._redirectClient = new Rpc.RedirectRpcClient('', RequestBehavior.getAllowedOrigin(this._endpoint)); | ||
this._redirectClient = new rpc.RedirectRpcClient('', RequestBehavior.getAllowedOrigin(this._endpoint)); | ||
} | ||
static get DEFAULT_ENDPOINT() { | ||
const originArray = location.origin.split('.'); | ||
originArray.shift(); | ||
const tld = originArray.join('.'); | ||
switch (tld) { | ||
case 'nimiq.com': | ||
return 'https://accounts.nimiq.com'; | ||
case 'nimiq-testnet.com': | ||
return 'https://accounts.nimiq-testnet.com'; | ||
default: | ||
return 'http://localhost:8080'; | ||
} | ||
} | ||
checkRedirectResponse() { | ||
@@ -165,3 +170,3 @@ return this._redirectClient.init(); | ||
// State is always an object containing at least the __command property | ||
(result, rpcId, state) => resolve(result, JSON.parse(state)), (error, rpcId, state) => reject && reject(error, JSON.parse(state))); | ||
(result, rpcId, state) => resolve(result, state), (error, rpcId, state) => reject && reject(error, state)); | ||
} | ||
@@ -192,7 +197,7 @@ onboard(request, requestBehavior = this._defaultBehavior) { | ||
} | ||
changePassphrase(request, requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.CHANGE_PASSPHRASE, [request]); | ||
changePassword(request, requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.CHANGE_PASSWORD, [request]); | ||
} | ||
addAccount(request, requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.ADD_ACCOUNT, [request]); | ||
addAddress(request, requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.ADD_ADDRESS, [request]); | ||
} | ||
@@ -206,3 +211,3 @@ rename(request, requestBehavior = this._defaultBehavior) { | ||
migrate(requestBehavior = this._defaultBehavior) { | ||
return this._request(requestBehavior, RequestType.MIGRATE, [{}]); | ||
return this._request(requestBehavior, RequestType.MIGRATE, [{ appName: 'Accounts Client' }]); | ||
} | ||
@@ -223,5 +228,2 @@ /** | ||
AccountsClient.RedirectRequestBehavior = RedirectRequestBehavior; | ||
AccountsClient.DEFAULT_ENDPOINT = window.location.origin === 'https://safe-next.nimiq.com' ? 'https://accounts.nimiq.com' | ||
: window.location.origin === 'https://safe-next.nimiq-testnet.com' ? 'https://accounts.nimiq-testnet.com' | ||
: 'http://localhost:8080'; | ||
@@ -228,0 +230,0 @@ return AccountsClient; |
@@ -1,1 +0,1 @@ | ||
class RandomUtils{static generateRandomId(){const e=new Uint32Array(1);return crypto.getRandomValues(e),e[0]}}var ResponseStatus;!function(e){e.OK="ok",e.ERROR="error"}(ResponseStatus||(ResponseStatus={}));const POSTMESSAGE_RETURN_URL="<postMessage>";class Base64{static byteLength(e){const[t,s]=Base64._getLengths(e);return Base64._byteLength(t,s)}static decode(e){Base64._initRevLookup();const[t,s]=Base64._getLengths(e),r=new Uint8Array(Base64._byteLength(t,s));let i=0;const n=s>0?t-4:t;let o=0;for(;o<n;o+=4){const t=Base64._revLookup[e.charCodeAt(o)]<<18|Base64._revLookup[e.charCodeAt(o+1)]<<12|Base64._revLookup[e.charCodeAt(o+2)]<<6|Base64._revLookup[e.charCodeAt(o+3)];r[i++]=t>>16&255,r[i++]=t>>8&255,r[i++]=255&t}if(2===s){const t=Base64._revLookup[e.charCodeAt(o)]<<2|Base64._revLookup[e.charCodeAt(o+1)]>>4;r[i++]=255&t}if(1===s){const t=Base64._revLookup[e.charCodeAt(o)]<<10|Base64._revLookup[e.charCodeAt(o+1)]<<4|Base64._revLookup[e.charCodeAt(o+2)]>>2;r[i++]=t>>8&255,r[i]=255&t}return r}static encode(e){const t=e.length,s=t%3,r=[];for(let i=0,n=t-s;i<n;i+=16383)r.push(Base64._encodeChunk(e,i,i+16383>n?n:i+16383));if(1===s){const s=e[t-1];r.push(Base64._lookup[s>>2]+Base64._lookup[s<<4&63]+"==")}else if(2===s){const s=(e[t-2]<<8)+e[t-1];r.push(Base64._lookup[s>>10]+Base64._lookup[s>>4&63]+Base64._lookup[s<<2&63]+"=")}return r.join("")}static encodeUrl(e){return Base64.encode(e).replace(/\//g,"_").replace(/\+/g,"-").replace(/=/g,".")}static decodeUrl(e){return Base64.decode(e.replace(/_/g,"/").replace(/-/g,"+").replace(/\./g,"="))}static _initRevLookup(){if(0===Base64._revLookup.length){Base64._revLookup=[];for(let e=0,t=Base64._lookup.length;e<t;e++)Base64._revLookup[Base64._lookup.charCodeAt(e)]=e;Base64._revLookup["-".charCodeAt(0)]=62,Base64._revLookup["_".charCodeAt(0)]=63}}static _getLengths(e){const t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");let s=e.indexOf("=");return-1===s&&(s=t),[s,s===t?0:4-s%4]}static _byteLength(e,t){return 3*(e+t)/4-t}static _tripletToBase64(e){return Base64._lookup[e>>18&63]+Base64._lookup[e>>12&63]+Base64._lookup[e>>6&63]+Base64._lookup[63&e]}static _encodeChunk(e,t,s){const r=[];for(let i=t;i<s;i+=3){const t=(e[i]<<16&16711680)+(e[i+1]<<8&65280)+(255&e[i+2]);r.push(Base64._tripletToBase64(t))}return r.join("")}}var ExtraJSONTypes,BehaviorType,RequestType;Base64._lookup="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",Base64._revLookup=[],function(e){e[e.UINT8_ARRAY=0]="UINT8_ARRAY"}(ExtraJSONTypes||(ExtraJSONTypes={}));class JSONUtils{static stringify(e){return JSON.stringify(e,JSONUtils._jsonifyType)}static parse(e){return JSON.parse(e,JSONUtils._parseType)}static _parseType(e,t){if(t&&t.hasOwnProperty&&t.hasOwnProperty(JSONUtils.TYPE_SYMBOL)&&t.hasOwnProperty(JSONUtils.VALUE_SYMBOL))switch(t[JSONUtils.TYPE_SYMBOL]){case ExtraJSONTypes.UINT8_ARRAY:return Base64.decode(t[JSONUtils.VALUE_SYMBOL])}return t}static _jsonifyType(e,t){return t instanceof Uint8Array?JSONUtils._typedObject(ExtraJSONTypes.UINT8_ARRAY,Base64.encode(t)):t}static _typedObject(e,t){const s={};return s[JSONUtils.TYPE_SYMBOL]=e,s[JSONUtils.VALUE_SYMBOL]=t,s}}JSONUtils.TYPE_SYMBOL="__",JSONUtils.VALUE_SYMBOL="v";class RequestIdStorage{constructor(e=!0){this._store=e?window.sessionStorage:null,this._validIds=new Map,e&&this._restoreIds()}static _decodeIds(e){const t=JSONUtils.parse(e),s=new Map;for(const e of Object.keys(t)){const r=parseInt(e,10);s.set(isNaN(r)?e:r,t[e])}return s}has(e){return this._validIds.has(e)}getCommand(e){const t=this._validIds.get(e);return t?t[0]:null}getState(e){const t=this._validIds.get(e);return t?t[1]:null}add(e,t,s=null){this._validIds.set(e,[t,s]),this._storeIds()}remove(e){this._validIds.delete(e),this._storeIds()}clear(){this._validIds.clear(),this._store&&this._store.removeItem(RequestIdStorage.KEY)}_encodeIds(){const e=Object.create(null);for(const[t,s]of this._validIds)e[t]=s;return JSONUtils.stringify(e)}_restoreIds(){const e=this._store.getItem(RequestIdStorage.KEY);e&&(this._validIds=RequestIdStorage._decodeIds(e))}_storeIds(){this._store&&this._store.setItem(RequestIdStorage.KEY,this._encodeIds())}}RequestIdStorage.KEY="rpcRequests";class UrlRpcEncoder{static receiveRedirectCommand(e){if(!document.referrer)return null;const t=new URLSearchParams(e.search),s=new URL(document.referrer);if(!t.has("command"))return null;if(!t.has("id"))return null;if(!t.has("returnURL"))return null;const r=t.get("returnURL")===POSTMESSAGE_RETURN_URL&&window.opener;if(!r){if(new URL(t.get("returnURL")).origin!==s.origin)return null}let i=[];if(t.has("args"))try{i=JSONUtils.parse(t.get("args"))}catch(e){}return i=Array.isArray(i)?i:[],{origin:s.origin,data:{id:parseInt(t.get("id"),10),command:t.get("command"),args:i},returnURL:t.get("returnURL"),source:r?window.opener:null}}static receiveRedirectResponse(e){if(!document.referrer)return null;const t=new URLSearchParams(e.search),s=new URL(document.referrer);if(!t.has("status"))return null;if(!t.has("id"))return null;if(!t.has("result"))return null;const r=JSONUtils.parse(t.get("result")),i=t.get("status")===ResponseStatus.OK?ResponseStatus.OK:ResponseStatus.ERROR;return{origin:s.origin,data:{id:parseInt(t.get("id"),10),status:i,result:r}}}static prepareRedirectReply(e,t,s){const r=new URLSearchParams;return r.set("status",t),r.set("result",JSONUtils.stringify(s)),r.set("id",e.id.toString()),`${e.returnURL}${new URL(e.returnURL).search.length>0?"&":"?"}${r.toString()}`}static prepareRedirectInvocation(e,t,s,r,i){const n=new URLSearchParams;return n.set("id",t.toString()),n.set("returnURL",s),n.set("command",r),Array.isArray(i)&&n.set("args",JSONUtils.stringify(i)),`${e}?${n.toString()}`}}class RpcClient{constructor(e,t=!1){this._allowedOrigin=e,this._waitingRequests=new RequestIdStorage(t),this._responseHandlers=new Map}onResponse(e,t,s){this._responseHandlers.set(e,{resolve:t,reject:s})}_receive(e){if(!e.data||!e.data.status||!e.data.id||"*"!==this._allowedOrigin&&e.origin!==this._allowedOrigin)return;const t=e.data,s=this._getCallback(t.id),r=this._waitingRequests.getState(t.id);if(s){if(console.debug("RpcClient RECEIVE",t),t.status===ResponseStatus.OK)s.resolve(t.result,t.id,r);else if(t.status===ResponseStatus.ERROR){const e=new Error(t.result.message);t.result.stack&&(e.stack=t.result.stack),t.result.name&&(e.name=t.result.name),s.reject(e,t.id,r)}}else console.warn("Unknown RPC response:",t)}_getCallback(e){if(this._responseHandlers.has(e))return this._responseHandlers.get(e);{const t=this._waitingRequests.getCommand(e);if(t)return this._responseHandlers.get(t)}}}class PostMessageRpcClient extends RpcClient{constructor(e,t){super(t),this._target=e,this._connected=!1,this._receiveListener=this._receive.bind(this)}async init(){await this._connect(),window.addEventListener("message",this._receiveListener)}async call(e,...t){if(!this._connected)throw new Error("Client is not connected, call init first");return new Promise((s,r)=>{const i={command:e,args:t,id:RandomUtils.generateRandomId()};this._responseHandlers.set(i.id,{resolve:s,reject:r}),this._waitingRequests.add(i.id,e);const n=()=>{this._target.closed&&r(new Error("Window was closed")),setTimeout(n,500)};setTimeout(n,500),console.debug("RpcClient REQUEST",e,t),this._target.postMessage(i,this._allowedOrigin)})}close(){window.removeEventListener("message",this._receiveListener),this._connected=!1}_connect(){return new Promise((e,t)=>{const s=t=>{const{source:r,origin:i,data:n}=t;if(r===this._target&&n.status===ResponseStatus.OK&&"pong"===n.result&&1===n.id&&("*"===this._allowedOrigin||i===this._allowedOrigin)){if(n.result.stack){const e=new Error(n.result.message);e.stack=n.result.stack,n.result.name&&(e.name=n.result.name),console.error(e)}window.removeEventListener("message",s),this._connected=!0,console.log("RpcClient: Connection established"),window.addEventListener("message",this._receiveListener),e(!0)}};window.addEventListener("message",s);let r=0;const i=setTimeout(()=>{window.removeEventListener("message",s),clearTimeout(r),t(new Error("Connection timeout"))},1e4),n=()=>{if(this._connected)clearTimeout(i);else{try{this._target.postMessage({command:"ping",id:1},this._allowedOrigin)}catch(e){console.error(`postMessage failed: ${e}`)}r=setTimeout(n,1e3)}};r=setTimeout(n,100)})}}class ReceiveOnlyPostMessageRpcClient extends PostMessageRpcClient{constructor(e,t,s){super(e,t),this._requestId=s}async init(){window.addEventListener("message",this._receiveListener)}async listenFor(e){return new Promise((t,s)=>{this._responseHandlers.set(this._requestId,{resolve:t,reject:s}),this._waitingRequests.add(this._requestId,e);const r=()=>{this._target.closed&&s(new Error("Window was closed")),setTimeout(r,500)};setTimeout(r,500)})}}class RedirectRpcClient extends RpcClient{constructor(e,t){super(t,!0),this._target=e}async init(){const e=UrlRpcEncoder.receiveRedirectResponse(window.location);e?this._receive(e):UrlRpcEncoder.receiveRedirectCommand(window.location)||this._rejectOnBack()}close(){}call(e,t,...s){this.callAndSaveLocalState(e,null,t,...s)}callAndSaveLocalState(e,t,s,...r){const i=RandomUtils.generateRandomId(),n=UrlRpcEncoder.prepareRedirectInvocation(this._target,i,e,s,r);this._waitingRequests.add(i,s,t),history.replaceState({rpcRequestId:i},document.title),console.debug("RpcClient REQUEST",s,r),window.location.href=n}_rejectOnBack(){if(history.state&&history.state.rpcRequestId){const e=history.state.rpcRequestId,t=this._getCallback(e),s=this._waitingRequests.getState(e);if(t){console.debug("RpcClient BACK");const r=new Error("Request aborted");t.reject(r,e,s)}}}}class RequestBehavior{static getAllowedOrigin(e){return new URL(e).origin}constructor(e){this._type=e}async request(e,t,s){throw new Error("Not implemented")}get type(){return this._type}}!function(e){e[e.REDIRECT=0]="REDIRECT",e[e.POPUP=1]="POPUP",e[e.IFRAME=2]="IFRAME"}(BehaviorType||(BehaviorType={}));class RedirectRequestBehavior extends RequestBehavior{static withLocalState(e){return new RedirectRequestBehavior(void 0,e)}constructor(e,t){super(BehaviorType.REDIRECT);const s=window.location;if(this._returnUrl=e||`${s.origin}${s.pathname}`,this._localState=t||{},void 0!==this._localState.__command)throw new Error("Invalid localState: Property '__command' is reserved")}async request(e,t,s){const r=RequestBehavior.getAllowedOrigin(e),i=new RedirectRpcClient(e,r);await i.init();const n=Object.assign({},this._localState,{__command:t});console.log("state",n),i.callAndSaveLocalState(this._returnUrl,JSON.stringify(n),t,...s)}}class UrlEncodedPopupBehavior extends RequestBehavior{constructor(e=UrlEncodedPopupBehavior.DEFAULT_OPTIONS){super(BehaviorType.POPUP),this._options=e}async request(e,t,s){const r=RequestBehavior.getAllowedOrigin(e),i=RandomUtils.generateRandomId(),n=UrlRpcEncoder.prepareRedirectInvocation(e,i,"<postMessage>",t,s),o=this.createPopup(n),a=new ReceiveOnlyPostMessageRpcClient(o,r,i);await a.init();try{const e=await a.listenFor(t);return a.close(),o.close(),e}catch(e){throw a.close(),o.close(),e}}createPopup(e){const t=window.open(e,"NimiqAccounts",this._options);if(!t)throw new Error("Failed to open popup");return t}}UrlEncodedPopupBehavior.DEFAULT_OPTIONS="";class IFrameRequestBehavior extends RequestBehavior{constructor(){super(BehaviorType.IFRAME),this._iframe=null,this._client=null}async request(e,t,s){if(this._iframe&&this._iframe.src!==`${e}${IFrameRequestBehavior.IFRAME_PATH_SUFFIX}`)throw new Error("Accounts Manager iframe is already opened with another endpoint");const r=RequestBehavior.getAllowedOrigin(e);if(this._iframe||(this._iframe=await this.createIFrame(e)),!this._iframe.contentWindow)throw new Error(`IFrame contentWindow is ${typeof this._iframe.contentWindow}`);return this._client||(this._client=new PostMessageRpcClient(this._iframe.contentWindow,r),await this._client.init()),await this._client.call(t,...s)}async createIFrame(e){return new Promise((t,s)=>{const r=document.createElement("iframe");r.name="NimiqAccountsIFrame",r.style.display="none",document.body.appendChild(r),r.src=`${e}${IFrameRequestBehavior.IFRAME_PATH_SUFFIX}`,r.onload=(()=>t(r)),r.onerror=s})}}IFrameRequestBehavior.IFRAME_PATH_SUFFIX="/iframe.html",function(e){e.LIST="list",e.MIGRATE="migrate",e.CHECKOUT="checkout",e.SIGN_MESSAGE="sign-message",e.SIGN_TRANSACTION="sign-transaction",e.ONBOARD="onboard",e.SIGNUP="signup",e.LOGIN="login",e.EXPORT="export",e.CHANGE_PASSPHRASE="change-passphrase",e.LOGOUT="logout",e.ADD_ACCOUNT="add-account",e.RENAME="rename",e.CHOOSE_ADDRESS="choose-address"}(RequestType||(RequestType={}));class AccountsClient{constructor(e=AccountsClient.DEFAULT_ENDPOINT,t){this._endpoint=e,this._defaultBehavior=t||new UrlEncodedPopupBehavior(`left=${window.innerWidth/2-500},top=50,width=1000,height=900,location=yes,dependent=yes`),this._iframeBehavior=new IFrameRequestBehavior,this._redirectClient=new RedirectRpcClient("",RequestBehavior.getAllowedOrigin(this._endpoint))}checkRedirectResponse(){return this._redirectClient.init()}on(e,t,s){this._redirectClient.onResponse(e,(e,s,r)=>t(e,JSON.parse(r)),(e,t,r)=>s&&s(e,JSON.parse(r)))}onboard(e,t=this._defaultBehavior){return this._request(t,RequestType.ONBOARD,[e])}signup(e,t=this._defaultBehavior){return this._request(t,RequestType.SIGNUP,[e])}login(e,t=this._defaultBehavior){return this._request(t,RequestType.LOGIN,[e])}chooseAddress(e,t=this._defaultBehavior){return this._request(t,RequestType.CHOOSE_ADDRESS,[e])}signTransaction(e,t=this._defaultBehavior){return this._request(t,RequestType.SIGN_TRANSACTION,[e])}checkout(e,t=this._defaultBehavior){return this._request(t,RequestType.CHECKOUT,[e])}logout(e,t=this._defaultBehavior){return this._request(t,RequestType.LOGOUT,[e])}export(e,t=this._defaultBehavior){return this._request(t,RequestType.EXPORT,[e])}changePassphrase(e,t=this._defaultBehavior){return this._request(t,RequestType.CHANGE_PASSPHRASE,[e])}addAccount(e,t=this._defaultBehavior){return this._request(t,RequestType.ADD_ACCOUNT,[e])}rename(e,t=this._defaultBehavior){return this._request(t,RequestType.RENAME,[e])}signMessage(e,t=this._defaultBehavior){return this._request(t,RequestType.SIGN_MESSAGE,[e])}migrate(e=this._defaultBehavior){return this._request(e,RequestType.MIGRATE,[{}])}list(e=this._iframeBehavior){return this._request(e,RequestType.LIST,[])}_request(e,t,s){return e.request(this._endpoint,t,s)}}AccountsClient.RequestType=RequestType,AccountsClient.RedirectRequestBehavior=RedirectRequestBehavior,AccountsClient.DEFAULT_ENDPOINT="https://safe-next.nimiq.com"===window.location.origin?"https://accounts.nimiq.com":"https://safe-next.nimiq-testnet.com"===window.location.origin?"https://accounts.nimiq-testnet.com":"http://localhost:8080";export default AccountsClient; | ||
class RandomUtils{static generateRandomId(){const e=new Uint32Array(1);return crypto.getRandomValues(e),e[0]}}var ResponseStatus;!function(e){e.OK="ok",e.ERROR="error"}(ResponseStatus||(ResponseStatus={}));const POSTMESSAGE_RETURN_URL="<postMessage>";class Base64{static byteLength(e){const[t,s]=Base64._getLengths(e);return Base64._byteLength(t,s)}static decode(e){Base64._initRevLookup();const[t,s]=Base64._getLengths(e),r=new Uint8Array(Base64._byteLength(t,s));let i=0;const n=s>0?t-4:t;let o=0;for(;o<n;o+=4){const t=Base64._revLookup[e.charCodeAt(o)]<<18|Base64._revLookup[e.charCodeAt(o+1)]<<12|Base64._revLookup[e.charCodeAt(o+2)]<<6|Base64._revLookup[e.charCodeAt(o+3)];r[i++]=t>>16&255,r[i++]=t>>8&255,r[i++]=255&t}if(2===s){const t=Base64._revLookup[e.charCodeAt(o)]<<2|Base64._revLookup[e.charCodeAt(o+1)]>>4;r[i++]=255&t}if(1===s){const t=Base64._revLookup[e.charCodeAt(o)]<<10|Base64._revLookup[e.charCodeAt(o+1)]<<4|Base64._revLookup[e.charCodeAt(o+2)]>>2;r[i++]=t>>8&255,r[i]=255&t}return r}static encode(e){const t=e.length,s=t%3,r=[];for(let i=0,n=t-s;i<n;i+=16383)r.push(Base64._encodeChunk(e,i,i+16383>n?n:i+16383));if(1===s){const s=e[t-1];r.push(Base64._lookup[s>>2]+Base64._lookup[s<<4&63]+"==")}else if(2===s){const s=(e[t-2]<<8)+e[t-1];r.push(Base64._lookup[s>>10]+Base64._lookup[s>>4&63]+Base64._lookup[s<<2&63]+"=")}return r.join("")}static encodeUrl(e){return Base64.encode(e).replace(/\//g,"_").replace(/\+/g,"-").replace(/=/g,".")}static decodeUrl(e){return Base64.decode(e.replace(/_/g,"/").replace(/-/g,"+").replace(/\./g,"="))}static _initRevLookup(){if(0===Base64._revLookup.length){Base64._revLookup=[];for(let e=0,t=Base64._lookup.length;e<t;e++)Base64._revLookup[Base64._lookup.charCodeAt(e)]=e;Base64._revLookup["-".charCodeAt(0)]=62,Base64._revLookup["_".charCodeAt(0)]=63}}static _getLengths(e){const t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");let s=e.indexOf("=");return-1===s&&(s=t),[s,s===t?0:4-s%4]}static _byteLength(e,t){return 3*(e+t)/4-t}static _tripletToBase64(e){return Base64._lookup[e>>18&63]+Base64._lookup[e>>12&63]+Base64._lookup[e>>6&63]+Base64._lookup[63&e]}static _encodeChunk(e,t,s){const r=[];for(let i=t;i<s;i+=3){const t=(e[i]<<16&16711680)+(e[i+1]<<8&65280)+(255&e[i+2]);r.push(Base64._tripletToBase64(t))}return r.join("")}}var ExtraJSONTypes,BehaviorType,RequestType;Base64._lookup="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",Base64._revLookup=[],function(e){e[e.UINT8_ARRAY=0]="UINT8_ARRAY"}(ExtraJSONTypes||(ExtraJSONTypes={}));class JSONUtils{static stringify(e){return JSON.stringify(e,JSONUtils._jsonifyType)}static parse(e){return JSON.parse(e,JSONUtils._parseType)}static _parseType(e,t){if(t&&t.hasOwnProperty&&t.hasOwnProperty(JSONUtils.TYPE_SYMBOL)&&t.hasOwnProperty(JSONUtils.VALUE_SYMBOL))switch(t[JSONUtils.TYPE_SYMBOL]){case ExtraJSONTypes.UINT8_ARRAY:return Base64.decode(t[JSONUtils.VALUE_SYMBOL])}return t}static _jsonifyType(e,t){return t instanceof Uint8Array?JSONUtils._typedObject(ExtraJSONTypes.UINT8_ARRAY,Base64.encode(t)):t}static _typedObject(e,t){const s={};return s[JSONUtils.TYPE_SYMBOL]=e,s[JSONUtils.VALUE_SYMBOL]=t,s}}JSONUtils.TYPE_SYMBOL="__",JSONUtils.VALUE_SYMBOL="v";class RequestIdStorage{constructor(e=!0){this._store=e?window.sessionStorage:null,this._validIds=new Map,e&&this._restoreIds()}static _decodeIds(e){const t=JSONUtils.parse(e),s=new Map;for(const e of Object.keys(t)){const r=parseInt(e,10);s.set(isNaN(r)?e:r,t[e])}return s}has(e){return this._validIds.has(e)}getCommand(e){const t=this._validIds.get(e);return t?t[0]:null}getState(e){const t=this._validIds.get(e);return t?t[1]:null}add(e,t,s=null){this._validIds.set(e,[t,s]),this._storeIds()}remove(e){this._validIds.delete(e),this._storeIds()}clear(){this._validIds.clear(),this._store&&this._store.removeItem(RequestIdStorage.KEY)}_encodeIds(){const e=Object.create(null);for(const[t,s]of this._validIds)e[t]=s;return JSONUtils.stringify(e)}_restoreIds(){const e=this._store.getItem(RequestIdStorage.KEY);e&&(this._validIds=RequestIdStorage._decodeIds(e))}_storeIds(){this._store&&this._store.setItem(RequestIdStorage.KEY,this._encodeIds())}}RequestIdStorage.KEY="rpcRequests";class UrlRpcEncoder{static receiveRedirectCommand(e){if(!document.referrer)return null;const t=new URLSearchParams(e.search),s=new URL(document.referrer);if(!t.has("command"))return null;if(!t.has("id"))return null;if(!t.has("returnURL"))return null;const r=t.get("returnURL")===POSTMESSAGE_RETURN_URL&&(window.opener||window.parent);if(!r){if(new URL(t.get("returnURL")).origin!==s.origin)return null}let i=[];if(t.has("args"))try{i=JSONUtils.parse(t.get("args"))}catch(e){}return i=Array.isArray(i)?i:[],{origin:s.origin,data:{id:parseInt(t.get("id"),10),command:t.get("command"),args:i},returnURL:t.get("returnURL"),source:r?window.opener||window.parent:null}}static receiveRedirectResponse(e){if(!document.referrer)return null;const t=new URLSearchParams(e.search),s=new URL(document.referrer);if(!t.has("status"))return null;if(!t.has("id"))return null;if(!t.has("result"))return null;const r=JSONUtils.parse(t.get("result")),i=t.get("status")===ResponseStatus.OK?ResponseStatus.OK:ResponseStatus.ERROR;return{origin:s.origin,data:{id:parseInt(t.get("id"),10),status:i,result:r}}}static prepareRedirectReply(e,t,s){const r=new URL(e.returnURL),i=r.searchParams;return i.set("status",t),i.set("result",JSONUtils.stringify(s)),i.set("id",e.id.toString()),r.href}static prepareRedirectInvocation(e,t,s,r,i){const n=new URL(e),o=n.searchParams;return o.set("id",t.toString()),o.set("returnURL",s),o.set("command",r),Array.isArray(i)&&o.set("args",JSONUtils.stringify(i)),n.href}}class RpcClient{constructor(e,t=!1){this._allowedOrigin=e,this._waitingRequests=new RequestIdStorage(t),this._responseHandlers=new Map,this._preserveRequests=!1}onResponse(e,t,s){this._responseHandlers.set(e,{resolve:t,reject:s})}_receive(e){if(!e.data||!e.data.status||!e.data.id||"*"!==this._allowedOrigin&&e.origin!==this._allowedOrigin)return;const t=e.data,s=this._getCallback(t.id),r=this._waitingRequests.getState(t.id);if(s){if(this._preserveRequests||(this._waitingRequests.remove(t.id),this._responseHandlers.delete(t.id)),console.debug("RpcClient RECEIVE",t),t.status===ResponseStatus.OK)s.resolve(t.result,t.id,r);else if(t.status===ResponseStatus.ERROR){const e=new Error(t.result.message);t.result.stack&&(e.stack=t.result.stack),t.result.name&&(e.name=t.result.name),s.reject(e,t.id,r)}}else console.warn("Unknown RPC response:",t)}_getCallback(e){if(this._responseHandlers.has(e))return this._responseHandlers.get(e);{const t=this._waitingRequests.getCommand(e);if(t)return this._responseHandlers.get(t)}}}class PostMessageRpcClient extends RpcClient{constructor(e,t){super(t),this._serverCloseCheckInterval=-1,this._target=e,this._connectionState=0,this._receiveListener=this._receive.bind(this)}async init(){2!==this._connectionState&&(await this._connect(),window.addEventListener("message",this._receiveListener),-1===this._serverCloseCheckInterval&&(this._serverCloseCheckInterval=window.setInterval(()=>this._checkIfServerClosed(),300)))}async call(e,...t){return this._call({command:e,args:t,id:RandomUtils.generateRandomId()})}async callAndPersist(e,...t){return this._call({command:e,args:t,id:RandomUtils.generateRandomId(),persistInUrl:!0})}close(){this._connectionState=0,window.removeEventListener("message",this._receiveListener),window.clearInterval(this._serverCloseCheckInterval),this._serverCloseCheckInterval=-1;for(const[e,{reject:t}]of this._responseHandlers){const s=this._waitingRequests.getState(e);t("Connection was closed","number"==typeof e?e:void 0,s)}this._waitingRequests.clear(),this._responseHandlers.clear(),this._target&&this._target.closed&&(this._target=null)}_receive(e){e.source===this._target&&super._receive(e)}async _call(e){if(!this._target||this._target.closed)throw new Error("Connection was closed.");if(2!==this._connectionState)throw new Error("Client is not connected, call init first");return new Promise((t,s)=>{this._responseHandlers.set(e.id,{resolve:t,reject:s}),this._waitingRequests.add(e.id,e.command),console.debug("RpcClient REQUEST",e.command,e.args),this._target.postMessage(e,this._allowedOrigin)})}_connect(){if(2!==this._connectionState)return this._connectionState=1,new Promise((e,t)=>{const s=t=>{const{source:r,origin:i,data:n}=t;if(r===this._target&&n.status===ResponseStatus.OK&&"pong"===n.result&&1===n.id&&("*"===this._allowedOrigin||i===this._allowedOrigin)){if(n.result.stack){const e=new Error(n.result.message);e.stack=n.result.stack,n.result.name&&(e.name=n.result.name),console.error(e)}window.removeEventListener("message",s),this._connectionState=2,console.log("RpcClient: Connection established"),e(!0)}};window.addEventListener("message",s);const r=()=>{if(2!==this._connectionState){if(0===this._connectionState||this._checkIfServerClosed())return window.removeEventListener("message",s),void t(new Error("Connection was closed"));try{this._target.postMessage({command:"ping",id:1},this._allowedOrigin)}catch(e){console.error(`postMessage failed: ${e}`)}window.setTimeout(r,100)}};window.setTimeout(r,100)})}_checkIfServerClosed(){return!(this._target&&!this._target.closed)&&(this.close(),!0)}}class RedirectRpcClient extends RpcClient{constructor(e,t,s=!0){super(t,!0),this._target=e,this._preserveRequests=s}async init(){const e=UrlRpcEncoder.receiveRedirectResponse(window.location);e?this._receive(e):UrlRpcEncoder.receiveRedirectCommand(window.location)||this._rejectOnBack()}close(){}call(e,t,...s){this.callAndSaveLocalState(e,null,t,...s)}callAndSaveLocalState(e,t,s,...r){const i=RandomUtils.generateRandomId(),n=UrlRpcEncoder.prepareRedirectInvocation(this._target,i,e,s,r);this._waitingRequests.add(i,s,t),history.replaceState({rpcRequestId:i},document.title),console.debug("RpcClient REQUEST",s,r),window.location.href=n}_rejectOnBack(){if(history.state&&history.state.rpcRequestId){const e=history.state.rpcRequestId,t=this._getCallback(e),s=this._waitingRequests.getState(e);if(t){this._preserveRequests||(this._waitingRequests.remove(e),this._responseHandlers.delete(e)),console.debug("RpcClient BACK");const r=new Error("Request aborted");t.reject(r,e,s)}}}}class RequestBehavior{static getAllowedOrigin(e){return new URL(e).origin}constructor(e){this._type=e}async request(e,t,s){throw new Error("Not implemented")}get type(){return this._type}}!function(e){e[e.REDIRECT=0]="REDIRECT",e[e.POPUP=1]="POPUP",e[e.IFRAME=2]="IFRAME"}(BehaviorType||(BehaviorType={}));class RedirectRequestBehavior extends RequestBehavior{static withLocalState(e){return new RedirectRequestBehavior(void 0,e)}constructor(e,t){super(BehaviorType.REDIRECT);const s=window.location;if(this._returnUrl=e||`${s.origin}${s.pathname}`,this._localState=t||{},void 0!==this._localState.__command)throw new Error("Invalid localState: Property '__command' is reserved")}async request(e,t,s){const r=RequestBehavior.getAllowedOrigin(e),i=new RedirectRpcClient(e,r);await i.init();const n=Object.assign({},this._localState,{__command:t});i.callAndSaveLocalState(this._returnUrl,n,t,...s)}}class PopupRequestBehavior extends RequestBehavior{constructor(e=PopupRequestBehavior.DEFAULT_OPTIONS){super(BehaviorType.POPUP),this._options=e}async request(e,t,s){const r=RequestBehavior.getAllowedOrigin(e),i=this.createPopup(e),n=new PostMessageRpcClient(i,r);await n.init();try{return await n.callAndPersist(t,...s)}catch(e){throw e}finally{n.close(),i.close()}}createPopup(e){const t=window.open(e,"NimiqAccounts",this._options);if(!t)throw new Error("Failed to open popup");return t}}PopupRequestBehavior.DEFAULT_OPTIONS="";class IFrameRequestBehavior extends RequestBehavior{constructor(){super(BehaviorType.IFRAME),this._iframe=null,this._client=null}async request(e,t,s){if(this._iframe&&this._iframe.src!==`${e}${IFrameRequestBehavior.IFRAME_PATH_SUFFIX}`)throw new Error("Accounts Manager iframe is already opened with another endpoint");const r=RequestBehavior.getAllowedOrigin(e);if(this._iframe||(this._iframe=await this.createIFrame(e)),!this._iframe.contentWindow)throw new Error(`IFrame contentWindow is ${typeof this._iframe.contentWindow}`);return this._client||(this._client=new PostMessageRpcClient(this._iframe.contentWindow,r),await this._client.init()),await this._client.call(t,...s)}async createIFrame(e){return new Promise((t,s)=>{const r=document.createElement("iframe");r.name="NimiqAccountsIFrame",r.style.display="none",document.body.appendChild(r),r.src=`${e}${IFrameRequestBehavior.IFRAME_PATH_SUFFIX}`,r.onload=(()=>t(r)),r.onerror=s})}}IFrameRequestBehavior.IFRAME_PATH_SUFFIX="/iframe.html",function(e){e.LIST="list",e.MIGRATE="migrate",e.CHECKOUT="checkout",e.SIGN_MESSAGE="sign-message",e.SIGN_TRANSACTION="sign-transaction",e.ONBOARD="onboard",e.SIGNUP="signup",e.LOGIN="login",e.EXPORT="export",e.CHANGE_PASSWORD="change-password",e.LOGOUT="logout",e.ADD_ADDRESS="add-address",e.RENAME="rename",e.CHOOSE_ADDRESS="choose-address"}(RequestType||(RequestType={}));class AccountsClient{constructor(e=AccountsClient.DEFAULT_ENDPOINT,t){this._endpoint=e,this._defaultBehavior=t||new PopupRequestBehavior(`left=${window.innerWidth/2-400},top=75,width=800,height=850,location=yes,dependent=yes`),this._iframeBehavior=new IFrameRequestBehavior,this._redirectClient=new RedirectRpcClient("",RequestBehavior.getAllowedOrigin(this._endpoint))}static get DEFAULT_ENDPOINT(){const e=location.origin.split(".");switch(e.shift(),e.join(".")){case"nimiq.com":return"https://accounts.nimiq.com";case"nimiq-testnet.com":return"https://accounts.nimiq-testnet.com";default:return"http://localhost:8080"}}checkRedirectResponse(){return this._redirectClient.init()}on(e,t,s){this._redirectClient.onResponse(e,(e,s,r)=>t(e,r),(e,t,r)=>s&&s(e,r))}onboard(e,t=this._defaultBehavior){return this._request(t,RequestType.ONBOARD,[e])}signup(e,t=this._defaultBehavior){return this._request(t,RequestType.SIGNUP,[e])}login(e,t=this._defaultBehavior){return this._request(t,RequestType.LOGIN,[e])}chooseAddress(e,t=this._defaultBehavior){return this._request(t,RequestType.CHOOSE_ADDRESS,[e])}signTransaction(e,t=this._defaultBehavior){return this._request(t,RequestType.SIGN_TRANSACTION,[e])}checkout(e,t=this._defaultBehavior){return this._request(t,RequestType.CHECKOUT,[e])}logout(e,t=this._defaultBehavior){return this._request(t,RequestType.LOGOUT,[e])}export(e,t=this._defaultBehavior){return this._request(t,RequestType.EXPORT,[e])}changePassword(e,t=this._defaultBehavior){return this._request(t,RequestType.CHANGE_PASSWORD,[e])}addAddress(e,t=this._defaultBehavior){return this._request(t,RequestType.ADD_ADDRESS,[e])}rename(e,t=this._defaultBehavior){return this._request(t,RequestType.RENAME,[e])}signMessage(e,t=this._defaultBehavior){return this._request(t,RequestType.SIGN_MESSAGE,[e])}migrate(e=this._defaultBehavior){return this._request(e,RequestType.MIGRATE,[{appName:"Accounts Client"}])}list(e=this._iframeBehavior){return this._request(e,RequestType.LIST,[])}_request(e,t,s){return e.request(this._endpoint,t,s)}}AccountsClient.RequestType=RequestType,AccountsClient.RedirectRequestBehavior=RedirectRequestBehavior;export default AccountsClient; |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.AccountsClient=t()}(this,function(){"use strict";class e{static generateRandomId(){const e=new Uint32Array(1);return crypto.getRandomValues(e),e[0]}}var t;!function(e){e.OK="ok",e.ERROR="error"}(t||(t={}));const s="<postMessage>";class r{static byteLength(e){const[t,s]=r._getLengths(e);return r._byteLength(t,s)}static decode(e){r._initRevLookup();const[t,s]=r._getLengths(e),n=new Uint8Array(r._byteLength(t,s));let i=0;const o=s>0?t-4:t;let a=0;for(;a<o;a+=4){const t=r._revLookup[e.charCodeAt(a)]<<18|r._revLookup[e.charCodeAt(a+1)]<<12|r._revLookup[e.charCodeAt(a+2)]<<6|r._revLookup[e.charCodeAt(a+3)];n[i++]=t>>16&255,n[i++]=t>>8&255,n[i++]=255&t}if(2===s){const t=r._revLookup[e.charCodeAt(a)]<<2|r._revLookup[e.charCodeAt(a+1)]>>4;n[i++]=255&t}if(1===s){const t=r._revLookup[e.charCodeAt(a)]<<10|r._revLookup[e.charCodeAt(a+1)]<<4|r._revLookup[e.charCodeAt(a+2)]>>2;n[i++]=t>>8&255,n[i]=255&t}return n}static encode(e){const t=e.length,s=t%3,n=[];for(let i=0,o=t-s;i<o;i+=16383)n.push(r._encodeChunk(e,i,i+16383>o?o:i+16383));if(1===s){const s=e[t-1];n.push(r._lookup[s>>2]+r._lookup[s<<4&63]+"==")}else if(2===s){const s=(e[t-2]<<8)+e[t-1];n.push(r._lookup[s>>10]+r._lookup[s>>4&63]+r._lookup[s<<2&63]+"=")}return n.join("")}static encodeUrl(e){return r.encode(e).replace(/\//g,"_").replace(/\+/g,"-").replace(/=/g,".")}static decodeUrl(e){return r.decode(e.replace(/_/g,"/").replace(/-/g,"+").replace(/\./g,"="))}static _initRevLookup(){if(0===r._revLookup.length){r._revLookup=[];for(let e=0,t=r._lookup.length;e<t;e++)r._revLookup[r._lookup.charCodeAt(e)]=e;r._revLookup["-".charCodeAt(0)]=62,r._revLookup["_".charCodeAt(0)]=63}}static _getLengths(e){const t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");let s=e.indexOf("=");return-1===s&&(s=t),[s,s===t?0:4-s%4]}static _byteLength(e,t){return 3*(e+t)/4-t}static _tripletToBase64(e){return r._lookup[e>>18&63]+r._lookup[e>>12&63]+r._lookup[e>>6&63]+r._lookup[63&e]}static _encodeChunk(e,t,s){const n=[];for(let i=t;i<s;i+=3){const t=(e[i]<<16&16711680)+(e[i+1]<<8&65280)+(255&e[i+2]);n.push(r._tripletToBase64(t))}return n.join("")}}var n,i,o;r._lookup="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r._revLookup=[],function(e){e[e.UINT8_ARRAY=0]="UINT8_ARRAY"}(n||(n={}));class a{static stringify(e){return JSON.stringify(e,a._jsonifyType)}static parse(e){return JSON.parse(e,a._parseType)}static _parseType(e,t){if(t&&t.hasOwnProperty&&t.hasOwnProperty(a.TYPE_SYMBOL)&&t.hasOwnProperty(a.VALUE_SYMBOL))switch(t[a.TYPE_SYMBOL]){case n.UINT8_ARRAY:return r.decode(t[a.VALUE_SYMBOL])}return t}static _jsonifyType(e,t){return t instanceof Uint8Array?a._typedObject(n.UINT8_ARRAY,r.encode(t)):t}static _typedObject(e,t){const s={};return s[a.TYPE_SYMBOL]=e,s[a.VALUE_SYMBOL]=t,s}}a.TYPE_SYMBOL="__",a.VALUE_SYMBOL="v";class c{constructor(e=!0){this._store=e?window.sessionStorage:null,this._validIds=new Map,e&&this._restoreIds()}static _decodeIds(e){const t=a.parse(e),s=new Map;for(const e of Object.keys(t)){const r=parseInt(e,10);s.set(isNaN(r)?e:r,t[e])}return s}has(e){return this._validIds.has(e)}getCommand(e){const t=this._validIds.get(e);return t?t[0]:null}getState(e){const t=this._validIds.get(e);return t?t[1]:null}add(e,t,s=null){this._validIds.set(e,[t,s]),this._storeIds()}remove(e){this._validIds.delete(e),this._storeIds()}clear(){this._validIds.clear(),this._store&&this._store.removeItem(c.KEY)}_encodeIds(){const e=Object.create(null);for(const[t,s]of this._validIds)e[t]=s;return a.stringify(e)}_restoreIds(){const e=this._store.getItem(c.KEY);e&&(this._validIds=c._decodeIds(e))}_storeIds(){this._store&&this._store.setItem(c.KEY,this._encodeIds())}}c.KEY="rpcRequests";class d{static receiveRedirectCommand(e){if(!document.referrer)return null;const t=new URLSearchParams(e.search),r=new URL(document.referrer);if(!t.has("command"))return null;if(!t.has("id"))return null;if(!t.has("returnURL"))return null;const n=t.get("returnURL")===s&&window.opener;if(!n){if(new URL(t.get("returnURL")).origin!==r.origin)return null}let i=[];if(t.has("args"))try{i=a.parse(t.get("args"))}catch(e){}return i=Array.isArray(i)?i:[],{origin:r.origin,data:{id:parseInt(t.get("id"),10),command:t.get("command"),args:i},returnURL:t.get("returnURL"),source:n?window.opener:null}}static receiveRedirectResponse(e){if(!document.referrer)return null;const s=new URLSearchParams(e.search),r=new URL(document.referrer);if(!s.has("status"))return null;if(!s.has("id"))return null;if(!s.has("result"))return null;const n=a.parse(s.get("result")),i=s.get("status")===t.OK?t.OK:t.ERROR;return{origin:r.origin,data:{id:parseInt(s.get("id"),10),status:i,result:n}}}static prepareRedirectReply(e,t,s){const r=new URLSearchParams;return r.set("status",t),r.set("result",a.stringify(s)),r.set("id",e.id.toString()),`${e.returnURL}${new URL(e.returnURL).search.length>0?"&":"?"}${r.toString()}`}static prepareRedirectInvocation(e,t,s,r,n){const i=new URLSearchParams;return i.set("id",t.toString()),i.set("returnURL",s),i.set("command",r),Array.isArray(n)&&i.set("args",a.stringify(n)),`${e}?${i.toString()}`}}class l{constructor(e,t=!1){this._allowedOrigin=e,this._waitingRequests=new c(t),this._responseHandlers=new Map}onResponse(e,t,s){this._responseHandlers.set(e,{resolve:t,reject:s})}_receive(e){if(!e.data||!e.data.status||!e.data.id||"*"!==this._allowedOrigin&&e.origin!==this._allowedOrigin)return;const s=e.data,r=this._getCallback(s.id),n=this._waitingRequests.getState(s.id);if(r){if(console.debug("RpcClient RECEIVE",s),s.status===t.OK)r.resolve(s.result,s.id,n);else if(s.status===t.ERROR){const e=new Error(s.result.message);s.result.stack&&(e.stack=s.result.stack),s.result.name&&(e.name=s.result.name),r.reject(e,s.id,n)}}else console.warn("Unknown RPC response:",s)}_getCallback(e){if(this._responseHandlers.has(e))return this._responseHandlers.get(e);{const t=this._waitingRequests.getCommand(e);if(t)return this._responseHandlers.get(t)}}}class u extends l{constructor(e,t){super(t),this._target=e,this._connected=!1,this._receiveListener=this._receive.bind(this)}async init(){await this._connect(),window.addEventListener("message",this._receiveListener)}async call(t,...s){if(!this._connected)throw new Error("Client is not connected, call init first");return new Promise((r,n)=>{const i={command:t,args:s,id:e.generateRandomId()};this._responseHandlers.set(i.id,{resolve:r,reject:n}),this._waitingRequests.add(i.id,t);const o=()=>{this._target.closed&&n(new Error("Window was closed")),setTimeout(o,500)};setTimeout(o,500),console.debug("RpcClient REQUEST",t,s),this._target.postMessage(i,this._allowedOrigin)})}close(){window.removeEventListener("message",this._receiveListener),this._connected=!1}_connect(){return new Promise((e,s)=>{const r=s=>{const{source:n,origin:i,data:o}=s;if(n===this._target&&o.status===t.OK&&"pong"===o.result&&1===o.id&&("*"===this._allowedOrigin||i===this._allowedOrigin)){if(o.result.stack){const e=new Error(o.result.message);e.stack=o.result.stack,o.result.name&&(e.name=o.result.name),console.error(e)}window.removeEventListener("message",r),this._connected=!0,console.log("RpcClient: Connection established"),window.addEventListener("message",this._receiveListener),e(!0)}};window.addEventListener("message",r);let n=0;const i=setTimeout(()=>{window.removeEventListener("message",r),clearTimeout(n),s(new Error("Connection timeout"))},1e4),o=()=>{if(this._connected)clearTimeout(i);else{try{this._target.postMessage({command:"ping",id:1},this._allowedOrigin)}catch(e){console.error(`postMessage failed: ${e}`)}n=setTimeout(o,1e3)}};n=setTimeout(o,100)})}}class h extends u{constructor(e,t,s){super(e,t),this._requestId=s}async init(){window.addEventListener("message",this._receiveListener)}async listenFor(e){return new Promise((t,s)=>{this._responseHandlers.set(this._requestId,{resolve:t,reject:s}),this._waitingRequests.add(this._requestId,e);const r=()=>{this._target.closed&&s(new Error("Window was closed")),setTimeout(r,500)};setTimeout(r,500)})}}class _ extends l{constructor(e,t){super(t,!0),this._target=e}async init(){const e=d.receiveRedirectResponse(window.location);e?this._receive(e):d.receiveRedirectCommand(window.location)||this._rejectOnBack()}close(){}call(e,t,...s){this.callAndSaveLocalState(e,null,t,...s)}callAndSaveLocalState(t,s,r,...n){const i=e.generateRandomId(),o=d.prepareRedirectInvocation(this._target,i,t,r,n);this._waitingRequests.add(i,r,s),history.replaceState({rpcRequestId:i},document.title),console.debug("RpcClient REQUEST",r,n),window.location.href=o}_rejectOnBack(){if(history.state&&history.state.rpcRequestId){const e=history.state.rpcRequestId,t=this._getCallback(e),s=this._waitingRequests.getState(e);if(t){console.debug("RpcClient BACK");const r=new Error("Request aborted");t.reject(r,e,s)}}}}class p{static getAllowedOrigin(e){return new URL(e).origin}constructor(e){this._type=e}async request(e,t,s){throw new Error("Not implemented")}get type(){return this._type}}!function(e){e[e.REDIRECT=0]="REDIRECT",e[e.POPUP=1]="POPUP",e[e.IFRAME=2]="IFRAME"}(i||(i={}));class g extends p{static withLocalState(e){return new g(void 0,e)}constructor(e,t){super(i.REDIRECT);const s=window.location;if(this._returnUrl=e||`${s.origin}${s.pathname}`,this._localState=t||{},void 0!==this._localState.__command)throw new Error("Invalid localState: Property '__command' is reserved")}async request(e,t,s){const r=p.getAllowedOrigin(e),n=new _(e,r);await n.init();const i=Object.assign({},this._localState,{__command:t});console.log("state",i),n.callAndSaveLocalState(this._returnUrl,JSON.stringify(i),t,...s)}}class w extends p{constructor(e=w.DEFAULT_OPTIONS){super(i.POPUP),this._options=e}async request(t,s,r){const n=p.getAllowedOrigin(t),i=e.generateRandomId(),o=d.prepareRedirectInvocation(t,i,"<postMessage>",s,r),a=this.createPopup(o),c=new h(a,n,i);await c.init();try{const e=await c.listenFor(s);return c.close(),a.close(),e}catch(e){throw c.close(),a.close(),e}}createPopup(e){const t=window.open(e,"NimiqAccounts",this._options);if(!t)throw new Error("Failed to open popup");return t}}w.DEFAULT_OPTIONS="";class m extends p{constructor(){super(i.IFRAME),this._iframe=null,this._client=null}async request(e,t,s){if(this._iframe&&this._iframe.src!==`${e}${m.IFRAME_PATH_SUFFIX}`)throw new Error("Accounts Manager iframe is already opened with another endpoint");const r=p.getAllowedOrigin(e);if(this._iframe||(this._iframe=await this.createIFrame(e)),!this._iframe.contentWindow)throw new Error(`IFrame contentWindow is ${typeof this._iframe.contentWindow}`);return this._client||(this._client=new u(this._iframe.contentWindow,r),await this._client.init()),await this._client.call(t,...s)}async createIFrame(e){return new Promise((t,s)=>{const r=document.createElement("iframe");r.name="NimiqAccountsIFrame",r.style.display="none",document.body.appendChild(r),r.src=`${e}${m.IFRAME_PATH_SUFFIX}`,r.onload=(()=>t(r)),r.onerror=s})}}m.IFRAME_PATH_SUFFIX="/iframe.html",function(e){e.LIST="list",e.MIGRATE="migrate",e.CHECKOUT="checkout",e.SIGN_MESSAGE="sign-message",e.SIGN_TRANSACTION="sign-transaction",e.ONBOARD="onboard",e.SIGNUP="signup",e.LOGIN="login",e.EXPORT="export",e.CHANGE_PASSPHRASE="change-passphrase",e.LOGOUT="logout",e.ADD_ACCOUNT="add-account",e.RENAME="rename",e.CHOOSE_ADDRESS="choose-address"}(o||(o={}));class R{constructor(e=R.DEFAULT_ENDPOINT,t){this._endpoint=e,this._defaultBehavior=t||new w(`left=${window.innerWidth/2-500},top=50,width=1000,height=900,location=yes,dependent=yes`),this._iframeBehavior=new m,this._redirectClient=new _("",p.getAllowedOrigin(this._endpoint))}checkRedirectResponse(){return this._redirectClient.init()}on(e,t,s){this._redirectClient.onResponse(e,(e,s,r)=>t(e,JSON.parse(r)),(e,t,r)=>s&&s(e,JSON.parse(r)))}onboard(e,t=this._defaultBehavior){return this._request(t,o.ONBOARD,[e])}signup(e,t=this._defaultBehavior){return this._request(t,o.SIGNUP,[e])}login(e,t=this._defaultBehavior){return this._request(t,o.LOGIN,[e])}chooseAddress(e,t=this._defaultBehavior){return this._request(t,o.CHOOSE_ADDRESS,[e])}signTransaction(e,t=this._defaultBehavior){return this._request(t,o.SIGN_TRANSACTION,[e])}checkout(e,t=this._defaultBehavior){return this._request(t,o.CHECKOUT,[e])}logout(e,t=this._defaultBehavior){return this._request(t,o.LOGOUT,[e])}export(e,t=this._defaultBehavior){return this._request(t,o.EXPORT,[e])}changePassphrase(e,t=this._defaultBehavior){return this._request(t,o.CHANGE_PASSPHRASE,[e])}addAccount(e,t=this._defaultBehavior){return this._request(t,o.ADD_ACCOUNT,[e])}rename(e,t=this._defaultBehavior){return this._request(t,o.RENAME,[e])}signMessage(e,t=this._defaultBehavior){return this._request(t,o.SIGN_MESSAGE,[e])}migrate(e=this._defaultBehavior){return this._request(e,o.MIGRATE,[{}])}list(e=this._iframeBehavior){return this._request(e,o.LIST,[])}_request(e,t,s){return e.request(this._endpoint,t,s)}}return R.RequestType=o,R.RedirectRequestBehavior=g,R.DEFAULT_ENDPOINT="https://safe-next.nimiq.com"===window.location.origin?"https://accounts.nimiq.com":"https://safe-next.nimiq-testnet.com"===window.location.origin?"https://accounts.nimiq-testnet.com":"http://localhost:8080",R}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.AccountsClient=t()}(this,function(){"use strict";class e{static generateRandomId(){const e=new Uint32Array(1);return crypto.getRandomValues(e),e[0]}}var t;!function(e){e.OK="ok",e.ERROR="error"}(t||(t={}));const s="<postMessage>";class r{static byteLength(e){const[t,s]=r._getLengths(e);return r._byteLength(t,s)}static decode(e){r._initRevLookup();const[t,s]=r._getLengths(e),n=new Uint8Array(r._byteLength(t,s));let i=0;const o=s>0?t-4:t;let a=0;for(;a<o;a+=4){const t=r._revLookup[e.charCodeAt(a)]<<18|r._revLookup[e.charCodeAt(a+1)]<<12|r._revLookup[e.charCodeAt(a+2)]<<6|r._revLookup[e.charCodeAt(a+3)];n[i++]=t>>16&255,n[i++]=t>>8&255,n[i++]=255&t}if(2===s){const t=r._revLookup[e.charCodeAt(a)]<<2|r._revLookup[e.charCodeAt(a+1)]>>4;n[i++]=255&t}if(1===s){const t=r._revLookup[e.charCodeAt(a)]<<10|r._revLookup[e.charCodeAt(a+1)]<<4|r._revLookup[e.charCodeAt(a+2)]>>2;n[i++]=t>>8&255,n[i]=255&t}return n}static encode(e){const t=e.length,s=t%3,n=[];for(let i=0,o=t-s;i<o;i+=16383)n.push(r._encodeChunk(e,i,i+16383>o?o:i+16383));if(1===s){const s=e[t-1];n.push(r._lookup[s>>2]+r._lookup[s<<4&63]+"==")}else if(2===s){const s=(e[t-2]<<8)+e[t-1];n.push(r._lookup[s>>10]+r._lookup[s>>4&63]+r._lookup[s<<2&63]+"=")}return n.join("")}static encodeUrl(e){return r.encode(e).replace(/\//g,"_").replace(/\+/g,"-").replace(/=/g,".")}static decodeUrl(e){return r.decode(e.replace(/_/g,"/").replace(/-/g,"+").replace(/\./g,"="))}static _initRevLookup(){if(0===r._revLookup.length){r._revLookup=[];for(let e=0,t=r._lookup.length;e<t;e++)r._revLookup[r._lookup.charCodeAt(e)]=e;r._revLookup["-".charCodeAt(0)]=62,r._revLookup["_".charCodeAt(0)]=63}}static _getLengths(e){const t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");let s=e.indexOf("=");return-1===s&&(s=t),[s,s===t?0:4-s%4]}static _byteLength(e,t){return 3*(e+t)/4-t}static _tripletToBase64(e){return r._lookup[e>>18&63]+r._lookup[e>>12&63]+r._lookup[e>>6&63]+r._lookup[63&e]}static _encodeChunk(e,t,s){const n=[];for(let i=t;i<s;i+=3){const t=(e[i]<<16&16711680)+(e[i+1]<<8&65280)+(255&e[i+2]);n.push(r._tripletToBase64(t))}return n.join("")}}var n,i,o;r._lookup="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r._revLookup=[],function(e){e[e.UINT8_ARRAY=0]="UINT8_ARRAY"}(n||(n={}));class a{static stringify(e){return JSON.stringify(e,a._jsonifyType)}static parse(e){return JSON.parse(e,a._parseType)}static _parseType(e,t){if(t&&t.hasOwnProperty&&t.hasOwnProperty(a.TYPE_SYMBOL)&&t.hasOwnProperty(a.VALUE_SYMBOL))switch(t[a.TYPE_SYMBOL]){case n.UINT8_ARRAY:return r.decode(t[a.VALUE_SYMBOL])}return t}static _jsonifyType(e,t){return t instanceof Uint8Array?a._typedObject(n.UINT8_ARRAY,r.encode(t)):t}static _typedObject(e,t){const s={};return s[a.TYPE_SYMBOL]=e,s[a.VALUE_SYMBOL]=t,s}}a.TYPE_SYMBOL="__",a.VALUE_SYMBOL="v";class c{constructor(e=!0){this._store=e?window.sessionStorage:null,this._validIds=new Map,e&&this._restoreIds()}static _decodeIds(e){const t=a.parse(e),s=new Map;for(const e of Object.keys(t)){const r=parseInt(e,10);s.set(isNaN(r)?e:r,t[e])}return s}has(e){return this._validIds.has(e)}getCommand(e){const t=this._validIds.get(e);return t?t[0]:null}getState(e){const t=this._validIds.get(e);return t?t[1]:null}add(e,t,s=null){this._validIds.set(e,[t,s]),this._storeIds()}remove(e){this._validIds.delete(e),this._storeIds()}clear(){this._validIds.clear(),this._store&&this._store.removeItem(c.KEY)}_encodeIds(){const e=Object.create(null);for(const[t,s]of this._validIds)e[t]=s;return a.stringify(e)}_restoreIds(){const e=this._store.getItem(c.KEY);e&&(this._validIds=c._decodeIds(e))}_storeIds(){this._store&&this._store.setItem(c.KEY,this._encodeIds())}}c.KEY="rpcRequests";class l{static receiveRedirectCommand(e){if(!document.referrer)return null;const t=new URLSearchParams(e.search),r=new URL(document.referrer);if(!t.has("command"))return null;if(!t.has("id"))return null;if(!t.has("returnURL"))return null;const n=t.get("returnURL")===s&&(window.opener||window.parent);if(!n){if(new URL(t.get("returnURL")).origin!==r.origin)return null}let i=[];if(t.has("args"))try{i=a.parse(t.get("args"))}catch(e){}return i=Array.isArray(i)?i:[],{origin:r.origin,data:{id:parseInt(t.get("id"),10),command:t.get("command"),args:i},returnURL:t.get("returnURL"),source:n?window.opener||window.parent:null}}static receiveRedirectResponse(e){if(!document.referrer)return null;const s=new URLSearchParams(e.search),r=new URL(document.referrer);if(!s.has("status"))return null;if(!s.has("id"))return null;if(!s.has("result"))return null;const n=a.parse(s.get("result")),i=s.get("status")===t.OK?t.OK:t.ERROR;return{origin:r.origin,data:{id:parseInt(s.get("id"),10),status:i,result:n}}}static prepareRedirectReply(e,t,s){const r=new URL(e.returnURL),n=r.searchParams;return n.set("status",t),n.set("result",a.stringify(s)),n.set("id",e.id.toString()),r.href}static prepareRedirectInvocation(e,t,s,r,n){const i=new URL(e),o=i.searchParams;return o.set("id",t.toString()),o.set("returnURL",s),o.set("command",r),Array.isArray(n)&&o.set("args",a.stringify(n)),i.href}}class d{constructor(e,t=!1){this._allowedOrigin=e,this._waitingRequests=new c(t),this._responseHandlers=new Map,this._preserveRequests=!1}onResponse(e,t,s){this._responseHandlers.set(e,{resolve:t,reject:s})}_receive(e){if(!e.data||!e.data.status||!e.data.id||"*"!==this._allowedOrigin&&e.origin!==this._allowedOrigin)return;const s=e.data,r=this._getCallback(s.id),n=this._waitingRequests.getState(s.id);if(r){if(this._preserveRequests||(this._waitingRequests.remove(s.id),this._responseHandlers.delete(s.id)),console.debug("RpcClient RECEIVE",s),s.status===t.OK)r.resolve(s.result,s.id,n);else if(s.status===t.ERROR){const e=new Error(s.result.message);s.result.stack&&(e.stack=s.result.stack),s.result.name&&(e.name=s.result.name),r.reject(e,s.id,n)}}else console.warn("Unknown RPC response:",s)}_getCallback(e){if(this._responseHandlers.has(e))return this._responseHandlers.get(e);{const t=this._waitingRequests.getCommand(e);if(t)return this._responseHandlers.get(t)}}}class u extends d{constructor(e,t){super(t),this._serverCloseCheckInterval=-1,this._target=e,this._connectionState=0,this._receiveListener=this._receive.bind(this)}async init(){2!==this._connectionState&&(await this._connect(),window.addEventListener("message",this._receiveListener),-1===this._serverCloseCheckInterval&&(this._serverCloseCheckInterval=window.setInterval(()=>this._checkIfServerClosed(),300)))}async call(t,...s){return this._call({command:t,args:s,id:e.generateRandomId()})}async callAndPersist(t,...s){return this._call({command:t,args:s,id:e.generateRandomId(),persistInUrl:!0})}close(){this._connectionState=0,window.removeEventListener("message",this._receiveListener),window.clearInterval(this._serverCloseCheckInterval),this._serverCloseCheckInterval=-1;for(const[e,{reject:t}]of this._responseHandlers){const s=this._waitingRequests.getState(e);t("Connection was closed","number"==typeof e?e:void 0,s)}this._waitingRequests.clear(),this._responseHandlers.clear(),this._target&&this._target.closed&&(this._target=null)}_receive(e){e.source===this._target&&super._receive(e)}async _call(e){if(!this._target||this._target.closed)throw new Error("Connection was closed.");if(2!==this._connectionState)throw new Error("Client is not connected, call init first");return new Promise((t,s)=>{this._responseHandlers.set(e.id,{resolve:t,reject:s}),this._waitingRequests.add(e.id,e.command),console.debug("RpcClient REQUEST",e.command,e.args),this._target.postMessage(e,this._allowedOrigin)})}_connect(){if(2!==this._connectionState)return this._connectionState=1,new Promise((e,s)=>{const r=s=>{const{source:n,origin:i,data:o}=s;if(n===this._target&&o.status===t.OK&&"pong"===o.result&&1===o.id&&("*"===this._allowedOrigin||i===this._allowedOrigin)){if(o.result.stack){const e=new Error(o.result.message);e.stack=o.result.stack,o.result.name&&(e.name=o.result.name),console.error(e)}window.removeEventListener("message",r),this._connectionState=2,console.log("RpcClient: Connection established"),e(!0)}};window.addEventListener("message",r);const n=()=>{if(2!==this._connectionState){if(0===this._connectionState||this._checkIfServerClosed())return window.removeEventListener("message",r),void s(new Error("Connection was closed"));try{this._target.postMessage({command:"ping",id:1},this._allowedOrigin)}catch(e){console.error(`postMessage failed: ${e}`)}window.setTimeout(n,100)}};window.setTimeout(n,100)})}_checkIfServerClosed(){return!(this._target&&!this._target.closed)&&(this.close(),!0)}}class h extends d{constructor(e,t,s=!0){super(t,!0),this._target=e,this._preserveRequests=s}async init(){const e=l.receiveRedirectResponse(window.location);e?this._receive(e):l.receiveRedirectCommand(window.location)||this._rejectOnBack()}close(){}call(e,t,...s){this.callAndSaveLocalState(e,null,t,...s)}callAndSaveLocalState(t,s,r,...n){const i=e.generateRandomId(),o=l.prepareRedirectInvocation(this._target,i,t,r,n);this._waitingRequests.add(i,r,s),history.replaceState({rpcRequestId:i},document.title),console.debug("RpcClient REQUEST",r,n),window.location.href=o}_rejectOnBack(){if(history.state&&history.state.rpcRequestId){const e=history.state.rpcRequestId,t=this._getCallback(e),s=this._waitingRequests.getState(e);if(t){this._preserveRequests||(this._waitingRequests.remove(e),this._responseHandlers.delete(e)),console.debug("RpcClient BACK");const r=new Error("Request aborted");t.reject(r,e,s)}}}}class _{static getAllowedOrigin(e){return new URL(e).origin}constructor(e){this._type=e}async request(e,t,s){throw new Error("Not implemented")}get type(){return this._type}}!function(e){e[e.REDIRECT=0]="REDIRECT",e[e.POPUP=1]="POPUP",e[e.IFRAME=2]="IFRAME"}(i||(i={}));class p extends _{static withLocalState(e){return new p(void 0,e)}constructor(e,t){super(i.REDIRECT);const s=window.location;if(this._returnUrl=e||`${s.origin}${s.pathname}`,this._localState=t||{},void 0!==this._localState.__command)throw new Error("Invalid localState: Property '__command' is reserved")}async request(e,t,s){const r=_.getAllowedOrigin(e),n=new h(e,r);await n.init();const i=Object.assign({},this._localState,{__command:t});n.callAndSaveLocalState(this._returnUrl,i,t,...s)}}class g extends _{constructor(e=g.DEFAULT_OPTIONS){super(i.POPUP),this._options=e}async request(e,t,s){const r=_.getAllowedOrigin(e),n=this.createPopup(e),i=new u(n,r);await i.init();try{return await i.callAndPersist(t,...s)}catch(e){throw e}finally{i.close(),n.close()}}createPopup(e){const t=window.open(e,"NimiqAccounts",this._options);if(!t)throw new Error("Failed to open popup");return t}}g.DEFAULT_OPTIONS="";class w extends _{constructor(){super(i.IFRAME),this._iframe=null,this._client=null}async request(e,t,s){if(this._iframe&&this._iframe.src!==`${e}${w.IFRAME_PATH_SUFFIX}`)throw new Error("Accounts Manager iframe is already opened with another endpoint");const r=_.getAllowedOrigin(e);if(this._iframe||(this._iframe=await this.createIFrame(e)),!this._iframe.contentWindow)throw new Error(`IFrame contentWindow is ${typeof this._iframe.contentWindow}`);return this._client||(this._client=new u(this._iframe.contentWindow,r),await this._client.init()),await this._client.call(t,...s)}async createIFrame(e){return new Promise((t,s)=>{const r=document.createElement("iframe");r.name="NimiqAccountsIFrame",r.style.display="none",document.body.appendChild(r),r.src=`${e}${w.IFRAME_PATH_SUFFIX}`,r.onload=(()=>t(r)),r.onerror=s})}}w.IFRAME_PATH_SUFFIX="/iframe.html",function(e){e.LIST="list",e.MIGRATE="migrate",e.CHECKOUT="checkout",e.SIGN_MESSAGE="sign-message",e.SIGN_TRANSACTION="sign-transaction",e.ONBOARD="onboard",e.SIGNUP="signup",e.LOGIN="login",e.EXPORT="export",e.CHANGE_PASSWORD="change-password",e.LOGOUT="logout",e.ADD_ADDRESS="add-address",e.RENAME="rename",e.CHOOSE_ADDRESS="choose-address"}(o||(o={}));class m{constructor(e=m.DEFAULT_ENDPOINT,t){this._endpoint=e,this._defaultBehavior=t||new g(`left=${window.innerWidth/2-400},top=75,width=800,height=850,location=yes,dependent=yes`),this._iframeBehavior=new w,this._redirectClient=new h("",_.getAllowedOrigin(this._endpoint))}static get DEFAULT_ENDPOINT(){const e=location.origin.split(".");switch(e.shift(),e.join(".")){case"nimiq.com":return"https://accounts.nimiq.com";case"nimiq-testnet.com":return"https://accounts.nimiq-testnet.com";default:return"http://localhost:8080"}}checkRedirectResponse(){return this._redirectClient.init()}on(e,t,s){this._redirectClient.onResponse(e,(e,s,r)=>t(e,r),(e,t,r)=>s&&s(e,r))}onboard(e,t=this._defaultBehavior){return this._request(t,o.ONBOARD,[e])}signup(e,t=this._defaultBehavior){return this._request(t,o.SIGNUP,[e])}login(e,t=this._defaultBehavior){return this._request(t,o.LOGIN,[e])}chooseAddress(e,t=this._defaultBehavior){return this._request(t,o.CHOOSE_ADDRESS,[e])}signTransaction(e,t=this._defaultBehavior){return this._request(t,o.SIGN_TRANSACTION,[e])}checkout(e,t=this._defaultBehavior){return this._request(t,o.CHECKOUT,[e])}logout(e,t=this._defaultBehavior){return this._request(t,o.LOGOUT,[e])}export(e,t=this._defaultBehavior){return this._request(t,o.EXPORT,[e])}changePassword(e,t=this._defaultBehavior){return this._request(t,o.CHANGE_PASSWORD,[e])}addAddress(e,t=this._defaultBehavior){return this._request(t,o.ADD_ADDRESS,[e])}rename(e,t=this._defaultBehavior){return this._request(t,o.RENAME,[e])}signMessage(e,t=this._defaultBehavior){return this._request(t,o.SIGN_MESSAGE,[e])}migrate(e=this._defaultBehavior){return this._request(e,o.MIGRATE,[{appName:"Accounts Client"}])}list(e=this._iframeBehavior){return this._request(e,o.LIST,[])}_request(e,t,s){return e.request(this._endpoint,t,s)}}return m.RequestType=o,m.RedirectRequestBehavior=p,m}); |
{ | ||
"name": "@nimiq/accounts-client", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"main": "dist/AccountsClient.umd.js", | ||
@@ -11,3 +11,3 @@ "module": "dist/AccountsClient.es.js", | ||
"dependencies": { | ||
"@nimiq/rpc": "^0.1.0-beta.5" | ||
"@nimiq/rpc": "^0.1.4" | ||
}, | ||
@@ -19,3 +19,3 @@ "devDependencies": { | ||
"tslint": "^5.11.0", | ||
"typescript": "^3.0.3" | ||
"typescript": "^3.3.3333" | ||
}, | ||
@@ -22,0 +22,0 @@ "files": [ |
400
README.md
@@ -9,20 +9,26 @@ # Nimiq Accounts Manager <!-- omit in toc --> | ||
- [The Accounts Client library](#the-accounts-client-library) | ||
- [Installation](#installation) | ||
- [Initialization](#initialization) | ||
- [Usage](#usage) | ||
- [Using top-level redirects](#using-top-level-redirects) | ||
- [API Methods](#api-methods) | ||
- [Checkout](#checkout) | ||
- [Choose Address](#choose-address) | ||
- [Sign transaction](#sign-transaction) | ||
- [Signup](#signup) | ||
- [Login](#login) | ||
- [Logout](#logout) | ||
- [Export](#export) | ||
- [Listening for redirect responses](#listening-for-redirect-responses) | ||
- [Installation](#installation) | ||
- [Initialization](#initialization) | ||
- [Usage](#usage) | ||
- [Using top-level redirects](#using-top-level-redirects) | ||
- [API Methods](#api-methods) | ||
- [Checkout](#checkout) | ||
- [Choose Address](#choose-address) | ||
- [Sign Transaction](#sign-transaction) | ||
- [Signup](#signup) | ||
- [Login](#login) | ||
- [Onboard](#onboard) | ||
- [Logout](#logout) | ||
- [Export](#export) | ||
- [Change Password](#change-password) | ||
- [Add Address](#add-address) | ||
- [Rename](#rename) | ||
- [Sign Message](#sign-message) | ||
- [Listening for redirect responses](#listening-for-redirect-responses) | ||
- [Running your own Accounts Manager](#running-your-own-accounts-manager) | ||
- [Contribute](#contribute) | ||
- [Setup](#setup) | ||
- [Run](#run) | ||
- [Build](#build) | ||
- [Setup](#setup) | ||
- [Run](#run) | ||
- [Build](#build) | ||
- [Configuration](#configuration) | ||
@@ -37,5 +43,5 @@ ## The Accounts Client library | ||
<!-- From CDN --> | ||
<script src="https://unpkg.com/@nimiq/accounts-client@v0.1/dist/standalone/AccountsClient.standalone.umd.js"></script> | ||
<script src="https://unpkg.com/@nimiq/accounts-client@v0.2/dist/standalone/AccountsClient.standalone.umd.js"></script> | ||
<!-- or --> | ||
<script src="https://cdn.jsdelivr.net/npm/@nimiq/accounts-client@v0.1/dist/standalone/AccountsClient.standalone.umd.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/@nimiq/accounts-client@v0.2/dist/standalone/AccountsClient.standalone.umd.js"></script> | ||
``` | ||
@@ -93,2 +99,5 @@ | ||
> **Note:** To use redirects instead of popups, your app must run under a | ||
> HTTPS domain! | ||
If you prefer top-level redirects instead of popups, you can pass an | ||
@@ -98,2 +107,5 @@ instance of `RedirectRequestBehavior` as a second parameter to either the | ||
> **Note:** The way to configure top-level redirects will change in an upcoming | ||
> version of the Accounts Client! | ||
```javascript | ||
@@ -133,19 +145,22 @@ const redirectBehavior = new AccountsClient.RedirectRequestBehavior(); | ||
- [Choose Address](#choose-address) | ||
- [Sign transaction](#sign-transaction) | ||
- [Sign Transaction](#sign-transaction) | ||
- [Signup](#signup) | ||
- [Login](#login) | ||
- [Onboard](#onboard) | ||
- [Logout](#logout) | ||
- [Export](#export) | ||
- [Change Password](#change-password) | ||
- [Add Address](#add-address) | ||
- [Rename](#rename) | ||
- [Sign Message](#sign-message) | ||
[//] TODO: Add methods 'onboard', 'changePassphrase', 'addAccount', 'rename', 'signMessage' | ||
> **Note:** | ||
> | ||
> All API methods run asynchronously and thus return promises. Please keep in | ||
> mind that promises can also be rejected for various reasons, e.g. if the user | ||
> mind that promises can also be rejected for various reasons, e.g. when the user | ||
> cancels the request by closing the popup window or clicking on a cancel | ||
> button. | ||
> | ||
> An error can also occur when the request contains invalid parameters. The | ||
> `Error` object will be passed on to the `reject` handler. | ||
> An error can also occur when the request contains invalid parameters. The request | ||
> promise will be rejected with an `Error` object. | ||
@@ -193,31 +208,45 @@ #### Checkout | ||
//flags: Nimiq.Transaction.Flag.CONTRACT_CREATION, | ||
// [optional] The duration (in number of blocks) that the signed transaction | ||
// should be valid for. The maximum is 120. | ||
// Default: 120 | ||
//validityDuration?: number; | ||
}; | ||
// All client requests are async and return a promise | ||
const checkoutResult = await accountsClient.checkout(requestOptions); | ||
const signedTransaction = await accountsClient.checkout(requestOptions); | ||
``` | ||
The `checkout()` method returns a promise which resolves to a | ||
`SignTransactionResult`: | ||
`SignedTransaction`: | ||
```javascript | ||
interface SignTransactionResult { | ||
serializedTx: Uint8Array; // The signed, serialized transaction | ||
sender: string; // Human-readable address of sender | ||
senderType: Nimiq.Account.Type; // 0, 1, 2 - see recipientType above | ||
senderPubKey: Uint8Array; // Serialized public key of the sender | ||
recipient: string; // Human-readable address of recipient | ||
recipientType: Nimiq.Account.Type; // 0, 1, 2 - see above | ||
value: number; | ||
fee: number; | ||
validityStartHeight: number; // Automatically determined validity | ||
// start height of the transaction | ||
signature: Uint8Array; // Serialized signature of the sender | ||
extraData: Uint8Array; | ||
flags: number; | ||
networkId: number; | ||
hash: string; // Base64 transaction hash | ||
interface SignedTransaction { | ||
serializedTx: string; // HEX signed and serialized transaction | ||
hash: string; // HEX transaction hash | ||
raw: { | ||
signerPublicKey: Uint8Array; // Serialized public key of the signer | ||
signature: Uint8Array; // Serialized signature of the signer | ||
sender: string; // Human-readable address of sender | ||
senderType: Nimiq.Account.Type; // 0, 1, 2 - see recipientType above | ||
recipient: string; // Human-readable address of recipient | ||
recipientType: Nimiq.Account.Type; // 0, 1, 2 - see above | ||
value: number; | ||
fee: number; | ||
validityStartHeight: number; // Automatically determined validity | ||
// start height of the transaction | ||
extraData: Uint8Array; | ||
flags: number; | ||
networkId: number; | ||
} | ||
} | ||
``` | ||
The `serializedTx` can be handed to a Nimiq JSON-RPC's `sendRawTransaction` method. | ||
The `raw` object can be handed to the NanoApi's `relayTransaction` method. | ||
#### Choose Address | ||
@@ -227,8 +256,8 @@ | ||
their addresses to provide to your website. This can be used for example to find | ||
out, which address your app should send funds to. | ||
out which address your app should send funds to. | ||
**Note:** This method should not yet be used as a login or authentication mechanism, | ||
**Note:** This method should not be used as a login or authentication mechanism, | ||
as it does not provide any security that the user actually owns the provided address! | ||
The method takes a simple request object as its only argument, which must only contain | ||
The method takes a basic request object as its only argument, which must only contain | ||
the `appName` property: | ||
@@ -243,19 +272,19 @@ | ||
// All client requests are async and return a promise | ||
const providedAddress = await accountsClient.chooseAddress(requestOptions); | ||
const address = await accountsClient.chooseAddress(requestOptions); | ||
``` | ||
The request's result contains a userfriendly address string as `address` and a `label`: | ||
The request's result contains an address string as `address` and a `label`: | ||
```javascript | ||
providedAddress = { | ||
address: 'NQ07 0000 0000 0000 0000 0000 0000 0000 0000', | ||
label: 'Burner Address', | ||
interface Address { | ||
address: string; // Human-readable address | ||
label: string; // The address's label (name) | ||
} | ||
``` | ||
#### Sign transaction | ||
#### Sign Transaction | ||
The `signTransaction()` method is similar to checkout, but provides a different | ||
UI to the user. The main difference to `checkout()` is that it requires the | ||
request to already include the sender's account (wallet) ID and address as `walletId` and | ||
request to already include the sender's account ID and address as `accountId` and | ||
`sender` respectively, as well as the transaction's `validityStartHeight`. The | ||
@@ -273,3 +302,3 @@ created transaction will only be returned to the caller, not sent to the network | ||
// Sender information | ||
walletId: 'xxxxxxxx', | ||
accountId: 'xxxxxxxx', | ||
sender: 'NQxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx', | ||
@@ -296,6 +325,6 @@ | ||
// All client requests are async and return a promise | ||
const signTxResult = await accountsClient.signTransaction(requestOptions); | ||
const signedTransaction = await accountsClient.signTransaction(requestOptions); | ||
``` | ||
The `signTransaction()` method returns a `SignTransactionResult` as well. See | ||
The `signTransaction()` method returns a `SignedTransaction`. See | ||
[Checkout](#checkout) for details. | ||
@@ -315,11 +344,11 @@ | ||
// All client requests are async and return a promise | ||
const newAccount = await accountsClient.signup(requestOptions); | ||
const account = await accountsClient.signup(requestOptions); | ||
``` | ||
The `signup()` method returns a promise which resolves to a `SignupResult`: | ||
The `signup()` method returns a promise which resolves to an `Account`: | ||
```javascript | ||
interface SignupResult { | ||
walletId: string; // Automatically generated account (wallet) ID | ||
label: string; // The label/name given to the account by the user | ||
interface Account { | ||
accountId: string; // Automatically generated account ID | ||
label: string; // The label (name) generated for the account | ||
@@ -329,5 +358,8 @@ type: WalletType; // 1 for in-browser multi-address accounts, | ||
accounts: Array<{ // During signup, only one address is added to the account | ||
fileExported: boolean; // These two flags signal if the user already | ||
wordsExported: boolean; // has the Login File or the recovery words | ||
addresses: Array<{ // During signup, only one address is added to the account | ||
address: string; // Human-readable address | ||
label: string; // The label/name given to the address by the user | ||
label: string; // The label (name) of the address | ||
}>; | ||
@@ -341,3 +373,3 @@ } | ||
**Accounts Manager** by importing their *Login File*, *Recovery Words* or | ||
*Account Access File*. After an account has been imported, the | ||
old *Account Access File*. After an account has been imported, the | ||
**Accounts Manager** automatically detects active addresses following the | ||
@@ -354,26 +386,26 @@ [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account-discovery) | ||
// All client requests are async and return a promise | ||
const newAccount = await accountsClient.login(requestOptions); | ||
const account = await accountsClient.login(requestOptions); | ||
``` | ||
The `login()` method returns a promise which resolves to a `LoginResult`: | ||
The `login()` method returns a promise which resolves to an `Account`. Please see | ||
the result type for [`signup()`](#signup) for details. | ||
#### Onboard | ||
The `onboard()` method presents a choice menu between _Signup_, _Login_, and | ||
_Connect Ledger_ to the user and is thus a general purpose onboarding method. | ||
Just like the direct methods, it only requires a simple request object: | ||
```javascript | ||
interface LoginResult { | ||
walletId: string; // Automatically generated account (wallet) ID | ||
label: string; // The label/name given to the account by the user | ||
const requestOptions = { | ||
// The name of your app, should be as short as possible. | ||
appName: 'Nimiq Safe', | ||
}; | ||
type: WalletType; // 0 for in-browser single-address accounts, | ||
// 1 for in-browser multi-address accounts, | ||
// 2 for Ledger hardware accounts | ||
accounts: Array<{ // Array of active addresses detected during login | ||
address: string; // Human-readable address | ||
label: string; // Label/name given by the user | ||
}>; | ||
} | ||
// All client requests are async and return a promise | ||
const account = await accountsClient.onboard(requestOptions); | ||
``` | ||
<!-- IDEA should we use std JS Array notation in the doc? | ||
Or do we officially switch to TS? Then that's another section of the | ||
general doc to add on this IMO. --> | ||
Since `onboard()` is a wrapper around Signup, Login and Ledger, it also returns an | ||
`Account` result type. Please see the result type for [`signup()`](#signup) for details. | ||
@@ -383,4 +415,4 @@ #### Logout | ||
The `logout()` method removes an account from the **Accounts Manager**. During the | ||
logout process, the user can retrieve the *Login File* or *Recovery Words* | ||
before the account is deleted. | ||
logout process, the user can export the *Login File* or *Recovery Words* before | ||
the account is deleted. | ||
@@ -392,4 +424,4 @@ ```javascript | ||
// The ID of the account (wallet) that should be removed | ||
walletId: 'xxxxxxxx', | ||
// The ID of the account that should be removed | ||
accountId: 'xxxxxxxx', | ||
}; | ||
@@ -405,10 +437,5 @@ | ||
```javascript | ||
{ | ||
success: true | ||
} | ||
{ success: true } | ||
``` | ||
<!-- TODO awaiting final decision on "simple return type" for API methods that | ||
don't really return anything --> | ||
#### Export | ||
@@ -424,4 +451,4 @@ | ||
// The ID of the account (wallet) to export | ||
walletId: 'xxxxxxxx', | ||
// The ID of the account to export | ||
accountId: 'xxxxxxxx', | ||
}; | ||
@@ -433,14 +460,156 @@ | ||
The `export()` method returns a promise which resolves to a simple object | ||
The `export()` method returns a promise which resolves to an object that | ||
contains flags for each export type: | ||
```javascript | ||
interface ExportResult { | ||
fileExported: boolean; | ||
wordsExported: boolean; | ||
} | ||
``` | ||
#### Change Password | ||
With the `changePassword()` method, a user can change the password of an account: | ||
```javascript | ||
const requestOptions = { | ||
// The name of your app, should be as short as possible. | ||
appName: 'Nimiq Safe', | ||
// The ID of the account whose password should be changed | ||
accountId: 'xxxxxxxx', | ||
}; | ||
// All client requests are async and return a promise | ||
const result = await accountsClient.changePassword(requestOptions); | ||
``` | ||
The `changePassword()` method returns a promise which resolves to a simple object | ||
containing the `success` property, which is always true: | ||
```javascript | ||
{ | ||
success: true | ||
{ success: true } | ||
``` | ||
#### Add Address | ||
By using the `addAddress()` method, the user is able to derive and add an additional | ||
address to their account. The method returns the added address and its label. | ||
The method takes a simple request object as its argument: | ||
```javascript | ||
const requestOptions = { | ||
// The name of your app, should be as short as possible. | ||
appName: 'Nimiq Safe', | ||
// The ID of the account to which an address should be added | ||
accountId: 'xxxxxxxx', | ||
}; | ||
// All client requests are async and return a promise | ||
const address = await accountsClient.addAddress(requestOptions); | ||
``` | ||
The request's result contains an address string as `address` and a `label`: | ||
```javascript | ||
interface Address { | ||
address: string; // Human-readable address | ||
label: string; // The address's label (name) | ||
} | ||
``` | ||
<!-- TODO awaiting final decision on "simple return type" for API methods that | ||
don't really return anything --> | ||
#### Rename | ||
To rename a user's account or addresses, you can call the `rename()` method. The | ||
UI for the rename action always presents the given account and all its addresses | ||
to the user. By sending an optional address with the request, that address's label | ||
will be already pre-selected for the user. | ||
This method takes the following request object as its only argument: | ||
```javascript | ||
const requestOptions = { | ||
// The name of your app, should be as short as possible. | ||
appName: 'Nimiq Safe', | ||
// The ID of the account which should be renamed, or to which the | ||
// address, which should be renamed, belongs | ||
accountId: 'xxxxxxxx', | ||
// [optional] The human-readable address which should be pre-selected | ||
// for the user to be renamed | ||
address: 'NQxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx'; | ||
}; | ||
// All client requests are async and return a promise | ||
const account = await accountsClient.rename(requestOptions); | ||
``` | ||
Since more than one label can be renamed during the rename request, the result | ||
contains the whole account, including all visible addresses. Please see the | ||
result type for [`signup()`](#signup) for details about the `Account` object. | ||
#### Sign Message | ||
To let the user sign an arbitrary message with any of their addresses, you can | ||
call `signMessage()` with the following request object. If you do not include | ||
_both_ the `accountId` _and_ `signer` properties, the user will be prompted to | ||
select an address from their available accounts. The message can be either a | ||
string or a Uint8Array byte array. | ||
```javascript | ||
const requestOptions = { | ||
// The name of your app, should be as short as possible. | ||
appName: 'Nimiq Safe', | ||
// The message to sign. Can either be string of valid UTF-8 or a | ||
// byte array to sign arbitrary data | ||
message: 'String to sign' || new Uint8Array([...]), | ||
// [optional] The ID of the account with which to sign | ||
accountId: 'xxxxxxxx', | ||
// [optional] The human-readable address with which to sign | ||
signer: 'NQxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx'; | ||
}; | ||
// All client requests are async and return a promise | ||
const signedMessage = await accountsClient.signMessage(requestOptions); | ||
``` | ||
The method returns a `SignedMessage` object containing the following properties: | ||
```javascript | ||
interface SignedMessage { | ||
signer: string; // Userfriendly address | ||
signerPublicKey: Uint8Array; // The public key of the signer | ||
signature: Uint8Array; // Signature for the message | ||
message: string | Uint8Array; // The signed message (as the same type as it | ||
// was handed in) | ||
} | ||
``` | ||
**Note:** To prevent users from signing valid transactions or other | ||
blockchain-related proofs which could be used to impersonate them, the Nimiq | ||
Keyguard prefixes the string `'Nimiq Signed Message: '` (22 one-byte-characters) | ||
to the input message. The signature is then created over the combined message. | ||
The prefix is already included in the result's `message` property. | ||
Verifying a signed message could go like this: | ||
```javascript | ||
const signature = new Nimiq.Signature(signedMessage.signature); | ||
const publicKey = new Nimiq.PublicKey(signedMessage.signerPublicKey); | ||
const message = typeof signedMessage.message === 'string' | ||
? Nimiq.BufferUtils.fromUtf8(signedMessage.message)) | ||
: signedMessage.message; | ||
// Check signature against the message | ||
const isValid = signature.verify(publicKey, message); | ||
``` | ||
### Listening for redirect responses | ||
@@ -451,3 +620,3 @@ | ||
follow the four steps below to specifically listen for the redirects from the | ||
**Accounts Manager** back to your site using the `on()` method. | ||
**Accounts Manager** back to your site, using the `on()` method. | ||
@@ -474,8 +643,6 @@ Your handler functions will be called with two parameters: the result object and | ||
// 3. Listen for the redirect responses you expect | ||
const RequestType = AccountsClient.RequestType; | ||
accountsClient.on(AccountsClient.RequestType.CHECKOUT, onSuccess, onError); | ||
accountsClient.on(AccountsClient.RequestType.SIGN_TRANSACTION, onSuccess, onError); | ||
accountsClient.on(AccountsClient.RequestType.LOGIN, onSuccess, onError); | ||
accountsClient.on(RequestType.CHECKOUT, onSuccess, onError); | ||
accountsClient.on(RequestType.SIGN_TRANSACTION, onSuccess, onError); | ||
accountsClient.on(RequestType.LOGIN, onSuccess, onError); | ||
// 4. After setup is complete, check for a redirect response | ||
@@ -500,4 +667,9 @@ accountsClient.checkRedirectResponse(); | ||
LOGIN = 'login', | ||
ONBOARD = 'onboard', | ||
LOGOUT = 'logout', | ||
EXPORT = 'export', | ||
CHANGE_PASSWORD = 'change-password', | ||
ADD_ADDRESS = 'add-address', | ||
RENAME = 'rename', | ||
SIGN_MESSAGE = 'sign-message', | ||
} | ||
@@ -510,2 +682,5 @@ ``` | ||
If you want to run your own instance of Accounts Manager, you also need to run | ||
an instance of the [Keyguard](https://github.com/nimiq/keyguard-next/). | ||
## Contribute | ||
@@ -546,3 +721,3 @@ | ||
```bash | ||
yarn run test:unit | ||
yarn run test | ||
``` | ||
@@ -557,1 +732,18 @@ | ||
``` | ||
### Configuration | ||
The following values can be changed via configuration files: | ||
- keyguardEndpoint: The location of your keyguard instance. | ||
- network: The network you want to use. Possible values are 'main', 'test' and | ||
'dev'. You can use the constants (see default configs). | ||
- networkEndpoint: The location of the network iframe instance you want to use. | ||
- privilegedOrigins: An array of origins with special access rights, nameley | ||
permission to use iframe methods like `list()`. | ||
The default config file is `config.local.ts`. To use a different file | ||
(especially useful for deployment), set an environment variable | ||
`build`. E.g. `export build='testnet'` to use `config.testnet.ts`. To set | ||
environment variables permanently, please refer to your server's documentation, | ||
e.g. [for Apache](https://httpd.apache.org/docs/2.4/env.html). |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
72287
538
725
Updated@nimiq/rpc@^0.1.4