diff --git a/packages/captp/lib/captp.js b/packages/captp/lib/captp.js index 4fb711299a7..8ae262b572b 100644 --- a/packages/captp/lib/captp.js +++ b/packages/captp/lib/captp.js @@ -3,14 +3,19 @@ // This logic was mostly lifted from @agoric/swingset-vat liveSlots.js // Defects in it are mfig's fault. import { Remotable, makeMarshal, QCLASS } from '@agoric/marshal'; -import Nat from '@agoric/nat'; -import { HandledPromise, E } from '@agoric/eventual-send'; +import { E, HandledPromise } from '@agoric/eventual-send'; import { isPromise } from '@agoric/produce-promise'; -export { E, HandledPromise, Nat }; +export { E, HandledPromise }; -export function makeCapTP(ourId, send, bootstrapObj = undefined) { +export function makeCapTP(ourId, rawSend, bootstrapObj = undefined) { let unplug = false; + function send(...args) { + if (unplug !== false) { + throw unplug; + } + return rawSend(...args); + } // convertValToSlot and convertSlotToVal both perform side effects, // populating the c-lists (imports/exports/questions/answers) upon @@ -108,6 +113,9 @@ export function makeCapTP(ourId, send, bootstrapObj = undefined) { // as also being questions / remote handled promises const handler = { get(_o, prop) { + if (unplug !== false) { + throw unplug; + } const [questionID, pr] = makeQuestion(); send({ type: 'CTP_CALL', @@ -118,6 +126,9 @@ export function makeCapTP(ourId, send, bootstrapObj = undefined) { return harden(pr.p); }, applyMethod(_o, prop, args) { + if (unplug !== false) { + throw unplug; + } // Support: o~.[prop](...args) remote method invocation const [questionID, pr] = makeQuestion(); send({ @@ -253,7 +264,6 @@ export function makeCapTP(ourId, send, bootstrapObj = undefined) { // Pull the plug! CTP_ABORT(obj) { const { exception } = obj; - unplug = true; for (const pr of questions.values()) { pr.rej(exception); } @@ -261,11 +271,15 @@ export function makeCapTP(ourId, send, bootstrapObj = undefined) { pr.rej(exception); } send(obj); + unplug = exception; }, }; // Get a reference to the other side's bootstrap object. - const getBootstrap = () => { + const getBootstrap = async () => { + if (unplug !== false) { + throw unplug; + } const [questionID, pr] = makeQuestion(); send({ type: 'CTP_BOOTSTRAP', @@ -277,7 +291,7 @@ export function makeCapTP(ourId, send, bootstrapObj = undefined) { // Return a dispatch function. const dispatch = obj => { - if (unplug) { + if (unplug !== false) { return false; } const fn = handler[obj.type]; @@ -289,7 +303,9 @@ export function makeCapTP(ourId, send, bootstrapObj = undefined) { }; // Abort a connection. - const abort = exception => dispatch({ type: 'CTP_ABORT', exception }); + const abort = ( + exception = Error(`disconnected from ${JSON.stringify(ourId)}`), + ) => dispatch({ type: 'CTP_ABORT', exception }); return harden({ abort, dispatch, getBootstrap }); } diff --git a/packages/captp/lib/index.js b/packages/captp/lib/index.js new file mode 100644 index 00000000000..14ecb8e46aa --- /dev/null +++ b/packages/captp/lib/index.js @@ -0,0 +1,6 @@ +import Nat from '@agoric/nat'; + +export * from '@agoric/marshal'; + +export * from './captp'; +export { Nat }; diff --git a/packages/captp/package.json b/packages/captp/package.json index b79d9ce4074..bbda5f4a7d3 100644 --- a/packages/captp/package.json +++ b/packages/captp/package.json @@ -27,7 +27,7 @@ "build": "exit 0", "test": "tape -r esm 'test/**/*.js'", "lint-fix": "eslint --fix '**/*.js'", - "lint-check": "eslint '**/*.js'", + "lint-check": "eslint 'lib/*.js'", "lint-fix-jessie": "eslint -c '.eslintrc-jessie.js' --fix '**/*.js'", "lint-check-jessie": "eslint -c '.eslintrc-jessie.js' '**/*.js'" }, diff --git a/packages/captp/test/disco.js b/packages/captp/test/disco.js index 1f1716a932d..daf338e1900 100644 --- a/packages/captp/test/disco.js +++ b/packages/captp/test/disco.js @@ -2,7 +2,7 @@ import '@agoric/install-ses'; import { test } from 'tape-promise/tape'; -import { makeCapTP } from '../lib/captp'; +import { E, makeCapTP } from '../lib/captp'; test('try disconnecting captp', async t => { try { @@ -10,24 +10,35 @@ test('try disconnecting captp', async t => { const { getBootstrap, abort } = makeCapTP( 'us', obj => objs.push(obj), - () => harden({}), + () => + harden({ + method() { + return 'hello'; + }, + }), ); t.deepEqual(objs, [], 'expected no messages'); const bs = getBootstrap(); + const ps = []; + ps.push(t.rejects(E.G(bs).prop, Error, 'rejected get after disconnect')); + ps.push( + t.rejects(E(bs).method(), Error, 'rejected method after disconnect'), + ); t.deepEqual( objs, [{ type: 'CTP_BOOTSTRAP', questionID: 1 }], 'expected bootstrap messages', ); - const pr = t.rejects(bs, Error, 'rejected after disconnect'); + ps.push(t.rejects(bs, Error, 'rejected after disconnect')); const abortMsg = { type: 'CTP_ABORT', exception: Error('disconnect') }; abort(abortMsg.exception); + await t.rejects(getBootstrap(), Error, 'rejected disconnected bootstrap'); t.deepEqual( objs, [{ type: 'CTP_BOOTSTRAP', questionID: 1 }, abortMsg], 'expected disconnect messages', ); - await pr; + await ps; } catch (e) { t.isNot(e, e, 'unexpected exception'); } finally { diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/captp.js b/packages/cosmic-swingset/lib/ag-solo/vats/captp.js index f906f0881f9..dcce6f3a7b4 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/captp.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/captp.js @@ -4,7 +4,7 @@ // in its own makeHardener, etc. import { makeCapTP } from '@agoric/captp/lib/captp'; -export const getCapTPHandler = (E, send, getBootstrapObject) => { +export const getCapTPHandler = (send, getBootstrapObject, powers) => { const chans = new Map(); const handler = harden({ onOpen(_obj, meta) { @@ -17,6 +17,7 @@ export const getCapTPHandler = (E, send, getBootstrapObject) => { origin, sendObj, getBootstrapObject, + { harden, ...powers }, ); chans.set(channelHandle, [dispatch, abort]); }, diff --git a/packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js b/packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js index 25c997fd874..f1349c983ee 100644 --- a/packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js +++ b/packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js @@ -78,9 +78,11 @@ export function buildRootObject(vatPowers) { // Assign the captp handler. // TODO: Break this out into a separate vat. - const captpHandler = getCapTPHandler(E, send, () => + const captpHandler = getCapTPHandler( + send, // Harden only our exported objects. - harden(exportedToCapTP), + () => harden(exportedToCapTP), + { E, harden, ...vatPowers }, ); registerURLHandler(captpHandler, '/private/captp'); }