@velas/account-agent
Version:
sdk
422 lines (340 loc) • 14.1 kB
JavaScript
import urljoin from 'url-join';
import qs from 'qs';
import assert from '../helper/assert';
import Storage from '../helper/storage';
import KeyStorage from '../helper/key-storage';
import use from '../middleware/use';
import checkJWT from '../middleware/check-jwt';
import checkScopes from '../middleware/check-scopes';
import checkClient from '../middleware/check-client';
import initInteraction from '../middleware/init-interaction';
import deleteInteraction from '../middleware/delete-interaction';
import checkRedirectUri from '../middleware/check-redirect-uri';
import checkBroadcastUri from '../middleware/check-broadcast-uri';
import checkCSRFToken from '../middleware/check-csrf-token';
import saveInteraction from '../middleware/save-interaction';
import checkPolicy from '../middleware/check-policy';
import processSubmission from '../middleware/process-submission';
import bindSession from '../middleware/bind-session';
import bindSessions from '../middleware/bind-sessions';
import bindScopes from '../middleware/bind-scopes';
import AuthorizationCode from '../middleware/authorization-code';
import RedirectUri from '../middleware/redirect-uri';
import removeExpiredItems from '../middleware/remove-expired-items';
import setInteractionType from "../middleware/set-interaction-type";
import tokenController from '../controllers/token';
import mobileTokenController from '../controllers/mobileToken';
import transactionController from '../controllers/transaction';
import userinfoController from '../controllers/userinfo';
import exchangeController from '../controllers/exchange';
import storageAccessController from '../controllers/storageAccess';
import authorizationController from '../controllers/authorization';
import Client from '../Client';
import getSession from '../helper/session';
import ScopesConstructor from '../helper/scopes';
import httpProvider from '../helper/http-provider';
/**
* Creates a new Agent API
* @constructor
* @param {Object} options
*/
function Agent(options) {
assert.check(
options,
{ type: 'object', message: 'options parameter is not valid' },
{
UserInfoIconHandler: { type: 'function', message: 'UserInfoIconHandler option should be function', optional: true },
StorageHandler: { type: 'function', message: 'StorageHandler option is required' },
KeyStorageHandler: { type: 'function', message: 'KeyStorageHandler option is required' },
broadcastTransactionHendler: { type: 'function', message: 'broadcastTransactionHendler option is required' },
client_host: { type: 'string', message: 'client_host option is required' },
client_provider: { type: 'object', message: 'client_provider option is required' },
client_account_contract: { type: 'string', message: 'client_account_contract option is required' },
},
);
this.baseOptions = options;
this.baseOptions.issuer = 'agent';
const storage = new Storage(this.baseOptions);
const keyStorage = new KeyStorage(this.baseOptions);
const client = new Client(this.baseOptions, keyStorage);
this.provider = {
Session: getSession(storage, keyStorage),
storage,
keyStorage,
client,
sc: new ScopesConstructor({ account_contract: options.client_account_contract }),
sendAsync: httpProvider.provider
};
if (this.baseOptions.UserInfoIconHandler) {
this.provider.iconHandler = this.baseOptions.UserInfoIconHandler;
};
};
async function methodHandler(method, ctx) {
try {
if (!['token', 'transaction', 'userinfo', 'exchange', 'storage_access', 'authorization', 'mobile_token' ].includes(method)) throw new Error('method option is wrong');
if (method === 'token') {
await use([ checkJWT ], ctx);
return await tokenController(ctx);
} if (method === 'mobile_token') {
await use([ checkJWT ], ctx);
return await mobileTokenController(ctx);
} else if(method === 'transaction') {
return await transactionController(ctx);
} else if(method === 'userinfo') {
return await userinfoController(ctx);
} else if(method === 'exchange') {
return await exchangeController(ctx);
} else if(method === 'storage_access') {
return await storageAccessController(ctx);
} else if(method === 'authorization') {
return await authorizationController(ctx);
};
} catch(e) {
return {
error: 'invalid_request',
description: e.message,
};
};
};
const eventHendler = async function(event) {
const { origin, data: { method, state, token, data }} = event;
if (!state || method === 'custom') return;
event.source.postMessage({ state, stage: 'pending'}, origin);
const ctx = {
params: { token, method, data, origin },
provider: this.provider,
};
event.source.postMessage({ state, stage: 'done', ...await methodHandler(method, ctx)}, origin);
};
Agent.prototype.process = async function(method, { token, data }) {
assert.check(method, { type: 'string', message: 'options method is not valid' });
assert.check(token, { type: 'string', message: 'options token is not valid' });
const ctx = {
params: { token, method, data },
provider: this.provider,
};
return await methodHandler(method, ctx);
};
Agent.prototype.addEventListener = function() {
var eventListener = (eventHendler).bind(this);
if ( window.addEventListener ) {
if (!window.accountListener) {
window.accountListener = true;
window.addEventListener("message", eventListener, false);
};
}
else { window.attachEvent("onmessage", eventListener, false) }; // IE 8;
};
Agent.prototype.interaction = async function(options) {
assert.check(
options,
{ type: 'object', message: 'options parameter is not valid' },
{ id: { type: 'string', message: 'id option is required' }},
);
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${options.id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
return { interaction: foundInteraction };
};
Agent.prototype.authorize = async function(options) {
assert.check(
options,
{ type: 'object', message: 'options parameter is not valid' },
{ token: { type: 'string', message: 'token option is required' }},
);
const ctx = {
params: { token: options.token },
provider: this.provider,
};
await use([
checkJWT,
checkScopes,
initInteraction,
removeExpiredItems,
bindSessions,
checkClient,
checkRedirectUri,
checkBroadcastUri,
checkCSRFToken,
checkPolicy,
bindScopes,
saveInteraction,
], ctx);
if (ctx.interaction.redirect) {
return { redirect: ctx.interaction.redirect };
};
return { interaction: ctx.interaction };
};
Agent.prototype.authorizeAccountManagement = async function(options) {
assert.check(
options,
{ type: 'object', message: 'options parameter is not valid.' },
{ token: { type: 'string', message: 'token option is required.' }},
);
const ctx = {
params: {
csrf_token: options.token,
transactions_sponsor_api_host: options.transactions_sponsor_api_host,
transactions_sponsor_pub_key: options.transactions_sponsor_pub_key,
scope: [`${this.provider.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS}:10`, `${this.provider.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS}:11`]
},
provider: this.provider
};
await use([
initInteraction,
setInteractionType,
removeExpiredItems,
bindSessions,
checkBroadcastUri,
checkCSRFToken,
checkPolicy,
saveInteraction,
], ctx);
return { interaction: ctx.interaction };
}
Agent.prototype.getAccounts = async function () {
const ctx = {
provider: this.provider,
interaction: {}
}
await use([
bindSessions
], ctx)
return ctx.interaction.sessions
}
Agent.prototype.endSession = async function(interaction_id, session_id) {
assert.check(interaction_id,{ type: 'string', message: 'interaction_id option is required in endSession' });
assert.check(session_id,{ type: 'string', message: 'session_id option is required in endSession' });
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${interaction_id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
const session = await this.provider.Session.findById(session_id);
if (session) await session.destroy();
const ctx = {
result: {},
interaction: foundInteraction,
provider: this.provider,
};
await use([
bindSession,
processSubmission,
bindSessions,
checkPolicy,
bindScopes,
saveInteraction,
], ctx);
if (ctx.interaction.stage === 'completed') {
await use([
AuthorizationCode,
RedirectUri,
deleteInteraction,
], ctx);
return { redirect: ctx.redirect };
};
return { interaction: ctx.interaction };
};
Agent.prototype.logout = async function(interaction_id, session_id, client_id) {
assert.check(interaction_id,{ type: 'string', message: 'interaction_id option is required in logout' });
assert.check(session_id,{ type: 'string', message: 'session_id option is required in logout' });
assert.check(client_id,{ type: 'string', message: 'client_id option is required in logout' });
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${interaction_id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
const session = await this.provider.Session.findById(session_id);
if (session) await session.logout(client_id);
const ctx = {
result: {},
interaction: foundInteraction,
provider: this.provider,
};
await use([
bindSession,
processSubmission,
bindSessions,
checkPolicy,
bindScopes,
saveInteraction,
], ctx);
if (ctx.interaction.stage === 'completed') {
await use([
AuthorizationCode,
RedirectUri,
deleteInteraction,
], ctx);
return { redirect: ctx.redirect };
};
return { interaction: ctx.interaction };
};
Agent.prototype.cancelInteraction = async function(id) {
assert.check(id, { type: 'string', message: 'id option is required' });
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
let redirect = {
error: 'access_denied',
description: 'canceled by user',
state: foundInteraction.params.state || undefined,
}
if (foundInteraction.params.mode === 'redirect' || foundInteraction.params.mode === 'mobile') {
redirect = urljoin(foundInteraction.params.redirect_uri, '', '?' + qs.stringify(redirect));
}
this.provider.storage.removeItem(`${'Interaction'}:${id}`);
return { redirect };
};
Agent.prototype.removeSession = async function (interactionID, sessionID) {
assert.check(interactionID, { type: 'string', message: 'interactionID option is required' });
assert.check(sessionID, { type: 'string', message: 'sessionID option is required' });
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${interactionID}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
const foundSession = await this.provider.Session.findById(sessionID);
assert.check(foundSession, { type: 'object', message: 'session not found' });
await foundSession.destroy();
const ctx = {
result: { mergeWithLastSubmission: false },
interaction: foundInteraction,
provider: this.provider,
};
await use([
bindSession,
processSubmission,
bindSessions,
checkPolicy,
bindScopes,
saveInteraction,
], ctx);
return { interaction: ctx.interaction };
}
Agent.prototype.finishInteraction = async function(id, result) {
assert.check(
result,
{ type: 'object', message: 'result parameter is not valid in finishInteraction' },
{ mergeWithLastSubmission: { type: 'boolean', message: 'mergeWithLastSubmission option is required in finishInteraction' }},
);
assert.check(id,{ type: 'string', message: 'id option is required in finishInteraction' });
const foundInteraction = await this.provider.storage.getItem(`${'Interaction'}:${id}`);
assert.check(foundInteraction, { type: 'object', message: 'interaction not found' });
const ctx = {
result,
interaction: foundInteraction,
provider: this.provider,
};
await use([
bindSession,
processSubmission,
bindSessions,
checkPolicy,
bindScopes,
saveInteraction,
], ctx);
if (ctx.interaction.stage === 'completed' && ctx.interaction.type === "account_management") {
await use([
deleteInteraction,
], ctx);
return { success: true }
} else if (ctx.interaction.stage === 'completed') {
await use([
AuthorizationCode,
RedirectUri,
deleteInteraction,
], ctx);
return { redirect: ctx.redirect };
};
return { interaction: ctx.interaction };
}
export default Agent;