Simplify gatekeeper client-side protocols

This commit is contained in:
Tony Garnock-Jones 2024-06-07 11:53:22 +02:00
parent f45ab47175
commit 6fb100ee60
10 changed files with 222 additions and 193 deletions

View File

@ -1,10 +1,10 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { fromJS, Bytes, Dataspace, Ref, Sturdy, AnyValue, Reader, Schemas, stringify } from "@syndicate-lang/core";
import { fromJS, Bytes, Dataspace, Ref, AnyValue, Reader, Schemas, stringify } from "@syndicate-lang/core";
import { boot as bootHtml, Anchor, template as html, HtmlFragments, GlobalEvent, UIAttribute, UIChangeableProperty } from "@syndicate-lang/html";
import { boot as bootWakeDetector, WakeEvent } from "./wake-detector";
import { boot as bootWsRelay } from "@syndicate-lang/ws-relay";
import { boot as bootWsRelay, contactRemote } from "@syndicate-lang/ws-relay";
import { Present, Says } from './gen/simpleChatProtocol';
import G = Schemas.gatekeeper;
import N = Schemas.noise;
@ -38,47 +38,40 @@ function bootChat(ds: Ref) {
console.error(e);
}
assert UIAttribute('#route', 'class', 'invalid') when (!route);
if (route) contactRemote(route);
}
function contactRemote(route: G.Route<Ref>) {
console.log('contactRemote', route);
during G.ResolvePath({
"route": route,
"addr": $addr,
"control": $control: Ref,
"resolved": G.Resolved.accepted($remoteDs: Ref),
}) => {
on message WakeEvent() => at control {
send message G.ForceDisconnect();
}
outputState('connected', 'connected to ' + stringify(addr));
on stop outputState('disconnected', 'disconnected from ' + stringify(addr));
on message GlobalEvent('#send_chat', 'click', _) => {
const inp = document.getElementById("chat_input") as HTMLInputElement;
var utterance = inp.value;
inp.value = '';
if (utterance) {
at remoteDs {
send message Says({ who: nym.value, what: utterance });
if (route) {
console.log('contactRemote', route);
contactRemote(route, (remoteDs, control, addr) => {
on message WakeEvent() => at control {
send message G.ForceDisconnect();
}
outputState('connected', 'connected to ' + stringify(addr));
on stop outputState('disconnected', 'disconnected from ' + stringify(addr));
on message GlobalEvent('#send_chat', 'click', _) => {
const inp = document.getElementById("chat_input") as HTMLInputElement;
var utterance = inp.value;
inp.value = '';
if (utterance) {
at remoteDs {
send message Says({ who: nym.value, what: utterance });
}
}
}
}
at remoteDs {
assert Present(nym.value);
const ui = new Anchor();
during Present($who: string) => at ds {
assert ui.context(who).html('#nymlist', html`<li>${who}</li>`);
at remoteDs {
assert Present(nym.value);
const ui = new Anchor();
during Present($who: string) => at ds {
assert ui.context(who).html('#nymlist', html`<li>${who}</li>`);
}
on message Says({ "who": $who: string, "what": $what: string }) => {
outputUtterance(who, what);
}
}
on message Says({ "who": $who: string, "what": $what: string }) => {
outputUtterance(who, what);
}
}
}, ds);
}
}
}

View File

@ -2,30 +2,35 @@
# yarn lockfile v1
"@preserves/core@0.995.200", "@preserves/core@^0.995.200":
"@preserves/core@^0.995.200":
version "0.995.200"
resolved "https://registry.yarnpkg.com/@preserves/core/-/core-0.995.200.tgz#65575cf8f9320e73b5d37fa9ac9d6881a33fd3a6"
integrity sha512-htZ2x+hltUpKoPsviWSuelzZW96po9zVSsN0RZEih60FahNX0R2LCqFJ6v5lyATuQ9oHALVi0w8w4rtf8oiAJw==
"@preserves/schema-cli@0.995.201":
version "0.995.201"
resolved "https://registry.yarnpkg.com/@preserves/schema-cli/-/schema-cli-0.995.201.tgz#e87cb7ba51b225ff6ea96f177c5388907adf6717"
integrity sha512-n/lpVgWBY1UTRySPXIwWG4Yu87OrQ9b7Xo8zPPQzD/DJDTEDT3y3SUujY7UTF1pQn09xRGnfPnDu1RoRCv2J3w==
"@preserves/core@^0.995.206":
version "0.995.206"
resolved "https://registry.yarnpkg.com/@preserves/core/-/core-0.995.206.tgz#f13507aa39dbc8cbd5d10ac18d107c06e92bf519"
integrity sha512-Uc4SSSXHQG6xYER9cCc0bkbLYM8fGVbO4kXw3DI8BaQjeUFPOX3yYNYPAvRNzmaF4MuMOZY+jSJpjJ9o5g6A0w==
"@preserves/schema-cli@>=0.995.201":
version "0.995.206"
resolved "https://registry.yarnpkg.com/@preserves/schema-cli/-/schema-cli-0.995.206.tgz#709c96d938dce3bf5b132a3da1304ca3c84bbe2e"
integrity sha512-870rwjKtQHA8huwt8MpMImnKC2cBHKYzcJi4811k5RuYs/HdbT9B81NMSiSn+Hq19/v9fEBicwwEmao3oM7hJw==
dependencies:
"@preserves/core" "^0.995.200"
"@preserves/schema" "^0.995.201"
"@preserves/core" "^0.995.206"
"@preserves/schema" "^0.995.206"
chalk "^4.1"
chokidar "^3.5"
commander "^7.2"
glob "^7.1"
minimatch "^3.0"
"@preserves/schema@^0.995.201":
version "0.995.201"
resolved "https://registry.yarnpkg.com/@preserves/schema/-/schema-0.995.201.tgz#827ad539afb0fdf68ba885ee3ff43db0fd473aa5"
integrity sha512-0SuhwOEAfxtrAeOTZ8V1yNEcHHHEbfUmoFawrF+vzTQFNZRz/X3LF1FM6wOiJlHtb7tBr4B+rHXX8jfNrqrdkA==
"@preserves/schema@^0.995.206":
version "0.995.206"
resolved "https://registry.yarnpkg.com/@preserves/schema/-/schema-0.995.206.tgz#8e35a7db5363374f6794009d394510871f86746e"
integrity sha512-JmXGxSqsNSEzZNpvOYPupSZhffvMIvgnUmKE+Yt5oiPiD/8H52EbKRrqBUQuVpvI4hvwgThWAtNIg3+q42vI2w==
dependencies:
"@preserves/core" "^0.995.200"
"@preserves/core" "^0.995.206"
"@rollup/pluginutils@^3.0.9":
version "3.1.0"

View File

@ -1,4 +1,4 @@
´³bundle·µ³tcp„´³schema·³version°³ definitions·³TcpLocal´³rec´³lit³ tcp-local„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpRemote´³rec´³lit³
´³bundle·µ³rpc„´³schema·³version°³ definitions·³Answer´³rec´³lit³a„´³tupleµ´³named³request³any„´³named³response³any„„„„³Result´³orµµ±ok´³rec´³lit³ok„´³tupleµ´³named³value³any„„„„„µ±error´³rec´³lit³error„´³tupleµ´³named³error³any„„„„„„„³Question´³rec´³lit³q„´³tupleµ´³named³request³any„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³tcp„´³schema·³version°³ definitions·³TcpLocal´³rec´³lit³ tcp-local„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpRemote´³rec´³lit³
tcp-remote„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpPeerInfo´³rec´³lit³tcp-peer„´³tupleµ´³named³handle´³embedded³any„„´³named³local´³refµ„³TcpLocal„„´³named³remote´³refµ„³ TcpRemote„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³http„´³schema·³version°³ definitions·³Chunk´³orµµ±string´³atom³String„„µ±bytes´³atom³
ByteString„„„„³Headers´³dictof´³atom³Symbol„´³atom³String„„³MimeType´³atom³Symbol„³
QueryValue´³orµµ±string´³atom³String„„µ±file´³rec´³lit³file„´³tupleµ´³named³filename´³atom³String„„´³named³headers´³refµ„³Headers„„´³named³body´³atom³
@ -40,5 +40,5 @@ ByteString
RunService´³rec´³lit³ run-service„´³tupleµ´³named³ serviceName³any„„„„³ ServiceState´³rec´³lit³ service-state„´³tupleµ´³named³ serviceName³any„´³named³state´³refµ„³State„„„„„³ ServiceObject´³rec´³lit³service-object„´³tupleµ´³named³ serviceName³any„´³named³object³any„„„„³RequireService´³rec´³lit³require-service„´³tupleµ´³named³ serviceName³any„„„„³RestartService´³rec´³lit³restart-service„´³tupleµ´³named³ serviceName³any„„„„³ServiceDependency´³rec´³lit³
depends-on„´³tupleµ´³named³depender³any„´³named³dependee´³refµ„³ ServiceState„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³protocol„´³schema·³version°³ definitions·³Nop´³lit€„³Oid´³atom³ SignedInteger„³Sync´³rec´³lit³S„´³tupleµ´³named³peer´³embedded´³lit<69>„„„„„„³Turn´³seqof´³refµ„³ TurnEvent„„³Error´³rec´³lit³error„´³tupleµ´³named³message´³atom³String„„´³named³detail³any„„„„³Event´³orµµ±Assert´³refµ„³Assert„„µ±Retract´³refµ„³Retract„„µ±Message´³refµ„³Message„„µ±Sync´³refµ„³Sync„„„„³Assert´³rec´³lit³A„´³tupleµ´³named³ assertion´³refµ„³ Assertion„„´³named³handle´³refµ„³Handle„„„„„³Handle´³atom³ SignedInteger„³Packet´³orµµ±Turn´³refµ„³Turn„„µ±Error´³refµ„³Error„„µ± Extension´³refµ„³ Extension„„µ±Nop´³refµ„³Nop„„„„³Message´³rec´³lit³M„´³tupleµ´³named³body´³refµ„³ Assertion„„„„„³Retract´³rec´³lit³R„´³tupleµ´³named³handle´³refµ„³Handle„„„„„³ Assertion³any³ Extension´³rec´³named³label³any„´³named³fields´³seqof³any„„„³ TurnEvent´³tupleµ´³named³oid´³refµ„³Oid„„´³named³event´³refµ„³Event„„„„„³ embeddedType€„„µ³ dataspace„´³schema·³version°³ definitions·³Observe´³rec´³lit³Observe„´³tupleµ´³named³pattern´³refµ³dataspacePatterns„³Pattern„„´³named³observer´³embedded³any„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³
gatekeeper„´³schema·³version°³ definitions·³Bind´³rec´³lit³bind„´³tupleµ´³named³ description´³refµ„³ Description„„´³named³target´³embedded³any„„´³named³observer´³refµ„³ BindObserver„„„„„³Step´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³Bound´³orµµ±bound´³rec´³lit³bound„´³tupleµ´³named³pathStep´³refµ„³PathStep„„„„„„µ±Rejected´³refµ„³Rejected„„„„³Route´³rec´³lit³route„´³ tuplePrefixµ´³named³
transports´³seqof³any„„„´³named³ pathSteps´³seqof´³refµ„³PathStep„„„„„³Resolve´³rec´³lit³resolve„´³tupleµ´³named³step´³refµ„³Step„„´³named³observer´³embedded´³refµ„³Resolved„„„„„„³PathStep´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³Rejected´³rec´³lit³rejected„´³tupleµ´³named³detail³any„„„„³Resolved´³orµµ±accepted´³rec´³lit³accepted„´³tupleµ´³named³responderSession´³embedded³any„„„„„„µ±Rejected´³refµ„³Rejected„„„„³ Description´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³ ResolvePath´³rec´³lit³ resolve-path„´³tupleµ´³named³route´³refµ„³Route„„´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³resolved´³refµ„³Resolved„„„„„³ BindObserver´³orµµ±present´³embedded´³refµ„³Bound„„„µ±absent´³lit€„„„„³ForceDisconnect´³rec´³lit³force-disconnect„´³tupleµ„„„³ResolvedPathStep´³rec´³lit³ path-step„´³tupleµ´³named³origin´³embedded´³refµ„³Resolve„„„´³named³pathStep´³refµ„³PathStep„„´³named³resolved´³refµ„³Resolved„„„„„³TransportControl´³refµ„³ForceDisconnect„³TransportConnection´³rec´³lit³connect-transport„´³tupleµ´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³resolved´³refµ„³Resolved„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³transportAddress„´³schema·³version°³ definitions·³Tcp´³rec´³lit³tcp„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³Unix´³rec´³lit³unix„´³tupleµ´³named³path´³atom³String„„„„„³Stdio´³rec´³lit³stdio„´³tupleµ„„„³ WebSocket´³rec´³lit³ws„´³tupleµ´³named³url´³atom³String„„„„„„³ embeddedType€„„µ³dataspacePatterns„´³schema·³version°³ definitions·³AnyAtom´³orµµ±bool´³atom³Boolean„„µ±double´³atom³Double„„µ±int´³atom³ SignedInteger„„µ±string´³atom³String„„µ±bytes´³atom³
transports´³seqof³any„„„´³named³ pathSteps´³seqof´³refµ„³PathStep„„„„„³Resolve´³rec´³lit³resolve„´³tupleµ´³named³step´³refµ„³Step„„´³named³observer´³embedded´³refµ„³Resolved„„„„„„³PathStep´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³Rejected´³rec´³lit³rejected„´³tupleµ´³named³detail³any„„„„³Resolved´³orµµ±accepted´³rec´³lit³accepted„´³tupleµ´³named³responderSession´³embedded³any„„„„„„µ±Rejected´³refµ„³Rejected„„„„³ Description´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³ ResolvePath´³rec´³lit³ resolve-path„´³tupleµ´³named³route´³refµ„³Route„„„„„³ BindObserver´³orµµ±present´³embedded´³refµ„³Bound„„„µ±absent´³lit€„„„„³ ResolvedPath´³rec´³lit³ resolved-path„´³tupleµ´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³responderSession´³embedded³any„„„„„³ForceDisconnect´³rec´³lit³force-disconnect„´³tupleµ„„„³ResolvePathStep´³rec´³lit³resolve-path-step„´³tupleµ´³named³origin´³embedded´³refµ„³Resolve„„„´³named³pathStep´³refµ„³PathStep„„„„„³ConnectTransport´³rec´³lit³connect-transport„´³tupleµ´³named³addr³any„„„„³ResolvedPathStep´³embedded³any„³TransportControl´³refµ„³ForceDisconnect„³ConnectedTransport´³rec´³lit³connected-transport„´³tupleµ´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³responderSession´³embedded³any„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³transportAddress„´³schema·³version°³ definitions·³Tcp´³rec´³lit³tcp„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³Unix´³rec´³lit³unix„´³tupleµ´³named³path´³atom³String„„„„„³Stdio´³rec´³lit³stdio„´³tupleµ„„„³ WebSocket´³rec´³lit³ws„´³tupleµ´³named³url´³atom³String„„„„„„³ embeddedType€„„µ³dataspacePatterns„´³schema·³version°³ definitions·³AnyAtom´³orµµ±bool´³atom³Boolean„„µ±double´³atom³Double„„µ±int´³atom³ SignedInteger„„µ±string´³atom³String„„µ±bytes´³atom³
ByteString„„µ±symbol´³atom³Symbol„„µ±embedded´³embedded³any„„„„³Pattern´³orµµ±discard´³rec´³lit³_„´³tupleµ„„„„µ±bind´³rec´³lit³bind„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„„µ±lit´³rec´³lit³lit„´³tupleµ´³named³value´³refµ„³AnyAtom„„„„„„µ±group´³rec´³lit³group„´³tupleµ´³named³type´³refµ„³ GroupType„„´³named³entries´³dictof³any´³refµ„³Pattern„„„„„„„„„³ GroupType´³orµµ±rec´³rec´³lit³rec„´³tupleµ´³named³label³any„„„„„µ±arr´³rec´³lit³arr„´³tupleµ„„„„µ±dict´³rec´³lit³dict„´³tupleµ„„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„

View File

@ -52,14 +52,23 @@ Bound = <bound @pathStep PathStep> / Rejected .
# ---------------------------------------------------------------------------
# Protocol at client-side dataspaces, for resolution utilities
# Assertion. In response to observation of this with appropriate captures/wildcards in `addr`
# and `resolved`, respondent will follow `route.pathSteps` starting from one of the
# `route.transports`, asserting `ResolvePath` with the final `Resolved` as well as the selected
# transport `addr` and a `control` for it.
ResolvePath = <resolve-path @route Route @addr any @control #:TransportControl @resolved Resolved> .
# The client-side operates using `rpc.Question`s and `rpc.Answer`s.
TransportConnection = <connect-transport @addr any @control #:TransportControl @resolved Resolved> .
ResolvedPathStep = <path-step @origin #:Resolve @pathStep PathStep @resolved Resolved> .
# Assert `rpc.Question` with `ResolvePath` to request resolution of `Route`. The resolution
# utility will continuously try to satisfy the request, following `route.pathSteps` starting
# from one of the `route.transports`, ultimately asserting `rpc.Answer` with an `rpc.Result` in
# response. If the process completes successfully, the `rpc.Result.ok` will carry a
# `ResolvedPath`.
ResolvePath = <resolve-path @route Route> .
ResolvedPath = <resolved-path @addr any @control #:TransportControl @responderSession #:any> .
# Assertions. As `ResolvePath`/`ResolvedPath`, but just for an initial transport link setup.
ConnectTransport = <connect-transport @addr any> .
ConnectedTransport = <connected-transport @addr any @control #:TransportControl @responderSession #:any> .
# Assertions. Like `ResolvePath`/`ResolvedPath`, but for incremental resolution along a route.
ResolvePathStep = <resolve-path-step @origin #:Resolve @pathStep PathStep> .
ResolvedPathStep = #:any .
PathStep = <<rec> @stepType symbol [@detail any]> .
# A `Route` describes a network path that can be followed to reach some target entity.

View File

@ -0,0 +1,11 @@
version 1 .
embeddedType EntityRef.Cap .
# Assertion. Establishes a frame for a request.
Question = <q @request any> .
# Assertion or message. Responds to a question.
Answer = <a @request any @response any> .
# Value. Captures a common "ok-or-error" pattern as seen in e.g. Rust.
Result = <ok @value any> / <error @error any> .

View File

@ -16,6 +16,8 @@ export * as Pattern from './runtime/pattern.js';
export * as QuasiValue from './runtime/quasivalue.js';
export * from './runtime/randomid.js';
export * as Rewrite from './runtime/rewrite.js';
export * from './runtime/rpc.js';
export * as Rpc from './runtime/rpc.js';
export * as Skeleton from './runtime/skeleton.js';
export * from './runtime/space.js';
export * from './runtime/supervise.js';

View File

@ -0,0 +1,51 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Assertable, Ref } from './actor.js';
import { ctor, dict, QuasiValue } from './quasivalue.js';
import * as R from '../gen/rpc.js';
import { fromJS } from '@preserves/core';
export { Result } from '../gen/rpc.js';
export function Question<Q extends Assertable>(q: Q): R.Question<Ref> {
return R.Question(fromJS<Ref>(q));
}
export namespace Question {
export function quasiValue(q: QuasiValue): QuasiValue {
return ctor(R.Question, q);
}
}
export function Answer<Q extends Assertable, A extends Assertable>(q: Q, a: A): R.Answer<Ref> {
return R.Answer({ request: fromJS<Ref>(q), response: fromJS<Ref>(a) });
}
export namespace Answer {
export function quasiValue(q: QuasiValue, a: QuasiValue): QuasiValue {
return ctor(R.Answer, dict(
["request", q],
["response", a]));
}
}
export function resultOk<V extends Assertable>(v: V): R.Result<Ref> {
return R.Result.ok(fromJS<Ref>(v));
}
export namespace resultOk {
export function quasiValue(v: QuasiValue): QuasiValue {
return ctor(R.Result.ok, v);
}
}
export function resultError<E extends Assertable>(e: E): R.Result<Ref> {
return R.Result.error(fromJS<Ref>(e));
}
export namespace resultError {
export function quasiValue(e: QuasiValue): QuasiValue {
return ctor(R.Result.error, e);
}
}

View File

@ -6,6 +6,7 @@ export * as dataspace from './gen/dataspace.js';
export * as gatekeeper from './gen/gatekeeper.js';
export * as protocol from './gen/protocol.js';
export * as noise from './gen/noise.js';
export * as rpc from './gen/rpc.js';
export * as service from './gen/service.js';
export * as stdenv from './gen/stdenv.js';
export * as stream from './gen/stream.js';

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2023-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Dataspace, Ref, Sturdy, Reader, Schemas, Embedded, randomId, fromJS } from "@syndicate-lang/core";
import { Dataspace, Ref, Sturdy, Reader, Schemas, Embedded, randomId, fromJS, Question, resultOk, Answer } from "@syndicate-lang/core";
import { boot as bootHtml, Widget } from "@syndicate-lang/html2";
import wsRelay from "@syndicate-lang/ws-relay";
import { ExampleDefinition } from './gen/example';
@ -34,11 +34,10 @@ function bootApp(ds: Ref) {
'<ref {oid: "syndicate" sig: #[acowDB2/oI+6aSEC3YIxGg==]}>').next())))],
});
during G.ResolvePath({
"route": route,
"resolved": G.Resolved.accepted($remoteDs_e: Embedded),
}) => {
const remoteDs = remoteDs_e.embeddedValue;
assert Question(G.ResolvePath(route));
during Answer(G.ResolvePath(route), resultOk(G.ResolvedPath({
responderSession: $remoteDs: Ref,
}))) => {
at remoteDs {
assert ExampleDefinition(this_instance);
during ExampleDefinition($who: string) => {

View File

@ -7,7 +7,6 @@ import {
Bytes,
Dataspace,
IdentitySet,
Observe,
QuasiValue as Q,
Record,
Ref,
@ -19,23 +18,28 @@ import {
canonicalEncode,
decode,
fromJS,
isEmbedded,
parse,
stringify,
underlying,
Embeddable,
SaltyCrypto,
Question,
Answer,
Result,
resultOk,
resultError,
} 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 R = Schemas.rpc;
export * from './seal.js';
type TransportState = {
addr: T.WebSocket,
addr: AnyValue,
control: Ref,
peer: Ref,
};
@ -43,23 +47,17 @@ type TransportState = {
export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketConstructor?: typeof WebSocket) {
spawn named 'transportConnector' {
at ds {
during Observe({ "pattern": :pattern G.TransportConnection({
"addr": \$addrPatValue,
"control": \_,
"resolved": \_,
}) }) => {
const addr = Q.drop_lit(addrPatValue, T.toWebSocket);
if (!addr) return;
during Question(G.ConnectTransport(T.WebSocket($url: string))) => {
let counter =0;
Supervisor.always(() => ['transportConnector', fromJS(addr), counter++], () => {
console.log('connecting', addr.url, counter);
connectTo(addr);
Supervisor.always(() => ['wsTransport', fromJS(url), counter++], () => {
console.log('connecting', url, counter);
connectWs(url);
});
}
}
}
function connectTo(addr: T.WebSocket) {
function connectWs(url: string) {
const facet = Turn.activeFacet;
facet.preventInertCheck();
const controlEntity = {
@ -73,9 +71,9 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
function succeed(ws: WebSocket) {
if (final) return;
final = true;
console.log('opened', addr.url);
console.log('opened', url);
on stop {
console.log('closing', addr.url);
console.log('closing', url);
ws.close();
}
ws.onclose = () => facet.turn(() => { stop facet; });
@ -87,60 +85,50 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
setup: r => ws.onmessage = e => r.accept(new Uint8Array(e.data)),
initialOid: 0,
});
console.log('succeed', addr.url);
console.log('succeed', url);
at ds {
assert G.TransportConnection<Ref>({
"addr": fromJS(addr),
assert Answer(G.ConnectTransport(fromJS<Ref>(T.WebSocket(url))), resultOk(G.ConnectedTransport({
"addr": fromJS<Ref>(T.WebSocket(url)),
"control": create controlEntity,
"resolved": G.Resolved.accepted(relay.peer!),
});
"responderSession": relay.peer!,
})));
}
}
function fail(detail: Assertion) {
if (final) return;
final = true;
console.log('fail', addr.url, detail);
console.log('fail', url, detail);
at ds {
assert G.TransportConnection<Ref>({
"addr": fromJS(addr),
"control": create controlEntity,
"resolved": G.Resolved.Rejected(G.Rejected(detail)),
});
assert Answer(G.ConnectTransport(fromJS<Ref>(T.WebSocket(url))), resultError(detail));
}
setTimeout(() => facet.turn(() => { stop facet; }), 10000);
}
try {
const ws = new (WebSocketConstructor ?? WebSocket)(addr.url);
const ws = new (WebSocketConstructor ?? WebSocket)(url);
ws.binaryType = 'arraybuffer';
ws.onopen = () => facet.turn(() => succeed(ws));
ws.onclose = () => facet.turn(() => fail(Symbol.for('closed')));
ws.onerror = () => facet.turn(() => fail(Symbol.for('websocket-error-event')));
} catch (e) {
console.error('Failed opening websocket', addr.url, e);
console.error('Failed opening websocket', url, e);
fail(Symbol.for('websocket-exception'));
}
}
spawn named 'pathResolver' {
at ds {
during Observe({ "pattern": :pattern G.ResolvePath({
"route": \$routePatValue,
"addr": \_,
"control": \_,
"resolved": \_,
}) }) => {
const route = Q.drop_lit(routePatValue, G.toRoute);
during Question(G.ResolvePath($route0)) => {
const route = G.toRoute(route0);
if (!route) return;
field candidates: IdentitySet<TransportState> = new IdentitySet();
route.transports.forEach(t => {
const addr = T.toWebSocket(t);
if (!addr) return;
console.log('tracking', addr.url);
during G.TransportConnection({
route.transports.forEach(addr => {
console.log('tracking', addr);
assert Question(G.ConnectTransport(addr));
during Answer(G.ConnectTransport(addr), resultOk(G.ConnectedTransport({
"addr": addr,
"control": $control: Ref,
"resolved": G.Resolved.accepted($peer: Ref),
}) => {
"responderSession": $peer: Ref,
}))) => {
const entry = { addr, control, peer };
candidates.value.add(entry);
candidates.changed();
@ -161,39 +149,34 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
}
rootPeer.value = best.value?.peer ?? null;
}
resolve(() => rootPeer.value, route.pathSteps, (r) => {
console.log('leaf', best.value?.addr?.url);
assert G.ResolvePath<Ref>({
"route": route,
"addr": fromJS(best.value!.addr),
resolve(() => ({ ref: rootPeer.value, error: false }), route.pathSteps, (r) => {
console.log('leaf', best.value?.addr);
assert Answer(G.ResolvePath(route), resultOk(G.ResolvedPath({
"addr": best.value!.addr,
"control": best.value!.control,
"resolved": r()!
}) when (r());
"responderSession": r().ref!,
}))) when (r().ref);
assert Answer(G.ResolvePath(route), resultError(r().error)) when (!r().ref);
});
}
}
}
function resolve(
e: () => Ref | null,
e: () => { ref: Ref | null, error: AnyValue }, // gross
steps: G.PathStep[],
k: (r: () => G.Resolved | null) => void,
k: (r: () => { ref: Ref | null, error: AnyValue }) => void,
) {
if (steps.length === 0) {
k(() => {
const peer = e();
return peer === null ? null : G.Resolved.accepted(peer);
});
k(e);
} else {
const [step, ...more] = steps;
at ds {
during G.ResolvedPathStep({
"origin": (e()!),
"pathStep": step,
"resolved": $resolved: G.Resolved,
}) when (e()) => {
switch (resolved._variant) {
case "accepted":
const q = () => G.ResolvePathStep({ origin: (e().ref!), pathStep: step });
assert Question(q()) when (e().ref);
during Answer(((q)()) /* omg */, $a: Result) when (e().ref) => {
switch (a._variant) {
case "ok":
// Include a call to our e() in the e we pass in to the recursive
// call to resolve(). e() returning non-null is a precondition for
// the call; if that precondition ever changes, we want to NOT
@ -208,10 +191,18 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
// best.value!, which is now null, boom. With the call to e(), we
// short circuit and so the assertion becomes null at that point.
//
resolve(() => e() && resolved.responderSession, more, k);
const nextRef = G.toResolvedPathStep(a.value);
const nextResult = (nextRef === void 0)
? { ref: null, error: 'bad ResolvedPathStep' }
: { ref: nextRef, error: false };
const nextE = () => {
const result = e();
return result.ref ? nextResult : result;
};
resolve(nextE, more, k);
break;
case "Rejected":
k(() => resolved);
case "error":
k(() => ({ ref: null, error: a.error }));
break;
}
}
@ -221,22 +212,13 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
spawn named 'noiseStep' {
at ds {
during Observe({ "pattern": :pattern G.ResolvedPathStep({
"origin": \$originPatValue,
"pathStep": G.PathStep({
"stepType": N.$noise,
"detail": \$detailPatValue,
during Question($q(G.ResolvePathStep({
origin: $origin: Ref,
pathStep: G.PathStep({
stepType: N.$noise,
detail: $spec: N.NoisePathStepDetail,
}),
"resolved": \_,
}) }) => {
const origin0 = Q.drop_lit(originPatValue);
if (!origin0 || !isEmbedded(origin0)) return;
const origin = origin0;
const spec0 = Q.drop_lit(detailPatValue, N.toNoisePathStepDetail);
if (!spec0) return;
const spec = spec0;
}))) => {
const algorithms = SaltyCrypto.Noise_25519_ChaChaPoly_BLAKE2s;
const protocol =
spec.protocol._variant === "present" ? spec.protocol.protocol :
@ -270,14 +252,7 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
case "Rejected":
stop {
at ds {
assert G.ResolvedPathStep<Ref>({
"origin": origin,
"pathStep": G.PathStep({
"stepType": N.$noise,
"detail": fromJS(N.NoisePathStepDetail(spec)),
}),
"resolved": response,
});
assert Answer(q, resultError(response.value.detail));
}
}
}
@ -332,14 +307,7 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
setup: r => relay = r,
initialOid: 0,
}).peer!;
assert G.ResolvedPathStep<Ref>({
"origin": origin,
"pathStep": G.PathStep({
"stepType": N.$noise,
"detail": fromJS(spec),
}),
"resolved": G.Resolved.accepted(peer),
});
assert Answer(q, resultOk(peer));
}
function handlePacket(body: Assertion) {
@ -368,21 +336,13 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
spawn named 'sturdyRefStep' {
at ds {
during Observe({ "pattern": :pattern G.ResolvedPathStep({
"origin": \$originPatValue,
"pathStep": G.PathStep({
"stepType": S.$ref,
"detail": \$detailPatValue,
during Question($q(G.ResolvePathStep({
origin: $origin: Ref,
pathStep: G.PathStep({
stepType: S.$ref,
detail: $parameters: S.SturdyPathStepDetail,
}),
"resolved": \_,
}) }) => {
const origin0 = Q.drop_lit(originPatValue);
if (!origin0 || !isEmbedded(origin0)) return;
const origin = origin0;
const parameters = Q.drop_lit(detailPatValue, S.toSturdyPathStepDetail);
if (!parameters) return;
}))) => {
at origin {
assert G.Resolve({
"step": G.Step({
@ -392,15 +352,13 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
"observer": create assertionFacetObserver(e => {
const response = G.toResolved(e);
if (!response) return;
at ds {
assert G.ResolvedPathStep<Ref>({
"origin": origin,
"pathStep": G.PathStep({
"stepType": S.$ref,
"detail": fromJS(parameters),
}),
"resolved": response,
});
switch (response._variant) {
case "accepted":
assert Answer(q, resultOk(response.responderSession));
break;
case "Rejected":
assert Answer(q, resultError(response.value.detail));
break;
}
}),
});
@ -465,7 +423,7 @@ export function decodeStandardRoute(s: string): G.Route | null {
}
export function contactRemote(
route: G.Route<Ref> | Record<AnyValue, Array<AnyValue>, Ref>,
route: G.Route | Record<AnyValue, Array<AnyValue>, Ref> ,
connectedFacet: (
remoteObject: Ref,
controlObject: Ref,
@ -473,14 +431,14 @@ export function contactRemote(
) => void,
ds = Dataspace.local,
) {
const routeValue = 'pathSteps' in route ? G.fromRoute(G.Route(route)) : route;
const routeValue = 'pathSteps' in route ? route : G.asRoute(route);
at ds {
during G.ResolvePath({
"route": routeValue,
assert Question(G.ResolvePath(routeValue));
during Answer(G.ResolvePath(routeValue), resultOk(G.ResolvedPath({
"addr": $addr,
"control": $control: Ref,
"resolved": G.Resolved.accepted($resolved: Ref),
}) => {
"responderSession": $resolved: Ref,
}))) => {
connectedFacet(resolved, control, addr);
}
}