diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts index b29d652..a29334a 100644 --- a/packages/core/src/schemas.ts +++ b/packages/core/src/schemas.ts @@ -7,6 +7,7 @@ export * as gatekeeper from './gen/gatekeeper.js'; export * as protocol from './gen/protocol.js'; export * as noise from './gen/noise.js'; export * as service from './gen/service.js'; +export * as stdenv from './gen/stdenv.js'; export * as stream from './gen/stream.js'; export * as sturdy from './gen/sturdy.js'; export * as tcp from './gen/tcp.js'; diff --git a/packages/ws-relay/src/index.ts b/packages/ws-relay/src/index.ts index 420ad83..3d838a0 100644 --- a/packages/ws-relay/src/index.ts +++ b/packages/ws-relay/src/index.ts @@ -2,6 +2,7 @@ /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones import { + AnyValue, Assertion, Bytes, Dataspace, @@ -9,6 +10,7 @@ import { IdentitySet, Observe, QuasiValue as Q, + Record, Ref, Relay, Schemas, @@ -16,14 +18,17 @@ import { Turn, assertionFacetObserver, canonicalEncode, + decode, fromJS, isEmbedded, + stringify, underlying, } from "@syndicate-lang/core"; import G = Schemas.gatekeeper; import S = Schemas.sturdy; import N = Schemas.noise; import T = Schemas.transportAddress; +import E = Schemas.stdenv; import * as SaltyCrypto from 'salty-crypto'; type TransportState = { @@ -404,3 +409,66 @@ export function boot(ds = Dataspace.global, debug: boolean = false, WebSocketCon } } } + +export function unpackStandardRoute(route: E.StandardRoute): G.Route { + if (route._variant === 'general') return route.value; + + const { transports, key, service, sig, oid } = route; + + const protocol = { _variant: "absent" } satisfies N.NoiseProtocol; + const preSharedKeys = { _variant: "absent" } satisfies N.NoisePreSharedKeys; + const caveats: S.CaveatsField = route.caveats.length + ? { _variant: "present", caveats: route.caveats } + : { _variant: "absent" }; + + return G.Route({ + transports: transports.map(t => { + switch (t._variant) { + case 'wsUrl': return T.fromWebSocket({ url: t.value }); + default: { + const x = stringify(E.fromStandardTransport(t)); + throw new Error(`Unsupported transport: ${x}`); + } + } + }), + pathSteps: [ + { stepType: N.$noise, + detail: N.fromNoiseSpec({ service, key, protocol, preSharedKeys }) }, + { stepType: S.$ref, + detail: S.fromParameters({ oid, sig, caveats }) }, + ], + }); +} + +export function decodeStandardRoute(s: string): G.Route | null { + try { + const route = E.toStandardRoute(decode( + Bytes.fromBase64(s.replace(/[^-_+/A-Za-z0-9=]/g, '')))); + return route === void 0 ? null : unpackStandardRoute(route); + } catch (e) { + console.error('Decoding standard route:', e); + return null; + } +} + +export function contactRemote( + route: G.Route | Record, Ref>, + connectedFacet: ( + remoteObject: Ref, + controlObject: Ref, + transportAddr: AnyValue, + ) => void, + ds = Dataspace.global, +) { + const routeValue = Record.isRecord(route) ? route : G.fromRoute(route); + at ds { + during G.ResolvePath({ + "route": routeValue, + "addr": $addr, + "control": $control_e: Embedded, + "resolved": G.Resolved.accepted($resolved_e: Embedded), + }) => { + connectedFacet(resolved_e.embeddedValue, control_e.embeddedValue, addr); + } + } +}