From e0c2aaef33b528b14b1aca13fbf9fc9117a33776 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sun, 10 Aug 2025 11:27:28 -0700 Subject: [PATCH 1/2] feat(ocapn): marshall plugins --- packages/ocapn/src/client/index.js | 10 ++++ packages/ocapn/src/client/ocapn.js | 13 ++++- packages/ocapn/src/client/types.js | 6 ++ packages/ocapn/src/codecs/passable.js | 45 ++++++++++++++- packages/ocapn/test/client.test.js | 67 +++++++++++++++++++++- packages/ocapn/test/codecs/_codecs_util.js | 2 +- 6 files changed, 135 insertions(+), 8 deletions(-) diff --git a/packages/ocapn/src/client/index.js b/packages/ocapn/src/client/index.js index ab4a9408f3..d902ae769e 100644 --- a/packages/ocapn/src/client/index.js +++ b/packages/ocapn/src/client/index.js @@ -17,6 +17,7 @@ * @typedef {import('./types.js').SessionManager} SessionManager * @typedef {import('./ocapn.js').GrantTracker} GrantTracker * @typedef {import('./ocapn.js').Ocapn} Ocapn + * @typedef {import('./types.js').MarshalPlugin} MarshalPlugin */ import { makePromiseKit } from '@endo/promise-kit'; import { makeSyrupWriter } from '../syrup/encode.js'; @@ -155,6 +156,7 @@ const compareSessionKeysForCrossedHellos = ( * @param {GrantTracker} grantTracker * @param {Map} swissnumTable * @param {Map} giftTable + * @param {MarshalPlugin[]} marshalPlugins * @param {any} message */ const handleSessionHandshakeMessage = ( @@ -165,6 +167,7 @@ const handleSessionHandshakeMessage = ( grantTracker, swissnumTable, giftTable, + marshalPlugins, message, ) => { logger.info(`handling handshake message of type ${message.type}`); @@ -269,6 +272,7 @@ const handleSessionHandshakeMessage = ( swissnumTable, giftTable, 'ocapn', + marshalPlugins, ); const session = makeSession({ id: sessionId, @@ -306,6 +310,7 @@ const handleSessionHandshakeMessage = ( * @param {GrantTracker} grantTracker * @param {Map} swissnumTable * @param {Map} giftTable + * @param {MarshalPlugin[]} marshalPlugins * @param {Uint8Array} data */ const handleHandshakeMessageData = ( @@ -316,6 +321,7 @@ const handleHandshakeMessageData = ( grantTracker, swissnumTable, giftTable, + marshalPlugins, data, ) => { try { @@ -346,6 +352,7 @@ const handleHandshakeMessageData = ( grantTracker, swissnumTable, giftTable, + marshalPlugins, message, ); } else { @@ -457,6 +464,7 @@ const makeSessionManager = () => { * @param {boolean} [options.verbose] * @param {Map} [options.swissnumTable] * @param {Map} [options.giftTable] + * @param {MarshalPlugin[]} [options.marshalPlugins] * @returns {Client} */ export const makeClient = ({ @@ -464,6 +472,7 @@ export const makeClient = ({ verbose = false, swissnumTable = new Map(), giftTable = new Map(), + marshalPlugins = [], } = {}) => { /** @type {Map} */ const netlayers = new Map(); @@ -535,6 +544,7 @@ export const makeClient = ({ grantTracker, swissnumTable, giftTable, + marshalPlugins, data, ); } diff --git a/packages/ocapn/src/client/ocapn.js b/packages/ocapn/src/client/ocapn.js index 0e870f569a..50215dad00 100644 --- a/packages/ocapn/src/client/ocapn.js +++ b/packages/ocapn/src/client/ocapn.js @@ -11,6 +11,7 @@ * @typedef {import('./types.js').LocationId} LocationId * @typedef {import('./types.js').OcapnPublicKey} OcapnPublicKey * @typedef {import('./types.js').Session} Session + * @typedef {import('./types.js').MarshalPlugin} MarshalPlugin * @typedef {import('../captp/captp-engine.js').CapTPEngine} CapTPEngine * @typedef {import('../syrup/decode.js').SyrupReader} SyrupReader * @typedef {import('../codecs/components.js').OcapnLocation} OcapnLocation @@ -424,11 +425,12 @@ const makeMakeRemoteKit = ({ * @property {(message: any) => Uint8Array} writeOcapnMessage * * @param {TableKit} tableKit + * @param {MarshalPlugin[]} marshalPlugins * @returns {CodecKit} */ -const makeCodecKit = tableKit => { +const makeCodecKit = (tableKit, marshalPlugins) => { const descCodecs = makeDescCodecs(tableKit); - const passableCodecs = makePassableCodecs(descCodecs); + const passableCodecs = makePassableCodecs(descCodecs, marshalPlugins); const { readOcapnMessage, writeOcapnMessage } = makeOcapnOperationsCodecs( descCodecs, passableCodecs, @@ -815,6 +817,7 @@ const makeBootstrapObject = ( * @param {Map} swissnumTable * @param {Map} giftTable * @param {string} [ourIdLabel] + * @param {MarshalPlugin[]} [marshalPlugins] * @returns {Ocapn} */ export const makeOcapn = ( @@ -829,6 +832,7 @@ export const makeOcapn = ( swissnumTable, giftTable, ourIdLabel = 'OCapN', + marshalPlugins = [], ) => { const commitSendSlots = () => { logger.info(`commitSendSlots`); @@ -1145,7 +1149,10 @@ export const makeOcapn = ( sendDepositGift, ); - const { readOcapnMessage, writeOcapnMessage } = makeCodecKit(tableKit); + const { readOcapnMessage, writeOcapnMessage } = makeCodecKit( + tableKit, + marshalPlugins, + ); function serializeAndSendMessage(message) { // If we dont catch the error here it gets swallowed. diff --git a/packages/ocapn/src/client/types.js b/packages/ocapn/src/client/types.js index f153b95c56..ffe34d7e92 100644 --- a/packages/ocapn/src/client/types.js +++ b/packages/ocapn/src/client/types.js @@ -83,6 +83,12 @@ * @property {(sessionId: Uint8Array) => OcapnPublicKey | undefined} getPeerPublicKeyForSessionId */ +/** + * @typedef {object} MarshalPlugin + * @property {(value: any) => any} encode + * @property {(value: any) => any} decode + */ + /** * @typedef {object} Client * @property {Logger} logger diff --git a/packages/ocapn/src/codecs/passable.js b/packages/ocapn/src/codecs/passable.js index eec4220e03..b6cfce2b93 100644 --- a/packages/ocapn/src/codecs/passable.js +++ b/packages/ocapn/src/codecs/passable.js @@ -3,6 +3,7 @@ /** @typedef {import('../syrup/codec.js').SyrupCodec} SyrupCodec */ /** @typedef {import('../syrup/codec.js').SyrupRecordCodec} SyrupRecordCodec */ /** @typedef {import('./descriptors.js').DescCodecs} DescCodecs */ +/** @typedef {import('../client/types.js').MarshalPlugin} MarshalPlugin */ import { passStyleOf as realPassStyleOf } from '@endo/pass-style'; import { @@ -26,6 +27,7 @@ import { makeOcapnRecordCodecFromDefinition, } from './util.js'; +const { freeze } = Object; const quote = JSON.stringify; // OCapN Passable Atoms @@ -78,6 +80,31 @@ const AtomCodecs = { byteArray: BytestringCodec, }; +/** + * @param {any} value + * @param {MarshalPlugin[]} marshalPlugins + * @returns {any} + */ +const handleMarshalPluginsEncode = (value, marshalPlugins) => { + for (const plugin of marshalPlugins) { + const result = plugin.encode(value); + if (result !== undefined) { + return result; + } + } + return value; +}; + +const handleMarshalPluginsDecode = (value, marshalPlugins) => { + for (const plugin of marshalPlugins) { + const result = plugin.decode(value); + if (result !== undefined) { + return result; + } + } + return value; +}; + /** * @typedef {object} PassableCodecs * @property {SyrupCodec} PassableCodec @@ -85,9 +112,10 @@ const AtomCodecs = { /** * @param {DescCodecs} descCodecs + * @param {MarshalPlugin[]} marshalPlugins * @returns {PassableCodecs} */ -export const makePassableCodecs = descCodecs => { +export const makePassableCodecs = (descCodecs, marshalPlugins) => { const { ReferenceCodec } = descCodecs; // OCapN Passable Containers @@ -211,7 +239,7 @@ export const makePassableCodecs = descCodecs => { }, ); - const OcapnPassableUnionCodec = makeTypeHintUnionCodec( + const OcapnPassableUnionInternalCodec = makeTypeHintUnionCodec( 'OcapnPassable', // syrup type hint -> codec { @@ -286,6 +314,19 @@ export const makePassableCodecs = descCodecs => { }, ); + // OcapnPassable is a wrapper around OcapnPassableInternal that handles marshal plugins. + /** @type {SyrupCodec} */ + const OcapnPassableUnionCodec = freeze({ + read(syrupReader) { + const value = OcapnPassableUnionInternalCodec.read(syrupReader); + return handleMarshalPluginsDecode(value, marshalPlugins); + }, + write(providedValue, syrupWriter) { + const value = handleMarshalPluginsEncode(providedValue, marshalPlugins); + return OcapnPassableUnionInternalCodec.write(value, syrupWriter); + }, + }); + const ContainerCodecs = { list: makeListCodecFromEntryCodec('OcapnList', OcapnPassableUnionCodec), struct: OcapnStructCodec, diff --git a/packages/ocapn/test/client.test.js b/packages/ocapn/test/client.test.js index 11120aa568..eca8935a04 100644 --- a/packages/ocapn/test/client.test.js +++ b/packages/ocapn/test/client.test.js @@ -2,6 +2,7 @@ /** @typedef {import('../src/codecs/components.js').OcapnLocation} OcapnLocation */ /** @typedef {import('../src/client/types.js').Client} Client */ +/** @typedef {import('../src/client/types.js').MarshalPlugin} MarshalPlugin */ import test from '@endo/ses-ava/prepare-endo.js'; import { E } from '@endo/eventual-send'; @@ -11,16 +12,23 @@ import { makeTcpNetLayer } from '../src/netlayers/tcp-test-only.js'; import { makeClient } from '../src/client/index.js'; import { testWithErrorUnwrapping } from './_util.js'; import { encodeSwissnum } from '../src/client/util.js'; +import { makeTagged, passStyleOf } from '../src/pass-style-helpers.js'; /** * @param {string} debugLabel * @param {() => Map} [makeDefaultSwissnumTable] + * @param {MarshalPlugin[]} [marshalPlugins] * @returns {Promise<{ client: Client, location: OcapnLocation }>} */ -const makeTestClient = async (debugLabel, makeDefaultSwissnumTable) => { +const makeTestClient = async ( + debugLabel, + makeDefaultSwissnumTable, + marshalPlugins, +) => { const client = makeClient({ debugLabel, swissnumTable: makeDefaultSwissnumTable && makeDefaultSwissnumTable(), + marshalPlugins, }); const tcpNetlayer = await makeTcpNetLayer({ client }); client.registerNetlayer(tcpNetlayer); @@ -28,14 +36,21 @@ const makeTestClient = async (debugLabel, makeDefaultSwissnumTable) => { return { client, location }; }; -const makeTestClientPair = async makeDefaultSwissnumTable => { +/** + * @param {() => Map} makeDefaultSwissnumTable + * @param {MarshalPlugin[]} [marshalPlugins] + * @returns {Promise<{ clientA: Client, clientB: Client, locationB: OcapnLocation, shutdownBoth: () => void, ocapnA: Ocapn, bootstrapB: any }>} + */ +const makeTestClientPair = async (makeDefaultSwissnumTable, marshalPlugins) => { const { client: clientA } = await makeTestClient( 'A', makeDefaultSwissnumTable, + marshalPlugins, ); const { client: clientB, location: locationB } = await makeTestClient( 'B', makeDefaultSwissnumTable, + marshalPlugins, ); const shutdownBoth = () => { clientA.shutdown(); @@ -134,3 +149,51 @@ testWithErrorUnwrapping( shutdownBoth(); }, ); + +test('marshal plugins roundtrip', async t => { + class Greeble { + constructor(id) { + this.id = id; + } + } + + const testObjectTable = new Map(); + testObjectTable.set( + 'GreebleMaker', + Far('makeGreeble', id => { + return new Greeble(id); + }), + ); + + const GreeblePlugin = { + encode: value => { + if (value instanceof Greeble) { + return makeTagged('Greeble', value.id); + } + return undefined; + }, + decode: value => { + if ( + passStyleOf(value) === 'tagged' && + value[Symbol.toStringTag] === 'Greeble' + ) { + return new Greeble(value.payload); + } + return undefined; + }, + }; + + const { bootstrapB: bootstrap, shutdownBoth } = await makeTestClientPair( + () => testObjectTable, + [GreeblePlugin], + ); + + const makeGreeble = E(bootstrap).fetch(encodeSwissnum('GreebleMaker')); + const greeble = await E(makeGreeble)(123n); + + // Sanity check + t.is(greeble.id, 123n, 'Greeble id is 123'); + t.truthy(greeble instanceof Greeble, 'Greeble is a Greeble'); + + shutdownBoth(); +}); diff --git a/packages/ocapn/test/codecs/_codecs_util.js b/packages/ocapn/test/codecs/_codecs_util.js index cd2db02b10..afd452101b 100644 --- a/packages/ocapn/test/codecs/_codecs_util.js +++ b/packages/ocapn/test/codecs/_codecs_util.js @@ -204,7 +204,7 @@ export const makeCodecTestKit = (peerLocation = defaultPeerLocation) => { sendDepositGift, ); const descCodecs = makeDescCodecs(tableKit); - const passableCodecs = makePassableCodecs(descCodecs); + const passableCodecs = makePassableCodecs(descCodecs, []); const { OcapnMessageUnionCodec } = makeOcapnOperationsCodecs( descCodecs, passableCodecs, From 6bb49b07f0d7a61e3892d0d290acaf78c26b9ec2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Sun, 10 Aug 2025 17:01:27 -0700 Subject: [PATCH 2/2] feat(pbcap): send automerge docs over OCapN --- packages/ocapn/index.js | 17 ++ packages/pbcap/CHANGELOG.md | 4 + packages/pbcap/LICENSE | 201 +++++++++++++++++++ packages/pbcap/NEWS.md | 1 + packages/pbcap/README.md | 4 + packages/pbcap/SECURITY.md | 38 ++++ packages/pbcap/index.js | 0 packages/pbcap/package.json | 78 ++++++++ packages/pbcap/src/index.js | 135 +++++++++++++ packages/pbcap/src/repo.js | 20 ++ packages/pbcap/src/root-doc.js | 62 ++++++ packages/pbcap/test/index.test.js | 57 ++++++ packages/pbcap/tsconfig.build.json | 12 ++ packages/pbcap/tsconfig.json | 9 + yarn.lock | 311 ++++++++++++++++++++++++++++- 15 files changed, 946 insertions(+), 3 deletions(-) create mode 100644 packages/pbcap/CHANGELOG.md create mode 100644 packages/pbcap/LICENSE create mode 100644 packages/pbcap/NEWS.md create mode 100644 packages/pbcap/README.md create mode 100644 packages/pbcap/SECURITY.md create mode 100644 packages/pbcap/index.js create mode 100644 packages/pbcap/package.json create mode 100644 packages/pbcap/src/index.js create mode 100644 packages/pbcap/src/repo.js create mode 100644 packages/pbcap/src/root-doc.js create mode 100644 packages/pbcap/test/index.test.js create mode 100644 packages/pbcap/tsconfig.build.json create mode 100644 packages/pbcap/tsconfig.json diff --git a/packages/ocapn/index.js b/packages/ocapn/index.js index e69de29bb2..e3b435a6f4 100644 --- a/packages/ocapn/index.js +++ b/packages/ocapn/index.js @@ -0,0 +1,17 @@ +import { E } from '@endo/eventual-send'; +import { Far } from '@endo/marshal'; + +import { makeClient } from './src/client/index.js'; +import { makeTcpNetLayer } from './src/netlayers/tcp-test-only.js'; +import { makeTagged, passStyleOf } from './src/pass-style-helpers.js'; +import { encodeSwissnum } from './src/client/util.js'; + +export { + makeClient, + makeTcpNetLayer, + makeTagged, + passStyleOf, + encodeSwissnum, + E, + Far, +}; diff --git a/packages/pbcap/CHANGELOG.md b/packages/pbcap/CHANGELOG.md new file mode 100644 index 0000000000..e4d87c4d45 --- /dev/null +++ b/packages/pbcap/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/packages/pbcap/LICENSE b/packages/pbcap/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/packages/pbcap/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/pbcap/NEWS.md b/packages/pbcap/NEWS.md new file mode 100644 index 0000000000..8e2b76f65a --- /dev/null +++ b/packages/pbcap/NEWS.md @@ -0,0 +1 @@ +User-visible changes in `pbcap`: diff --git a/packages/pbcap/README.md b/packages/pbcap/README.md new file mode 100644 index 0000000000..9b5d3666a3 --- /dev/null +++ b/packages/pbcap/README.md @@ -0,0 +1,4 @@ +# Peanut Butter Cap + +a tastly little morsel of Ocapn + Automerge. + diff --git a/packages/pbcap/SECURITY.md b/packages/pbcap/SECURITY.md new file mode 100644 index 0000000000..9dbbb79534 --- /dev/null +++ b/packages/pbcap/SECURITY.md @@ -0,0 +1,38 @@ +# Security Policy + +## Supported Versions + +The SES package and associated Endo packages are still undergoing development and security review, and all +users are encouraged to use the latest version available. Security fixes will +be made for the most recent branch only. + +## Coordinated Vulnerability Disclosure of Security Bugs + +SES stands for fearless cooperation, and strong security requires strong collaboration with security researchers. If you believe that you have found a security sensitive bug that should not be disclosed until a fix has been made available, we encourage you to report it. To report a bug in HardenedJS, you have several options that include: + +* Reporting the issue to the [Agoric HackerOne vulnerability rewards program](https://hackerone.com/agoric). + +* Sending an email to security at (@) agoric.com., encrypted or unencrypted. To encrypt, please use @Warner’s personal GPG key [A476E2E6 11880C98 5B3C3A39 0386E81B 11CAA07A](http://www.lothar.com/warner-gpg.html) . + +* Sending a message on Keybase to `@agoric_security`, or sharing code and other log files via Keybase’s encrypted file system. ((_keybase_private/agoric_security,$YOURNAME). + +* It is important to be able to provide steps that reproduce the issue and demonstrate its impact with a Proof of Concept example in an initial bug report. Before reporting a bug, a reporter may want to have another trusted individual reproduce the issue. + +* A bug reporter can expect acknowledgment of a potential vulnerability reported through [security@agoric.com](mailto:security@agoric.com) within one business day of submitting a report. If an acknowledgement of an issue is not received within this time frame, especially during a weekend or holiday period, please reach out again. Any issues reported to the HackerOne program will be acknowledged within the time frames posted on the program page. + * The bug triage team and Agoric code maintainers are primarily located in the San Francisco Bay Area with business hours in [Pacific Time](https://www.timeanddate.com/worldclock/usa/san-francisco) . + +* For the safety and security of those who depend on the code, bug reporters should avoid publicly sharing the details of a security bug on Twitter, Discord, Telegram, or in public Github issues during the coordination process. + +* Once a vulnerability report has been received and triaged: + * Agoric code maintainers will confirm whether it is valid, and will provide updates to the reporter on validity of the report. + * It may take up to 72 hours for an issue to be validated, especially if reported during holidays or on weekends. + +* When the Agoric team has verified an issue, remediation steps and patch release timeline information will be shared with the reporter. + * Complexity, severity, impact, and likelihood of exploitation are all vital factors that determine the amount of time required to remediate an issue and distribute a software patch. + * If an issue is Critical or High Severity, Agoric code maintainers will release a security advisory to notify impacted parties to prepare for an emergency patch. + * While the current industry standard for vulnerability coordination resolution is 90 days, Agoric code maintainers will strive to release a patch as quickly as possible. + +When a bug patch is included in a software release, the Agoric code maintainers will: + * Confirm the version and date of the software release with the reporter. + * Provide information about the security issue that the software release resolves. + * Credit the bug reporter for discovery by adding thanks in release notes, securing a CVE designation, or adding the researcher’s name to a Hall of Fame. diff --git a/packages/pbcap/index.js b/packages/pbcap/index.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/pbcap/package.json b/packages/pbcap/package.json new file mode 100644 index 0000000000..8f1df2b8a0 --- /dev/null +++ b/packages/pbcap/package.json @@ -0,0 +1,78 @@ +{ + "name": "@endo/pbcap", + "version": "1.1.10", + "private": true, + "description": null, + "keywords": [], + "author": "Endo contributors", + "license": "Apache-2.0", + "homepage": "https://github.com/endojs/endo/tree/master/packages/pbcap#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/endojs/endo.git", + "directory": "packages/pbcap" + }, + "bugs": { + "url": "https://github.com/endojs/endo/issues" + }, + "type": "module", + "main": "./index.js", + "module": "./index.js", + "exports": { + ".": "./index.js", + "./package.json": "./package.json" + }, + "scripts": { + "build": "exit 0", + "lint": "yarn lint:types && yarn lint:eslint", + "lint-fix": "yarn lint:eslint --fix && yarn lint:types", + "lint:eslint": "eslint '**/*.js'", + "lint:types": "tsc", + "prepack": "tsc --build tsconfig.build.json", + "postpack": "git clean -f '*.d.ts*' '*.tsbuildinfo'", + "test": "ava", + "test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js", + "test:xs": "exit 0" + }, + "devDependencies": { + "@endo/lockdown": "workspace:^", + "@endo/ses-ava": "workspace:^", + "ava": "^6.1.3", + "c8": "^7.14.0", + "eslint": "^8.57.0", + "tsd": "^0.31.2", + "typescript": "~5.6.3" + }, + "files": [ + "./*.d.ts", + "./*.js", + "./*.map", + "LICENSE*", + "SECURITY*", + "dist", + "lib", + "src", + "tools" + ], + "publishConfig": { + "access": "public" + }, + "eslintConfig": { + "extends": [ + "plugin:@endo/internal" + ] + }, + "ava": { + "files": [ + "test/**/*.test.*" + ], + "timeout": "2m" + }, + "dependencies": { + "@automerge/automerge-repo": "^2.2.0", + "@automerge/automerge-repo-network-websocket": "^2.2.0", + "@automerge/automerge-repo-storage-nodefs": "^2.2.0", + "@endo/init": "workspace:^", + "@endo/ocapn": "workspace:^" + } +} diff --git a/packages/pbcap/src/index.js b/packages/pbcap/src/index.js new file mode 100644 index 0000000000..9b83173e84 --- /dev/null +++ b/packages/pbcap/src/index.js @@ -0,0 +1,135 @@ +import '@endo/init'; + +import { DocHandle } from '@automerge/automerge-repo'; +import { + makeTagged, + passStyleOf, + encodeSwissnum, + makeClient, + makeTcpNetLayer, + E, + Far, +} from '@endo/ocapn'; +import { makeRepo } from './repo.js'; + +const { freeze } = Object; + +const makeAutomergePlugin = repo => { + return freeze({ + encode: value => { + if (value instanceof DocHandle) { + // console.log('encode Automerge DocHandle', value.url); + return makeTagged('Automerge', value.url); + } + return undefined; + }, + decode: value => { + if ( + typeof value === 'object' && + value !== null && + passStyleOf(value) === 'tagged' && + value[Symbol.toStringTag] === 'Automerge' + ) { + // console.log('decode Automerge DocHandle', value.payload); + return repo.find(value.payload); + } + return undefined; + }, + }); +}; + +/** + * @param {string} debugLabel + * @param makeDefaultSwissnumTable + * @returns {Promise<{ client: Client, location: OcapnLocation, repo: Repo, rootDocUrl: string, handle: DocHandle }>} + */ +const makePBCapClient = async (debugLabel, makeDefaultSwissnumTable) => { + const { repo, rootDocUrl } = makeRepo(debugLabel); + const automergePlugin = makeAutomergePlugin(repo); + const client = makeClient({ + debugLabel, + swissnumTable: makeDefaultSwissnumTable && makeDefaultSwissnumTable(), + marshalPlugins: [automergePlugin], + }); + const tcpNetlayer = await makeTcpNetLayer({ client }); + client.registerNetlayer(tcpNetlayer); + const { location } = tcpNetlayer; + const handle = await repo.find(rootDocUrl); + return { client, location, repo, rootDocUrl, handle }; +}; + +/** + * @param makeDefaultSwissnumTable + * @returns {Promise<{ clientA: Client, clientB: Client, locationA: OcapnLocation, locationB: OcapnLocation, shutdownBoth: () => void, ocapnA: Ocapn, bootstrapB: any, repoA: Repo, rootDocUrlA: string, repoB: Repo, rootDocUrlB: string, handleA: DocHandle, handleB: DocHandle }>} + */ +const makeClientPair = async makeDefaultSwissnumTable => { + const { + client: clientA, + location: locationA, + repo: repoA, + rootDocUrl: rootDocUrlA, + handle: handleA, + } = await makePBCapClient('A', makeDefaultSwissnumTable); + const { + client: clientB, + location: locationB, + repo: repoB, + rootDocUrl: rootDocUrlB, + handle: handleB, + } = await makePBCapClient('B', makeDefaultSwissnumTable); + const shutdownBoth = () => { + clientA.shutdown(); + clientB.shutdown(); + }; + const { ocapn: ocapnA } = await clientA.provideSession(locationB); + const bootstrapB = await ocapnA.getBootstrap(); + return { + clientA, + clientB, + locationA, + locationB, + shutdownBoth, + ocapnA, + bootstrapB, + repoA, + rootDocUrlA, + repoB, + rootDocUrlB, + handleA, + handleB, + }; +}; + +const makeSwissnumTable = () => { + const swissnumTable = new Map(); + swissnumTable.set( + 'AutomergeDocTaker', + Far('take', async handleP => { + const handle = await handleP; + console.log('AutomergeDocTaker: modifying the received doc', handle.url); + handle.change(doc => { + doc.edited = true; + }); + }), + ); + return swissnumTable; +}; + +const { ocapnA, handleA } = await makeClientPair(makeSwissnumTable); + +console.log('handleA', handleA.doc()); + +handleA.change(doc => { + doc.edited = false; +}); +await new Promise(resolve => setTimeout(resolve, 200)); + +handleA.on('change', ({ doc }) => { + console.log('handleA changed', doc); +}); + +const bootstrap = ocapnA.getBootstrap(); +const docTaker = E(bootstrap).fetch(encodeSwissnum('AutomergeDocTaker')); + +// Client A sends the handle to Client B, which modifies the doc +await E(docTaker)(handleA); diff --git a/packages/pbcap/src/repo.js b/packages/pbcap/src/repo.js new file mode 100644 index 0000000000..99492d02a0 --- /dev/null +++ b/packages/pbcap/src/repo.js @@ -0,0 +1,20 @@ +import { Repo } from '@automerge/automerge-repo'; +import { WebSocketClientAdapter } from '@automerge/automerge-repo-network-websocket'; +import { NodeFSStorageAdapter } from '@automerge/automerge-repo-storage-nodefs'; +import { getOrCreateRoot } from './root-doc.js'; + +export const makeRepo = profileName => { + const dirPath = `/tmp/pbcap-${profileName}`; + const storageAdapter = new NodeFSStorageAdapter(dirPath); + + const repo = new Repo({ + network: [new WebSocketClientAdapter('wss://sync.automerge.org')], + storage: storageAdapter, + }); + + /** @type {string} */ + const rootDocUrl = getOrCreateRoot(repo, profileName); + // Cast to AnyDocumentId type that repo.find expects + // const handle = await repo.find(/** @type {any} */ (rootDocUrl)); + return { repo, rootDocUrl }; +}; diff --git a/packages/pbcap/src/root-doc.js b/packages/pbcap/src/root-doc.js new file mode 100644 index 0000000000..1d1544aeee --- /dev/null +++ b/packages/pbcap/src/root-doc.js @@ -0,0 +1,62 @@ +import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; +import { join } from 'path'; +import { homedir } from 'os'; + +const ROOT_DOC_URL_KEY = 'rootDocUrl'; + +// Ensure the storage directory exists +const ensureStorageDir = () => { + const dir = join(homedir(), '.pbcap'); + if (!existsSync(dir)) { + // Create directory synchronously if it doesn't exist + mkdirSync(dir, { recursive: true }); + } +}; + +/** + * Set the root document URL in persistent storage. + * @param profileName + * @param {string} url - The AutomergeUrl to set as the root document URL. + */ +export const setRootDocUrl = (profileName, url) => { + ensureStorageDir(); + const data = { [ROOT_DOC_URL_KEY]: url }; + const storageFile = join(homedir(), '.pbcap', `${profileName}-root-doc.json`); + writeFileSync(storageFile, JSON.stringify(data, null, 2)); +}; + +const getRootDocUrl = profileName => { + const storageFile = join(homedir(), '.pbcap', `${profileName}-root-doc.json`); + try { + if (!existsSync(storageFile)) { + return null; + } + const data = JSON.parse(readFileSync(storageFile, 'utf8')); + return data[ROOT_DOC_URL_KEY] || null; + } catch (error) { + // If there's any error reading the file, return null + return null; + } +}; + +/** + * Get or create the root document in the repo. + * @param {Repo} repo - The Automerge Repo instance. + * @param profileName + * @returns {string} The AutomergeUrl of the root document. + */ +export const getOrCreateRoot = (repo, profileName) => { + // Check if we already have a root document + const existingId = getRootDocUrl(profileName); + if (existingId) { + // @type {string} AutomergeUrl + return existingId; + } + + // Create the root document + /** @type {any} RootDocument */ + const root = repo.create({ edited: false }); + + setRootDocUrl(profileName, root.url); + return root.url; +}; diff --git a/packages/pbcap/test/index.test.js b/packages/pbcap/test/index.test.js new file mode 100644 index 0000000000..959e77fd81 --- /dev/null +++ b/packages/pbcap/test/index.test.js @@ -0,0 +1,57 @@ +import test from '@endo/ses-ava/prepare-endo.js'; +import { Repo } from '@automerge/automerge-repo'; +import { NodeFSStorageAdapter } from '@automerge/automerge-repo-storage-nodefs'; +import { join } from 'path'; +import { homedir } from 'os'; +import { rmSync, existsSync } from 'fs'; +import { setRootDocUrl, getOrCreateRoot } from '../src/root-doc.js'; + +test('root-doc storage and retrieval', async t => { + // Setup test storage directory + const testDir = join(homedir(), '.pbcap-test'); + const storageAdapter = new NodeFSStorageAdapter(testDir); + + const repo = new Repo({ + storage: storageAdapter, + }); + + // Mock contact object + const contact = { url: 'contact://test.com/self' }; + + // Test setting and getting root doc URL + const testUrl = 'automerge://test-doc-id'; + setRootDocUrl(testUrl); + + // Test getOrCreateRoot with existing URL + const existingId = getOrCreateRoot(repo, contact); + t.is(existingId, testUrl); + + // Clean up test storage + if (existsSync(testDir)) { + rmSync(testDir, { recursive: true, force: true }); + } +}); + +test('root-doc creation when none exists', async t => { + // Setup test storage directory + const testDir = join(homedir(), '.pbcap-test-2'); + const storageAdapter = new NodeFSStorageAdapter(testDir); + + const repo = new Repo({ + storage: storageAdapter, + }); + + // Mock contact object + const contact = { url: 'contact://test.com/self' }; + + // Test getOrCreateRoot when no URL exists (should create new) + const newId = getOrCreateRoot(repo, contact); + t.truthy(newId); + t.is(typeof newId, 'string'); + t.true(newId.startsWith('automerge:')); + + // Clean up test storage + if (existsSync(testDir)) { + rmSync(testDir, { recursive: true, force: true }); + } +}); diff --git a/packages/pbcap/tsconfig.build.json b/packages/pbcap/tsconfig.build.json new file mode 100644 index 0000000000..3e3877ed37 --- /dev/null +++ b/packages/pbcap/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": [ + "./tsconfig.json", + "../../tsconfig-build-options.json" + ], + "compilerOptions": { + "allowJs": true + }, + "exclude": [ + "test/" + ] +} diff --git a/packages/pbcap/tsconfig.json b/packages/pbcap/tsconfig.json new file mode 100644 index 0000000000..f77b8008a1 --- /dev/null +++ b/packages/pbcap/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.eslint-base.json", + "include": [ + "*.js", + "*.ts", + "src/**/*.js", + "src/**/*.ts" + ] +} diff --git a/yarn.lock b/yarn.lock index f7cb5d9c19..d45c9d8694 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,53 @@ __metadata: languageName: node linkType: hard +"@automerge/automerge-repo-network-websocket@npm:^2.2.0": + version: 2.2.0 + resolution: "@automerge/automerge-repo-network-websocket@npm:2.2.0" + dependencies: + "@automerge/automerge-repo": "npm:2.2.0" + cbor-x: "npm:^1.3.0" + debug: "npm:^4.3.4" + eventemitter3: "npm:^5.0.1" + isomorphic-ws: "npm:^5.0.0" + ws: "npm:^8.7.0" + checksum: 10c0/02fbafd77cc114a0d8822255fdb1bb28b9e650b16504b8c2d3f5a27325e4facf4abf624c556094a6cab8d000448d8fa1d4d3383e97037e1edcb962c5ab2e5ad5 + languageName: node + linkType: hard + +"@automerge/automerge-repo-storage-nodefs@npm:^2.2.0": + version: 2.2.0 + resolution: "@automerge/automerge-repo-storage-nodefs@npm:2.2.0" + dependencies: + "@automerge/automerge-repo": "npm:2.2.0" + rimraf: "npm:^5.0.1" + checksum: 10c0/0bbe36b93cedafe6db7571ed0498904bd9b797b432c69bb4b96180bbfa3f396ff5db2014aa08c7ad230ca9126515e03ba9b46a9bc4f5aea219df21250be36d4a + languageName: node + linkType: hard + +"@automerge/automerge-repo@npm:2.2.0, @automerge/automerge-repo@npm:^2.2.0": + version: 2.2.0 + resolution: "@automerge/automerge-repo@npm:2.2.0" + dependencies: + "@automerge/automerge": "npm:2.2.8 - 3" + bs58check: "npm:^3.0.1" + cbor-x: "npm:^1.3.0" + debug: "npm:^4.3.4" + eventemitter3: "npm:^5.0.1" + fast-sha256: "npm:^1.3.0" + uuid: "npm:^9.0.0" + xstate: "npm:^5.9.1" + checksum: 10c0/77e343d5d421831b081ab9fe9e0d0219a47e3121be043e4c1303d3e9548a9b6d880a08706af8d74c2781e607f0b194fd71f2e30546da26c923a34a9bb6948f8a + languageName: node + linkType: hard + +"@automerge/automerge@npm:2.2.8 - 3": + version: 3.1.1 + resolution: "@automerge/automerge@npm:3.1.1" + checksum: 10c0/1731cc7f29f16109705dfc8d7ea777702c454a3554e471a2d6fc5b45413ed59f6f0fada7c57ab8a9921144360f067f8bebecb4911f9193e1242c095cdbbf5335 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5": version: 7.23.5 resolution: "@babel/code-frame@npm:7.23.5" @@ -217,6 +264,48 @@ __metadata: languageName: node linkType: hard +"@cbor-extract/cbor-extract-darwin-arm64@npm:2.2.0": + version: 2.2.0 + resolution: "@cbor-extract/cbor-extract-darwin-arm64@npm:2.2.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-darwin-x64@npm:2.2.0": + version: 2.2.0 + resolution: "@cbor-extract/cbor-extract-darwin-x64@npm:2.2.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-linux-arm64@npm:2.2.0": + version: 2.2.0 + resolution: "@cbor-extract/cbor-extract-linux-arm64@npm:2.2.0" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-linux-arm@npm:2.2.0": + version: 2.2.0 + resolution: "@cbor-extract/cbor-extract-linux-arm@npm:2.2.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-linux-x64@npm:2.2.0": + version: 2.2.0 + resolution: "@cbor-extract/cbor-extract-linux-x64@npm:2.2.0" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@cbor-extract/cbor-extract-win32-x64@npm:2.2.0": + version: 2.2.0 + resolution: "@cbor-extract/cbor-extract-win32-x64@npm:2.2.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@emnapi/core@npm:^1.1.0": version: 1.2.0 resolution: "@emnapi/core@npm:1.2.0" @@ -775,7 +864,7 @@ __metadata: languageName: unknown linkType: soft -"@endo/ocapn@workspace:packages/ocapn": +"@endo/ocapn@workspace:^, @endo/ocapn@workspace:packages/ocapn": version: 0.0.0-use.local resolution: "@endo/ocapn@workspace:packages/ocapn" dependencies: @@ -863,6 +952,25 @@ __metadata: languageName: unknown linkType: soft +"@endo/pbcap@workspace:packages/pbcap": + version: 0.0.0-use.local + resolution: "@endo/pbcap@workspace:packages/pbcap" + dependencies: + "@automerge/automerge-repo": "npm:^2.2.0" + "@automerge/automerge-repo-network-websocket": "npm:^2.2.0" + "@automerge/automerge-repo-storage-nodefs": "npm:^2.2.0" + "@endo/init": "workspace:^" + "@endo/lockdown": "workspace:^" + "@endo/ocapn": "workspace:^" + "@endo/ses-ava": "workspace:^" + ava: "npm:^6.1.3" + c8: "npm:^7.14.0" + eslint: "npm:^8.57.0" + tsd: "npm:^0.31.2" + typescript: "npm:~5.6.3" + languageName: unknown + linkType: soft + "@endo/promise-kit@workspace:^, @endo/promise-kit@workspace:packages/promise-kit": version: 0.0.0-use.local resolution: "@endo/promise-kit@workspace:packages/promise-kit" @@ -1398,7 +1506,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.8.0": +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.8.0": version: 1.8.0 resolution: "@noble/hashes@npm:1.8.0" checksum: 10c0/06a0b52c81a6fa7f04d67762e08b2c476a00285858150caeaaff4037356dd5e119f45b2a530f638b77a5eeca013168ec1b655db41bae3236cb2e9d511484fc77 @@ -3351,6 +3459,13 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^4.0.0": + version: 4.0.1 + resolution: "base-x@npm:4.0.1" + checksum: 10c0/26a5a24105b27d94f21fa0640d5345620d758ab5d9269cf11828c502094d4f2fc5e84f3bfee63e9af29e83e0d3c97129264f1ac9653620b9bdab3f81d6aca881 + languageName: node + linkType: hard + "base64-arraybuffer@npm:0.1.4": version: 0.1.4 resolution: "base64-arraybuffer@npm:0.1.4" @@ -3493,6 +3608,25 @@ __metadata: languageName: node linkType: hard +"bs58@npm:^5.0.0": + version: 5.0.0 + resolution: "bs58@npm:5.0.0" + dependencies: + base-x: "npm:^4.0.0" + checksum: 10c0/0d1b05630b11db48039421b5975cb2636ae0a42c62f770eec257b2e5c7d94cb5f015f440785f3ec50870a6e9b1132b35bd0a17c7223655b22229f24b2a3491d1 + languageName: node + linkType: hard + +"bs58check@npm:^3.0.1": + version: 3.0.1 + resolution: "bs58check@npm:3.0.1" + dependencies: + "@noble/hashes": "npm:^1.2.0" + bs58: "npm:^5.0.0" + checksum: 10c0/a01f62351d17cea5f6607f75f6b4b79d3473d018c52f1dfa6f449751062bb079ebfd556ea81c453de657102ab8c5a6b78620161f21ae05f0e5a43543e0447700 + languageName: node + linkType: hard + "buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -3632,6 +3766,49 @@ __metadata: languageName: node linkType: hard +"cbor-extract@npm:^2.2.0": + version: 2.2.0 + resolution: "cbor-extract@npm:2.2.0" + dependencies: + "@cbor-extract/cbor-extract-darwin-arm64": "npm:2.2.0" + "@cbor-extract/cbor-extract-darwin-x64": "npm:2.2.0" + "@cbor-extract/cbor-extract-linux-arm": "npm:2.2.0" + "@cbor-extract/cbor-extract-linux-arm64": "npm:2.2.0" + "@cbor-extract/cbor-extract-linux-x64": "npm:2.2.0" + "@cbor-extract/cbor-extract-win32-x64": "npm:2.2.0" + node-gyp: "npm:latest" + node-gyp-build-optional-packages: "npm:5.1.1" + dependenciesMeta: + "@cbor-extract/cbor-extract-darwin-arm64": + optional: true + "@cbor-extract/cbor-extract-darwin-x64": + optional: true + "@cbor-extract/cbor-extract-linux-arm": + optional: true + "@cbor-extract/cbor-extract-linux-arm64": + optional: true + "@cbor-extract/cbor-extract-linux-x64": + optional: true + "@cbor-extract/cbor-extract-win32-x64": + optional: true + bin: + download-cbor-prebuilds: bin/download-prebuilds.js + checksum: 10c0/c36dec273f2114fcfe3b544d03d8bfddd2d537d114b9f94ba52a9366a8b852ea9725850e3d29ceda5df6894faeb37026e3bf2cb0d2bb4429f0a699fcfdfa1b8b + languageName: node + linkType: hard + +"cbor-x@npm:^1.3.0": + version: 1.6.0 + resolution: "cbor-x@npm:1.6.0" + dependencies: + cbor-extract: "npm:^2.2.0" + dependenciesMeta: + cbor-extract: + optional: true + checksum: 10c0/c6ab391e935a60c8a768080806f2c9aee01b2b124de68997e3e4cb700753757286860186094a92f510b595d7f8c77b3023d9125a05247afcbfea08cae45a0615 + languageName: node + linkType: hard + "cbor@npm:^9.0.1": version: 9.0.2 resolution: "cbor@npm:9.0.2" @@ -4542,6 +4719,13 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^2.0.1": + version: 2.0.4 + resolution: "detect-libc@npm:2.0.4" + checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c + languageName: node + linkType: hard + "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3" @@ -5263,6 +5447,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 10c0/4ba5c00c506e6c786b4d6262cfbce90ddc14c10d4667e5c83ae993c9de88aa856033994dd2b35b83e8dc1170e224e66a319fa80adc4c32adcd2379bbc75da814 + languageName: node + linkType: hard + "execa@npm:5.0.0": version: 5.0.0 resolution: "execa@npm:5.0.0" @@ -5436,6 +5627,13 @@ __metadata: languageName: node linkType: hard +"fast-sha256@npm:^1.3.0": + version: 1.3.0 + resolution: "fast-sha256@npm:1.3.0" + checksum: 10c0/87f9e4baa7639576cf60a2b6235c9f436e1a1c52323abbd8a705b5bea8355500acf176f2aed0c14f2ecd6d6007e26151461bab2f27b8953bcca8d9d6b76a86e4 + languageName: node + linkType: hard + "fastq@npm:^1.6.0": version: 1.15.0 resolution: "fastq@npm:1.15.0" @@ -5939,6 +6137,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.3.7": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -6975,6 +7189,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:^5.0.0": + version: 5.0.0 + resolution: "isomorphic-ws@npm:5.0.0" + peerDependencies: + ws: "*" + checksum: 10c0/a058ac8b5e6efe9e46252cb0bc67fd325005d7216451d1a51238bc62d7da8486f828ef017df54ddf742e0fffcbe4b1bcc2a66cc115b027ed0180334cd18df252 + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -7016,6 +7239,19 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + languageName: node + linkType: hard + "jake@npm:^10.8.5": version: 10.8.5 resolution: "jake@npm:10.8.5" @@ -7944,6 +8180,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + "minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": version: 2.1.2 resolution: "minizlib@npm:2.1.2" @@ -8114,6 +8357,19 @@ __metadata: languageName: node linkType: hard +"node-gyp-build-optional-packages@npm:5.1.1": + version: 5.1.1 + resolution: "node-gyp-build-optional-packages@npm:5.1.1" + dependencies: + detect-libc: "npm:^2.0.1" + bin: + node-gyp-build-optional-packages: bin.js + node-gyp-build-optional-packages-optional: optional.js + node-gyp-build-optional-packages-test: build-test.js + checksum: 10c0/f9fad2061c48fb0fc90831cd11d6a7670d731d22a5b00c7d3441b43b4003543299ff64ff2729afe2cefd7d14928e560d469336e5bb00f613932ec2cd56b3665b + languageName: node + linkType: hard + "node-gyp-build@npm:^4.2.2": version: 4.8.0 resolution: "node-gyp-build@npm:4.8.0" @@ -8834,6 +9090,13 @@ __metadata: languageName: node linkType: hard +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + "pacote@npm:^18.0.0, pacote@npm:^18.0.6": version: 18.0.6 resolution: "pacote@npm:18.0.6" @@ -9001,7 +9264,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.10.2, path-scurry@npm:^1.6.1": +"path-scurry@npm:^1.10.2, path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" dependencies: @@ -9614,6 +9877,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^5.0.1": + version: 5.0.10 + resolution: "rimraf@npm:5.0.10" + dependencies: + glob: "npm:^10.3.7" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10c0/7da4fd0e15118ee05b918359462cfa1e7fe4b1228c7765195a45b55576e8c15b95db513b8466ec89129666f4af45ad978a3057a02139afba1a63512a2d9644cc + languageName: node + linkType: hard + "rimraf@npm:~2.6.2": version: 2.6.3 resolution: "rimraf@npm:2.6.3" @@ -11267,6 +11541,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.0": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b + languageName: node + linkType: hard + "v8-to-istanbul@npm:^9.0.0": version: 9.0.1 resolution: "v8-to-istanbul@npm:9.0.1" @@ -11555,6 +11838,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.7.0": + version: 8.18.3 + resolution: "ws@npm:8.18.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + languageName: node + linkType: hard + "ws@npm:~7.4.2": version: 7.4.6 resolution: "ws@npm:7.4.6" @@ -11586,6 +11884,13 @@ __metadata: languageName: node linkType: hard +"xstate@npm:^5.9.1": + version: 5.20.2 + resolution: "xstate@npm:5.20.2" + checksum: 10c0/c1a875296fed9698a23e7a5adde777ae542a0c6d49248e19ccb7bb31f353945df0b874e270340c37a176850aad029a64b10ba396f72e69a585d282aae2febc25 + languageName: node + linkType: hard + "xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2"