Skip to content

Commit

Permalink
feat: implement wallet bridge separately from wallet user
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Mar 16, 2020
1 parent 7c670d9 commit 41c1278
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 258 deletions.
2 changes: 1 addition & 1 deletion packages/agoric-cli/lib/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default async function startMain(progname, rawArgs, powers, opts) {
const { anylogger, fs, spawn, os, process } = powers;
const log = anylogger('agoric:start');

const pspawn = (cmd, cargs, { stdio = 'inherit', ...rest }) => {
const pspawn = (cmd, cargs, { stdio = 'inherit', ...rest } = {}) => {
log.info(chalk.blueBright(cmd, ...cargs));
return new Promise((resolve, _reject) => {
const cp = spawn(cmd, cargs, { stdio, ...rest });
Expand Down
58 changes: 58 additions & 0 deletions packages/cosmic-swingset/lib/ag-solo/html/wallet-bridge.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<title>Agoric Wallet Bridge</title>
</head>
<body>
<p>This bridge is only for dApps to reach your wallet. It contains no user-servicable parts.</p>

<script type="text/javascript">
const walletPublicURL = new URL('/wallet-bridge', window.origin.replace(/^http/, 'ws')).href;
const ws = new WebSocket(walletPublicURL);
const wsQueue = [];
const dappQueue = [];
let origin;
ws.addEventListener('message', ev => {
const obj = JSON.parse(ev.data)
// console.log('to dapp', origin, obj);
if (origin === undefined) {
dappQueue.push(obj);
return;
}
if (window.parent !== window) {
window.parent.postMessage(obj, origin);
}
});

ws.addEventListener('open', () => {
if (wsQueue.length) {
console.log('sending', wsQueue.length, 'queued messages from', origin);
}
while (wsQueue.length) {
ws.send(wsQueue.shift());
}
});


window.addEventListener('message', ev => {
if (origin === undefined) {
// First-come, first-serve.
origin = ev.origin;
while (dappQueue.length) {
const dappObj = dappQueue.shift();
if (window.parent !== window) {
window.parent.postMessage(dappObj);
}
}
}
// console.log('from dapp', origin, ev.data);
const obj = { ...ev.data, dappOrigin: origin };
if (ws.readyState !== ws.OPEN) {
wsQueue.push(obj);
} else {
ws.send(JSON.stringify(obj));
}
});
</script>
</body>
</html>
2 changes: 2 additions & 0 deletions packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export default function setup(syscall, state, helpers) {

async function setupWalletVat(commandDevice, httpVat, walletVat) {
await E(httpVat).registerURLHandler(walletVat, '/vat');
const bridgeURLHandler = await E(walletVat).getBridgeURLHandler();
await E(httpVat).registerURLHandler(bridgeURLHandler, '/wallet-bridge');
await E(walletVat).setCommandDevice(commandDevice);
await E(walletVat).setPresences();
}
Expand Down
141 changes: 82 additions & 59 deletions packages/cosmic-swingset/lib/ag-solo/vats/vat-wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,66 +23,62 @@ function build(E, D, _log) {
commandDevice = d;
}

function getCommandHandler() {
return {
async processInbound(obj) {
const { type, data, requestContext } = obj;
switch (type) {
case 'walletGetPurses': {
if (!pursesState) return {};
return {
type: 'walletUpdatePurses',
data: pursesState,
};
}
case 'walletGetInbox': {
if (!inboxState) return {};
return {
type: 'walletUpdateInbox',
data: inboxState,
};
}
case 'walletAddOffer': {
// We only need to do this because we can't reach addOffer.
const hooks = wallet.hydrateHooks(data.hooks);
return {
type: 'walletOfferAdded',
data: await wallet.addOffer(data, hooks, requestContext),
};
}
case 'walletDeclineOffer': {
return {
type: 'walletOfferDeclined',
data: wallet.declineOffer(data),
};
}
case 'walletCancelOffer': {
return {
type: 'walletOfferCancelled',
data: wallet.cancelOffer(data),
};
}
case 'walletAcceptOffer': {
await wallet.acceptOffer(data);
return {
type: 'walletOfferAccepted',
data: true,
};
}
case 'walletGetOfferDescriptions': {
const result = await wallet.getOfferDescriptions(data);
return {
type: 'walletOfferDescriptions',
data: result,
};
}
async function adminProcessInbound(obj) {
const { type, data, requestContext } = obj;
switch (type) {
case 'walletGetPurses': {
if (!pursesState) return {};
return {
type: 'walletUpdatePurses',
data: pursesState,
};
}
case 'walletGetInbox': {
if (!inboxState) return {};
return {
type: 'walletUpdateInbox',
data: inboxState,
};
}
case 'walletAddOffer': {
// We only need to do this because we can't reach addOffer.
const hooks = wallet.hydrateHooks(data.hooks);
return {
type: 'walletOfferAdded',
data: await wallet.addOffer(data, hooks, requestContext),
};
}
case 'walletDeclineOffer': {
return {
type: 'walletOfferDeclined',
data: wallet.declineOffer(data),
};
}
case 'walletCancelOffer': {
return {
type: 'walletOfferCancelled',
data: wallet.cancelOffer(data),
};
}
case 'walletAcceptOffer': {
await wallet.acceptOffer(data);
return {
type: 'walletOfferAccepted',
data: true,
};
}
case 'walletGetOfferDescriptions': {
const result = await wallet.getOfferDescriptions(data);
return {
type: 'walletOfferDescriptions',
data: result,
};
}

default: {
return false;
}
}
},
};
default: {
return false;
}
}
}

function setPresences() {
Expand Down Expand Up @@ -119,11 +115,38 @@ function build(E, D, _log) {
);
}

function getCommandHandler() {
return harden({
processInbound: adminProcessInbound,
});
}

function getBridgeURLHandler() {
return harden({
getCommandHandler() {
return harden({
processInbound(obj) {
const { type, requestContext } = obj;
if (['walletGetPurses', 'walletAddOffer'].includes(type)) {
// Override the origin since we got it from the bridge.
return adminProcessInbound({
...obj,
requestContext: { ...requestContext, origin: obj.dappOrigin },
});
}
return Promise.resolve(false);
},
});
},
});
}

return harden({
startup,
getWallet,
setCommandDevice,
getCommandHandler,
getBridgeURLHandler,
setPresences,
});
}
Expand Down
6 changes: 3 additions & 3 deletions packages/cosmic-swingset/lib/ag-solo/web.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,16 @@ export function makeHTTPListener(basedir, port, host, rawInboundCommand) {
return true;
};

// accept messages on /vat and /api
// accept messages on some well-known endpoints
// todo: later allow arbitrary endpoints?
for (const ep of ['/vat', '/api']) {
for (const ep of ['/vat', '/wallet-public', '/api']) {
app.post(ep, (req, res) => {
if (ep !== '/api' && !validateOrigin(req)) {
res.json({ ok: false, rej: 'Invalid Origin' });
return;
}

// console.log(`POST /vat got`, req.body); // should be jsonable
// console.log(`POST ${ep} got`, req.body); // should be jsonable
inboundCommand(req.body, req)
.then(
r => res.json({ ok: true, res: r }),
Expand Down
Loading

0 comments on commit 41c1278

Please sign in to comment.