Skip to content

Commit

Permalink
feat: allow CapTP URL handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Sep 22, 2020
1 parent fb8607d commit b3e1e61
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 45 deletions.
6 changes: 3 additions & 3 deletions packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,11 @@ export function buildRootObject(vatPowers, vatParameters) {
registerAPIHandler(handler) {
return E(vats.http).registerURLHandler(handler, '/api');
},
async registerWallet(wallet, handler, bridgeHandler) {
async registerWallet(wallet, privateWallet, privateWalletBridge) {
await Promise.all([
E(vats.http).registerURLHandler(handler, '/private/wallet'),
E(vats.http).registerURLHandler(privateWallet, '/private/wallet'),
E(vats.http).registerURLHandler(
bridgeHandler,
privateWalletBridge,
'/private/wallet-bridge',
),
E(vats.http).setWallet(wallet),
Expand Down
80 changes: 61 additions & 19 deletions packages/cosmic-swingset/lib/ag-solo/vats/captp.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,96 @@
// Avoid importing the full captp bundle, which would carry
// in its own makeHardener, etc.
import { makeCapTP } from '@agoric/captp/lib/captp';
import { E } from '@agoric/eventual-send';
import { makePromiseKit } from '@agoric/promise-kit';

export const getCapTPHandler = (send, getBootstrapObject) => {
export const getCapTPHandler = (
send,
getAdminAndConnectionFacets,
fallback = undefined,
) => {
const chans = new Map();
const doCall = (obj, method, ...args) => {
if (obj) {
return E(obj)
[method](...args)
.catch(_ => {});
}
return undefined;
};
const handler = harden({
onOpen(_obj, meta) {
onOpen(obj, meta) {
const { channelHandle, origin = 'unknown' } = meta || {};
console.debug(`Starting CapTP`, meta);
const sendObj = o => {
send(o, [channelHandle]);
};
const { dispatch, abort } = makeCapTP(
const adminFacetPK = makePromiseKit();
const connectionFacetPK = makePromiseKit();
let booted = false;
const { dispatch, abort, getBootstrap } = makeCapTP(
origin,
sendObj,
getBootstrapObject,
async () => {
if (!booted) {
booted = true;
const admconnP = getAdminAndConnectionFacets(meta, getBootstrap());
adminFacetPK.resolve(E.G(admconnP).adminFacet);
connectionFacetPK.resolve(E.G(admconnP).connectionFacet);
}
return connectionFacetPK.promise;
},
{
onReject(err) {
// Be quieter for sanity's sake.
console.log('CapTP', origin, 'exception:', err);
},
},
);
chans.set(channelHandle, [dispatch, abort]);
chans.set(channelHandle, {
dispatch,
abort,
adminFacet: adminFacetPK.promise,
});
doCall(fallback, 'onOpen', obj, meta);
},
onClose(_obj, meta) {
onClose(obj, meta) {
console.debug(`Finishing CapTP`, meta);
const dispatchAbort = chans.get(meta.channelHandle);
if (dispatchAbort) {
(1, dispatchAbort[1])();
const chan = chans.get(meta.channelHandle);
if (chan) {
const { abort, adminFacet } = chan;
doCall(adminFacet, 'onClose');
abort();
}
chans.delete(meta.channelHandle);
doCall(fallback, 'onClose', obj, meta);
},
onError(obj, meta) {
console.debug(`Error in CapTP`, meta, obj.error);
const chan = chans.get(meta.channelHandle);
if (chan) {
const { abort, adminFacet } = chan;
doCall(adminFacet, 'onError', obj.error);
abort(obj.error);
}
doCall(fallback, 'onError', obj, meta);
},
onMessage(obj, meta) {
async onMessage(obj, meta) {
console.debug('processing inbound', obj);
const dispatchAbort = chans.get(meta.channelHandle);
if (!dispatchAbort || !(1, dispatchAbort[0])(obj)) {
const chan = chans.get(meta.channelHandle);
if (chan) {
const { dispatch } = chan;
if (dispatch(obj)) {
return true;
}
}
const done = await doCall(fallback, 'onMessage', obj, meta);
if (!done) {
console.error(`Could not find CapTP handler ${obj.type}`, meta);
return false;
}
return true;
return done;
},
});

return harden({
getCommandHandler() {
return handler;
},
});
return harden(handler);
};
44 changes: 21 additions & 23 deletions packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,23 @@ export function buildRootObject(vatPowers) {
}
};

const handler = {};
const registeredURLHandlers = new Map();

// TODO: Don't leak memory.
async function registerURLHandler(newHandler, url) {
const commandHandler = await E(newHandler).getCommandHandler();
async function registerURLHandler(handler, url) {
const fallback = await E(handler)
.getCommandHandler()
.catch(_ => undefined);
const commandHandler = getCapTPHandler(
send,
(meta, otherSide) =>
E(handler)
.open(meta, otherSide)
.catch(e => {
console.error('Loading CapTP', e);
return {};
}),
fallback,
);
let reg = registeredURLHandlers.get(url);
if (!reg) {
reg = [];
Expand All @@ -67,12 +78,12 @@ export function buildRootObject(vatPowers) {
registerURLHandler(replHandler, '/private/repl');

// Assign the captp handler.
// TODO: Break this out into a separate vat.
const captpHandler = getCapTPHandler(
send,
// Harden only our exported objects, and fetch them afresh each time.
() => harden(exportedToCapTP),
);
const captpHandler = harden({
open() {
// Harden only our exported objects, and fetch them afresh each time.
return harden({ connectionFacet: exportedToCapTP });
},
});
registerURLHandler(captpHandler, '/private/captp');
},

Expand Down Expand Up @@ -164,19 +175,6 @@ export function buildRootObject(vatPowers) {
};
delete meta.channelID;

if (url === '/private/repl') {
// Use our local handler object (compatibility).
// TODO: standardise
if (handler[type]) {
D(commandDevice).sendResponse(
count,
false,
await handler[type](obj, meta),
);
return;
}
}

const urlHandlers = registeredURLHandlers.get(url);
if (urlHandlers) {
// todo fixme avoid the loop
Expand Down
26 changes: 26 additions & 0 deletions packages/cosmic-swingset/lib/ag-solo/web.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,28 @@ export async function makeHTTPListener(basedir, port, host, rawInboundCommand) {

server.listen(port, host, () => log.info('Listening on', `${host}:${port}`));

const wsActions = {
noop() {
// do nothing.
},
heartbeat() {
this.isAlive = true;
},
};

const pingInterval = setInterval(function ping() {
wss.clients.forEach(ws => {
if (!ws.isAlive) {
ws.terminate();
return;
}
ws.isAlive = false;
ws.ping(wsActions.noop);
});
}, 30000);

wss.on('close', () => clearInterval(pingInterval));

let lastChannelID = 0;

function newChannel(ws, req) {
Expand All @@ -206,6 +228,10 @@ export async function makeHTTPListener(basedir, port, host, rawInboundCommand) {

log(id, `new WebSocket ${req.url}`);

// Manage connection pings.
ws.isAlive = true;
ws.on('pong', wsActions.heartbeat);

// Register the point-to-point channel.
channels.set(channelID, ws);

Expand Down
71 changes: 71 additions & 0 deletions packages/dapp-svelte-wallet/api/src/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,77 @@ export function buildRootObject(_vatPowers) {

function getBridgeURLHandler() {
return harden({
// Use CapTP to interact with this object.
async open(meta, otherSide) {
const dappOrigin = meta.origin;
const suggestedDappPetname = meta.dappOrigin || meta.origin;

const notYetEnabled = () =>
E(otherSide)
.needDappApproval(dappOrigin, suggestedDappPetname)
.catch(_ => {});
const approve = () =>
wallet.waitForDappApproval(
suggestedDappPetname,
dappOrigin,
notYetEnabled,
);

return harden({
async getPurseNotifier() {
await approve();
return harden({
async getUpdateSince(count = undefined) {
await approve();
const pursesJSON = await pursesJSONNotifier.getUpdateSince(
count,
);
return JSON.parse(pursesJSON);
},
});
},
async addOffer(offer) {
await approve();
return wallet.addOffer(offer, { ...meta, dappOrigin });
},
async getOfferNotifier(status = null) {
await approve();
return harden({
async getUpdateSince(count = undefined) {
await approve();
const update = await inboxJSONNotifier.getUpdateSince(count);
const offers = JSON.parse(update.value);
return harden(
offers.filter(
offer =>
(status === null || offer.status === status) &&
offer.requestContext &&
offer.requestContext.origin === dappOrigin,
),
);
},
});
},
async getDepositFacetId(brandBoardId) {
await approve();
return wallet.getDepositFacetId(brandBoardId);
},
async suggestIssuer(petname, boardId) {
await approve();
return wallet.suggestIssuer(petname, boardId, dappOrigin);
},
async suggestInstallation(petname, boardId) {
await approve();
return wallet.suggestInstallation(petname, boardId, dappOrigin);
},
async suggestInstance(petname, boardId) {
await approve();
return wallet.suggestInstance(petname, boardId, dappOrigin);
},
});
},

// The legacy HTTP/WebSocket handler.
getCommandHandler() {
return harden({
onOpen(_obj, meta) {
Expand Down

0 comments on commit b3e1e61

Please sign in to comment.