Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@agoric/marshal

Package Overview
Dependencies
Maintainers
5
Versions
156
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agoric/marshal - npm Package Compare versions

Comparing version 0.3.0 to 0.3.1

15

CHANGELOG.md

@@ -6,2 +6,17 @@ # Change Log

## [0.3.1](https://github.com/Agoric/agoric-sdk/compare/@agoric/marshal@0.3.0...@agoric/marshal@0.3.1) (2021-02-16)
### Bug Fixes
* **marshal:** reject getters in pass-by-ref, even if it returns a function ([#2438](https://github.com/Agoric/agoric-sdk/issues/2438)) ([b9368b6](https://github.com/Agoric/agoric-sdk/commit/b9368b6ee16a5562a622551539eff2b8708f0fdd)), closes [#2436](https://github.com/Agoric/agoric-sdk/issues/2436)
* Correlate sent errors with received errors ([73b9cfd](https://github.com/Agoric/agoric-sdk/commit/73b9cfd33cf7842bdc105a79592028649cb1c92a))
* Far and Remotable do unverified local marking rather than WeakMap ([#2361](https://github.com/Agoric/agoric-sdk/issues/2361)) ([ab59ab7](https://github.com/Agoric/agoric-sdk/commit/ab59ab779341b9740827b7c4cca4680e7b7212b2))
* review comments ([7db7e5c](https://github.com/Agoric/agoric-sdk/commit/7db7e5c4c569dfedff8d748dd58893218b0a2458))
* use assert rather than FooError constructors ([f860c5b](https://github.com/Agoric/agoric-sdk/commit/f860c5bf5add165a08cb5bd543502857c3f57998))
# [0.3.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/marshal@0.2.7...@agoric/marshal@0.3.0) (2020-12-10)

@@ -8,0 +23,0 @@

3

index.js
export {
REMOTE_STYLE,
mustPassByPresence,
getInterfaceOf,

@@ -8,3 +7,2 @@ pureCopy,

getErrorConstructor,
mustPassByRemote,
sameValueZero,

@@ -14,2 +12,3 @@ passStyleOf,

Remotable,
Far,
} from './src/marshal';
{
"name": "@agoric/marshal",
"version": "0.3.0",
"version": "0.3.1",
"description": "marshal",

@@ -38,9 +38,9 @@ "parsers": {

"dependencies": {
"@agoric/assert": "^0.2.0",
"@agoric/eventual-send": "^0.13.0",
"@agoric/assert": "^0.2.1",
"@agoric/eventual-send": "^0.13.1",
"@agoric/nat": "^2.0.1",
"@agoric/promise-kit": "^0.2.0"
"@agoric/promise-kit": "^0.2.1"
},
"devDependencies": {
"@agoric/install-ses": "^0.5.0",
"@agoric/install-ses": "^0.5.1",
"ava": "^3.12.1",

@@ -76,3 +76,3 @@ "esm": "^3.2.25",

},
"gitHead": "f9fab94b70ed22d7fcccb261af11ed8dd6348614"
"gitHead": "93ccbba4efc9e375032becfdf7005502f9c4db18"
}

@@ -7,3 +7,3 @@ // @ts-check

import Nat from '@agoric/nat';
import { assert, details as d, q } from '@agoric/assert';
import { assert, details as X, q } from '@agoric/assert';
import { isPromise } from '@agoric/promise-kit';

@@ -13,17 +13,35 @@

const {
getPrototypeOf,
setPrototypeOf,
getOwnPropertyDescriptors,
is,
isFrozen,
fromEntries,
prototype: objectPrototype,
} = Object;
const { ownKeys } = Reflect;
// TODO: Use just 'remote' when we're willing to make a breaking change.
export const REMOTE_STYLE = 'presence';
// TODO, remove the mustPassByPresence alias when we make a breaking change.
// eslint-disable-next-line no-use-before-define
export { mustPassByRemote as mustPassByPresence };
const PASS_STYLE = Symbol.for('passStyle');
/**
* @type {WeakMap<Object, InterfaceSpec>}
*/
const remotableToInterface = new WeakMap();
/** @type {MarshalGetInterfaceOf} */
export function getInterfaceOf(maybeRemotable) {
return remotableToInterface.get(maybeRemotable);
export function getInterfaceOf(val) {
if (typeof val !== 'object' || val === null) {
return undefined;
}
if (val[PASS_STYLE] !== REMOTE_STYLE) {
return undefined;
}
assert(isFrozen(val), X`Remotable ${val} must be frozen`, TypeError);
const iface = val[Symbol.toStringTag];
assert.typeof(
iface,
'string',
X`Remotable interface currently can only be a string`,
);
return iface;
}

@@ -69,2 +87,6 @@

// Object.entries(obj) takes a snapshot (even if a Proxy).
// Since we already know it is a copyRecord or copyArray, we
// know that Object.entries is safe enough. On a copyRecord it
// will represent all the own properties. On a copyArray it
// will represent all the own properties except for the length.
Object.entries(obj).forEach(([prop, value]) => {

@@ -96,4 +118,7 @@ copy[prop] = pureCopy(value, already);

case REMOTE_STYLE: {
throw TypeError(
`Input value ${passStyle} cannot be copied as it must be passed by reference`,
assert.fail(
X`Input value ${q(
passStyle,
)} cannot be copied as it must be passed by reference`,
TypeError,
);

@@ -103,7 +128,10 @@ }

case 'promise': {
throw TypeError(`Promises cannot be copied`);
assert.fail(X`Promises cannot be copied`, TypeError);
}
default:
throw TypeError(`Input value ${passStyle} is not recognized as data`);
assert.fail(
X`Input value ${q(passStyle)} is not recognized as data`,
TypeError,
);
}

@@ -144,8 +172,9 @@ }

}
const proto = Object.getPrototypeOf(val);
const proto = getPrototypeOf(val);
const { name } = val;
const EC = getErrorConstructor(name);
if (!EC || EC.prototype !== proto) {
throw TypeError(
`Errors must inherit from an error class .prototype ${val}`,
assert.fail(
X`Errors must inherit from an error class .prototype ${val}`,
TypeError,
);

@@ -159,14 +188,16 @@ }

...restDescs
} = Object.getOwnPropertyDescriptors(val);
const restNames = Object.keys(restDescs);
if (restNames.length >= 1) {
throw new TypeError(`Unexpected own properties in error: ${restNames}`);
}
} = getOwnPropertyDescriptors(val);
const restKeys = ownKeys(restDescs);
assert(
restKeys.length === 0,
X`Unexpected own properties in error: ${q(restKeys)}`,
TypeError,
);
if (mDesc) {
if (typeof mDesc.value !== 'string') {
throw new TypeError(`Malformed error object: ${val}`);
}
if (mDesc.enumerable) {
throw new TypeError(`An error's .message must not be enumerable`);
}
assert.typeof(mDesc.value, 'string', X`Malformed error object: ${val}`);
assert(
!mDesc.enumerable,
X`An error's .message must not be enumerable`,
TypeError,
);
}

@@ -184,25 +215,33 @@ return true;

}
if (Object.getPrototypeOf(val) !== Array.prototype) {
throw new TypeError(`Malformed array: ${val}`);
}
assert(
getPrototypeOf(val) === Array.prototype,
X`Malformed array: ${val}`,
TypeError,
);
const len = val.length;
const descs = Object.getOwnPropertyDescriptors(val);
const descs = getOwnPropertyDescriptors(val);
for (let i = 0; i < len; i += 1) {
const desc = descs[i];
if (!desc) {
throw new TypeError(`Arrays must not contain holes: ${i}`);
}
if (!('value' in desc)) {
throw new TypeError(`Arrays must not contain accessors: ${i}`);
}
if (typeof desc.value === 'function') {
throw new TypeError(`Arrays must not contain methods: ${i}`);
}
if (!desc.enumerable) {
throw new TypeError(`Array elements must be enumerable: ${i}`);
}
assert(desc, X`Arrays must not contain holes: ${q(i)}`, TypeError);
assert(
'value' in desc,
X`Arrays must not contain accessors: ${q(i)}`,
TypeError,
);
assert(
typeof desc.value !== 'function',
X`Arrays must not contain methods: ${q(i)}`,
TypeError,
);
assert(
desc.enumerable,
X`Array elements must be enumerable: ${q(i)}`,
TypeError,
);
}
if (Object.keys(descs).length !== len + 1) {
throw new TypeError(`Arrays must not have non-indexes: ${val}`);
}
assert(
ownKeys(descs).length === len + 1,
X`Arrays must not have non-indexes: ${val}`,
TypeError,
);
return true;

@@ -216,11 +255,18 @@ }

function isPassByCopyRecord(val) {
if (Object.getPrototypeOf(val) !== Object.prototype) {
if (getPrototypeOf(val) !== objectPrototype) {
return false;
}
const descEntries = Object.entries(Object.getOwnPropertyDescriptors(val));
if (descEntries.length === 0) {
const descs = getOwnPropertyDescriptors(val);
const descKeys = ownKeys(descs);
if (descKeys.length === 0) {
// empty non-array objects are pass-by-remote, not pass-by-copy
// TODO Beware: Unmarked empty records will become pass-by-copy
// See https://github.com/Agoric/agoric-sdk/issues/2018
return false;
}
for (const [_key, desc] of descEntries) {
for (const descKey of descKeys) {
if (typeof descKey === 'symbol') {
return false;
}
const desc = descs[descKey];
if (typeof desc.value === 'function') {

@@ -230,14 +276,19 @@ return false;

}
for (const [key, desc] of descEntries) {
if (typeof key === 'symbol') {
throw new TypeError(
`Records must not have symbol-named properties: ${String(key)}`,
);
}
if (!('value' in desc)) {
throw new TypeError(`Records must not contain accessors: ${key}`);
}
if (!desc.enumerable) {
throw new TypeError(`Record fields must be enumerable: ${key}`);
}
for (const descKey of descKeys) {
assert.typeof(
descKey,
'string',
X`Pass by copy records can only have string-named own properties`,
);
const desc = descs[descKey];
assert(
!('get' in desc),
X`Records must not contain accessors: ${q(descKey)}`,
TypeError,
);
assert(
desc.enumerable,
X`Record fields must be enumerable: ${q(descKey)}`,
TypeError,
);
}

@@ -247,2 +298,49 @@ return true;

const makeRemotableProto = (oldProto, allegedName) => {
assert(
oldProto === objectPrototype || oldProto === null,
X`For now, remotables cannot inherit from anything unusual`,
);
// Assign the arrow function to a variable to set its .name.
const toString = () => `[${allegedName}]`;
return harden({
__proto__: oldProto,
[PASS_STYLE]: REMOTE_STYLE,
toString,
[Symbol.toStringTag]: allegedName,
});
};
const assertRemotableProto = val => {
assert.typeof(val, 'object', X`cannot serialize non-objects like ${val}`);
assert(!Array.isArray(val), X`Arrays cannot be pass-by-remote`);
assert(val !== null, X`null cannot be pass-by-remote`);
const protoProto = getPrototypeOf(val);
assert(
protoProto === objectPrototype || protoProto === null,
X`The Remotable Proto marker cannot inherit from anything unusual`,
);
assert(isFrozen(val), X`The Remotable proto must be frozen`);
const {
// @ts-ignore
[PASS_STYLE]: { value: passStyleValue },
// @ts-ignore
toString: { value: toStringValue },
// @ts-ignore
[Symbol.toStringTag]: { value: toStringTagValue },
...rest
} = getOwnPropertyDescriptors(val);
assert(
ownKeys(rest).length === 0,
X`Unexpect properties on Remotable Proto ${ownKeys(rest)}`,
);
assert(
passStyleValue === REMOTE_STYLE,
X`Expected ${q(REMOTE_STYLE)}, not ${q(passStyleValue)}`,
);
assert.typeof(toStringValue, 'function', X`toString must be a function`);
assert.typeof(toStringTagValue, 'string', X`@@toStringTag must be a string`);
};
/**

@@ -257,23 +355,22 @@ * Ensure that val could become a legitimate remotable. This is used

// throws exception if cannot
if (typeof val !== 'object') {
throw new Error(`cannot serialize non-objects like ${val}`);
}
if (Array.isArray(val)) {
throw new Error(`Arrays cannot be pass-by-remote`);
}
if (val === null) {
throw new Error(`null cannot be pass-by-remote`);
}
assert.typeof(val, 'object', X`cannot serialize non-objects like ${val}`);
assert(!Array.isArray(val), X`Arrays cannot be pass-by-remote`);
assert(val !== null, X`null cannot be pass-by-remote`);
const names = Object.getOwnPropertyNames(val);
names.forEach(name => {
if (typeof val[name] !== 'function') {
throw new Error(
`cannot serialize objects with non-methods like the .${name} in ${val}`,
);
// return false;
}
const descs = getOwnPropertyDescriptors(val);
const keys = ownKeys(descs); // enumerable-and-not, string-or-Symbol
keys.forEach(key => {
assert(
// @ts-ignore
!('get' in descs[key]),
X`cannot serialize objects with getters like ${q(String(key))} in ${val}`,
);
assert.typeof(
val[key],
'function',
X`cannot serialize objects with non-methods like ${q(
String(key),
)} in ${val}`,
);
});
// ok!
}

@@ -284,16 +381,10 @@

*/
export function mustPassByRemote(val) {
if (!Object.isFrozen(val)) {
throw new Error(`cannot serialize non-frozen objects like ${val}`);
}
function assertRemotable(val) {
assert(isFrozen(val), X`cannot serialize non-frozen objects like ${val}`);
if (getInterfaceOf(val) === undefined) {
// Not a registered Remotable, so check its contents.
assertCanBeRemotable(val);
}
assertCanBeRemotable(val);
// It's not a registered Remotable, so enforce the prototype check.
const p = Object.getPrototypeOf(val);
if (p !== null && p !== Object.prototype) {
mustPassByRemote(p);
const p = getPrototypeOf(val);
if (p !== null && p !== objectPrototype) {
assertRemotableProto(p);
}

@@ -316,3 +407,3 @@ }

export function sameValueZero(x, y) {
return x === y || Object.is(x, y);
return x === y || is(x, y);
}

@@ -325,3 +416,3 @@

* 2: pass-by-copy: all string-named own properties are data, not methods
* the object must inherit from Object.prototype or null
* the object must inherit from objectPrototype or null
* 3: the empty object is pass-by-remote, for identity comparison

@@ -366,15 +457,15 @@ *

// TODO Hilbert hotel
throw new Error(`property "${QCLASS}" reserved`);
assert.fail(X`property ${q(QCLASS)} reserved`);
}
if (!Object.isFrozen(val)) {
throw new Error(
`Cannot pass non-frozen objects like ${val}. Use harden()`,
);
}
assert(
isFrozen(val),
X`Cannot pass non-frozen objects like ${val}. Use harden()`,
);
if (isPromise(val)) {
return 'promise';
}
if (typeof val.then === 'function') {
throw new Error(`Cannot pass non-promise thenables`);
}
assert(
typeof val.then !== 'function',
X`Cannot pass non-promise thenables`,
);
if (isPassByCopyError(val)) {

@@ -389,7 +480,7 @@ return 'copyError';

}
mustPassByRemote(val);
assertRemotable(val);
return REMOTE_STYLE;
}
case 'function': {
throw new Error(`Bare functions like ${val} are disabled for now`);
assert.fail(X`Bare functions like ${val} are disabled for now`);
}

@@ -405,3 +496,3 @@ case 'undefined':

default: {
throw new TypeError(`Unrecognized typeof ${typestr}`);
assert.fail(X`Unrecognized typeof ${q(typestr)}`, TypeError);
}

@@ -444,5 +535,3 @@ }

const index = Nat(allegedIndex);
if (index >= ibids.length) {
throw new RangeError(`ibid out of range: ${index}`);
}
assert(index < ibids.length, X`ibid out of range: ${index}`, RangeError);
const result = ibids[index];

@@ -459,6 +548,9 @@ if (unfinishedIbids.has(result)) {

case 'forbidCycles': {
throw new TypeError(`Ibid cycle at ${index}`);
assert.fail(X`Ibid cycle at ${q(index)}`, TypeError);
}
default: {
throw new TypeError(`Unrecognized cycle policy: ${cyclePolicy}`);
assert.fail(
X`Unrecognized cycle policy: ${q(cyclePolicy)}`,
TypeError,
);
}

@@ -503,3 +595,13 @@ }

convertSlotToVal = defaultSlotToValFn,
{ marshalName = 'anon-marshal' } = {},
) {
assert.typeof(marshalName, 'string');
// Ascending numbers identifying the sending of errors relative to this
// marshal instance.
let errorCount = 0;
const nextErrorId = () => {
errorCount += 1;
return `error:${marshalName}#${errorCount}`;
};
/**

@@ -525,6 +627,23 @@ * @template Slot

if (iface !== undefined) {
/*
if (iface === undefined && passStyleOf(val) === REMOTE_STYLE) {
// iface = `Alleged: remotable at slot ${slotIndex}`;
if (
getPrototypeOf(val) === objectPrototype &&
ownKeys(val).length === 0
) {
// For now, skip the diagnostic if we have a pure empty object
} else {
try {
assert.fail(X`Serialize ${val} generates needs iface`);
} catch (err) {
console.info(err);
}
}
}
*/
if (iface === undefined) {
return harden({
[QCLASS]: 'slot',
iface,
index: slotIndex,

@@ -535,2 +654,3 @@ });

[QCLASS]: 'slot',
iface,
index: slotIndex,

@@ -540,6 +660,32 @@ });

function makeReplacer(slots, slotMap) {
/**
* @template Slot
* @type {Serialize<Slot>}
*/
const serialize = root => {
const slots = [];
// maps val (promise or remotable) to index of slots[]
const slotMap = new Map();
const ibidTable = makeReplacerIbidTable();
return function replacer(_, val) {
/**
* Just consists of data that rounds trips to plain data.
*
* @typedef {any} PlainJSONData
*/
/**
* Must encode `val` into plain JSON data *canonically*, such that
* `sameStructure(v1, v2)` implies
* `JSON.stringify(encode(v1)) === JSON.stringify(encode(v2))`
* For each record, we only accept sortable property names
* (no anonymous symbols) and on the encoded form. The sort
* order of these names must be the same as their enumeration
* order, so a `JSON.stringify` of the encoded form agrees with
* a canonical-json stringify of the encoded form.
*
* @param {Passable} val
* @returns {PlainJSONData}
*/
const encode = val => {
// First we handle all primitives. Some can be represented directly as

@@ -563,3 +709,3 @@ // JSON, and some must be encoded as [QCLASS] composites.

}
if (Object.is(val, -0)) {
if (is(val, -0)) {
return 0;

@@ -589,3 +735,3 @@ }

default: {
throw assert.fail(d`Unsupported symbol ${q(String(val))}`);
assert.fail(X`Unsupported symbol ${q(String(val))}`);
}

@@ -606,8 +752,11 @@ }

switch (passStyle) {
case 'copyRecord':
case 'copyRecord': {
// Currently copyRecord allows only string keys so this will
// work. If we allow sortable symbol keys, this will need to
// become more interesting.
const names = ownKeys(val).sort();
return fromEntries(names.map(name => [name, encode(val[name])]));
}
case 'copyArray': {
// console.log(`canPassByCopy: ${val}`);
// Purposely in-band for readability, but creates need for
// Hilbert hotel.
return val;
return val.map(encode);
}

@@ -622,6 +771,16 @@ case 'copyError': {

// with the correlation.
const errorId = nextErrorId();
assert.note(val, X`Sent as ${errorId}`);
// TODO we need to instead log to somewhere hidden
// to be revealed when correlating with the received error.
// By sending this to `console.log`, under swingset this is
// enabled by `agoric start --reset -v` and not enabled without
// the `-v` flag.
console.log('Temporary logging of sent error', val);
return harden({
[QCLASS]: 'error',
errorId,
message: `${val.message}`,
name: `${val.name}`,
message: `${val.message}`,
});

@@ -639,3 +798,3 @@ }

default: {
throw new TypeError(`unrecognized passStyle ${passStyle}`);
assert.fail(X`unrecognized passStyle ${q(passStyle)}`, TypeError);
}

@@ -646,17 +805,10 @@ }

};
}
/**
* @template Slot
* @type {Serialize<Slot>}
*/
function serialize(val) {
const slots = [];
const slotMap = new Map(); // maps val (promise or remotable) to
// index of slots[]
const encoded = encode(root);
return harden({
body: JSON.stringify(val, makeReplacer(slots, slotMap)),
body: JSON.stringify(encoded),
slots,
});
}
};

@@ -707,5 +859,7 @@ function makeFullRevive(slots, cyclePolicy) {

const qclass = rawTree[QCLASS];
if (typeof qclass !== 'string') {
throw new TypeError(`invalid qclass typeof ${typeof qclass}`);
}
assert.typeof(
qclass,
'string',
X`invalid qclass typeof ${q(typeof qclass)}`,
);
switch (qclass) {

@@ -726,7 +880,7 @@ // Encoding of primitives not handled by JSON

case 'bigint': {
if (typeof rawTree.digits !== 'string') {
throw new TypeError(
`invalid digits typeof ${typeof rawTree.digits}`,
);
}
assert.typeof(
rawTree.digits,
'string',
X`invalid digits typeof ${q(typeof rawTree.digits)}`,
);
/* eslint-disable-next-line no-undef */

@@ -744,14 +898,20 @@ return BigInt(rawTree.digits);

case 'error': {
if (typeof rawTree.name !== 'string') {
throw new TypeError(
`invalid error name typeof ${typeof rawTree.name}`,
);
assert.typeof(
rawTree.name,
'string',
X`invalid error name typeof ${q(typeof rawTree.name)}`,
);
assert.typeof(
rawTree.message,
'string',
X`invalid error message typeof ${q(typeof rawTree.message)}`,
);
const EC = getErrorConstructor(`${rawTree.name}`) || Error;
const error = harden(new EC(`${rawTree.message}`));
ibidTable.register(error);
if (typeof rawTree.errorId === 'string') {
// errorId is a late addition so be tolerant of its absence.
assert.note(error, X`Received as ${rawTree.errorId}`);
}
if (typeof rawTree.message !== 'string') {
throw new TypeError(
`invalid error message typeof ${typeof rawTree.message}`,
);
}
const EC = getErrorConstructor(`${rawTree.name}`) || Error;
return ibidTable.register(harden(new EC(`${rawTree.message}`)));
return error;
}

@@ -766,3 +926,3 @@

// TODO reverse Hilbert hotel
throw new TypeError(`unrecognized ${QCLASS} ${qclass}`);
assert.fail(X`unrecognized ${q(QCLASS)} ${q(qclass)}`, TypeError);
}

@@ -779,4 +939,5 @@ }

const result = ibidTable.start({});
const names = Object.getOwnPropertyNames(rawTree);
const names = ownKeys(rawTree);
for (const name of names) {
assert.typeof(name, 'string');
result[name] = fullRevive(rawTree[name]);

@@ -794,10 +955,11 @@ }

function unserialize(data, cyclePolicy = 'forbidCycles') {
if (data.body !== `${data.body}`) {
throw new Error(
`unserialize() given non-capdata (.body is ${data.body}, not string)`,
);
}
if (!Array.isArray(data.slots)) {
throw new Error(`unserialize() given non-capdata (.slots are not Array)`);
}
assert.typeof(
data.body,
'string',
X`unserialize() given non-capdata (.body is ${data.body}, not string)`,
);
assert(
Array.isArray(data.slots),
X`unserialize() given non-capdata (.slots are not Array)`,
);
const rawTree = harden(JSON.parse(data.body));

@@ -823,8 +985,14 @@ const fullRevive = makeFullRevive(data.slots, cyclePolicy);

* "Alleged: ", to serve as the alleged name. More general ifaces are not yet
* implemented. This is temporary.
* @param {object} [props={}] Own-properties are copied to the remotable
* implemented. This is temporary. We include the
* "Alleged" as a reminder that we do not yet have SwingSet or Comms Vat
* support for ensuring this is according to the vat hosting the object.
* Currently, Alice can tell Bob about Carol, where VatA (on Alice's behalf)
* misrepresents Carol's `iface`. VatB and therefore Bob will then see
* Carol's `iface` as misrepresented by VatA.
* @param {undefined} [props=undefined] Currently may only be undefined.
* That plan is that own-properties are copied to the remotable
* @param {object} [remotable={}] The object used as the remotable
* @returns {object} remotable, modified for debuggability
*/
function Remotable(iface = 'Remotable', props = {}, remotable = {}) {
function Remotable(iface = 'Remotable', props = undefined, remotable = {}) {
// TODO unimplemented

@@ -834,3 +1002,3 @@ assert.typeof(

'string',
d`Interface ${iface} must be a string; unimplemented`,
X`Interface ${iface} must be a string; unimplemented`,
);

@@ -840,9 +1008,11 @@ // TODO unimplemented

iface === 'Remotable' || iface.startsWith('Alleged: '),
d`For now, iface ${iface} must be "Remotable" or begin with "Alleged: "; unimplemented`,
X`For now, iface ${q(
iface,
)} must be "Remotable" or begin with "Alleged: "; unimplemented`,
);
iface = pureCopy(harden(iface));
// TODO: When iface is richer than just string, we need to get the allegedName
// in a different way.
const allegedName = iface;
assert(props === undefined, X`Remotable props not yet implemented ${props}`);

@@ -852,48 +1022,25 @@ // Fail fast: check that the unmodified object is able to become a Remotable.

// Ensure that the remotable isn't already registered.
if (remotableToInterface.has(remotable)) {
throw Error(`Remotable ${remotable} is already mapped to an interface`);
}
// A prototype for debuggability.
const oldRemotableProto = harden(Object.getPrototypeOf(remotable));
// Fail fast: create a fresh empty object with the old
// prototype in order to check it against our rules.
mustPassByRemote(harden(Object.create(oldRemotableProto)));
// Assign the arrow function to a variable to set its .name.
const toString = () => `[${allegedName}]`;
const remotableProto = harden(
Object.create(oldRemotableProto, {
toString: {
value: toString,
},
[Symbol.toStringTag]: {
value: allegedName,
},
}),
// Ensure that the remotable isn't already marked.
assert(
!(PASS_STYLE in remotable),
X`Remotable ${remotable} is already marked as a ${q(
remotable[PASS_STYLE],
)}`,
);
const remotableProto = makeRemotableProto(
getPrototypeOf(remotable),
allegedName,
);
// Take a static copy of the properties.
const propEntries = Object.entries(props);
// Take a static copy of the enumerable own properties as data properties.
// const propDescs = getOwnPropertyDescriptors({ ...props });
const mutateHardenAndCheck = target => {
// Add the snapshotted properties.
/** @type {PropertyDescriptorMap} */
const newProps = {};
propEntries.forEach(([prop, value]) => (newProps[prop] = { value }));
Object.defineProperties(target, newProps);
// Set the prototype for debuggability.
Object.setPrototypeOf(target, remotableProto);
harden(remotableProto);
// defineProperties(target, propDescs);
setPrototypeOf(target, remotableProto);
harden(target);
assertCanBeRemotable(target);
return target;
};
// Fail fast: check a fresh remotable to see if our rules fit.
const throwawayRemotable = Object.create(oldRemotableProto);
mutateHardenAndCheck(throwawayRemotable);
mutateHardenAndCheck({});

@@ -906,3 +1053,2 @@ // Actually finish the new remotable.

assert(iface !== undefined); // To make TypeScript happy
remotableToInterface.set(remotable, iface);
return remotable;

@@ -913,1 +1059,15 @@ }

export { Remotable };
/**
* A concise convenience for the most common `Remotable` use.
*
* @param {string} farName This name will be prepended with `Alleged: `
* for now to form the `Remotable` `iface` argument.
* @param {object} [remotable={}] The object used as the remotable
* @returns {object} remotable, modified for debuggability
*/
const Far = (farName, remotable = {}) =>
Remotable(`Alleged: ${farName}`, undefined, remotable);
harden(Far);
export { Far };

@@ -128,5 +128,11 @@ /**

* @param {ConvertSlotToVal=} convertSlotToVal
* @param {MakeMarshalOptions=} options
* @returns {Marshal}
*/
/**
* @typedef MakeMarshalOptions
* @property {string=} marshalName
*/
// /////////////////////////////////////////////////////////////////////////////

@@ -133,0 +139,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc