Improve wsRelay
This commit is contained in:
parent
09ae5ddb5b
commit
3fe2af848b
22
TODO.md
22
TODO.md
|
@ -1,3 +1,25 @@
|
|||
|
||||
// // v Ideally, this would be of type "Sturdy.SturdyRef | null", but (1) the
|
||||
// // current Dataflow implementation isn't bright enough to mark valuesas being
|
||||
// // convertible to preserves values on demand, and (2) null isn't preservesable
|
||||
// // in any case. If preserves were improved to be able to convert schema-parsed
|
||||
// // values to preserves on demand, and to know it could do that at the type
|
||||
// // level, then I could change Dataflow to support any value that could be
|
||||
// // converted to preserves on demand, and I could special-case null and
|
||||
// // undefined for the ergonomics.
|
||||
// field servercap: AnyValue = false;
|
||||
// on asserted InputValue('#servercap', $v: string) => {
|
||||
// servercap.value = false;
|
||||
// try {
|
||||
// const a = new Reader<Ref>(v).next();
|
||||
// if (Sturdy.toSturdyRef(a) !== void 0) servercap.value = a;
|
||||
// } catch (e) {
|
||||
// console.error(e);
|
||||
// }
|
||||
// }
|
||||
// assert UIAttribute('#servercap', 'class', 'invalid') when (!servercap.value);
|
||||
|
||||
|
||||
- [DONE] `during/spawn`
|
||||
- [DONE] `during`
|
||||
- [DONE] `let { TimeLaterThan } = activate require("@syndicate-lang/driver-timer");`
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prepare": "yarn regenerate && yarn compile && yarn rollup",
|
||||
"regenerate": "rm -rf ./src/gen && preserves-schema-ts --module EntityRef=@syndicate-lang/core --module transportAddress=@syndicate-lang/core:Schemas.transportAddress --output ./src/gen './protocols/schemas/**/*.prs'",
|
||||
"regenerate": "rm -rf ./src/gen && preserves-schema-ts --module EntityRef=@syndicate-lang/core --module transportAddress=@syndicate-lang/core:Schemas.transportAddress --module sturdy=@syndicate-lang/core:Schemas.sturdy --xref 'node_modules/@syndicate-lang/core/protocols/schemas/**/*.prs' --output ./src/gen './protocols/schemas/**/*.prs'",
|
||||
"regenerate:watch": "yarn regenerate --watch",
|
||||
"compile": "syndicate-tsc",
|
||||
"compile:watch": "syndicate-tsc -w --verbose --intermediate-directory src.ts",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
version 1 .
|
||||
|
||||
Resolved = <resolved @addr RelayAddress @sturdyref sturdy.SturdyRef @resolved #!any> .
|
||||
|
||||
ViaRelay = <via-relay @addr RelayAddress @assertion any> .
|
||||
ForceRelayDisconnect = <force-relay-disconnect @addr RelayAddress> .
|
||||
|
||||
|
|
|
@ -1,14 +1,96 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { QuasiValue as Q, Dataspace, Ref, Sturdy, Observe, AnyValue, Reader, Embedded, Schemas } from "@syndicate-lang/core";
|
||||
import { boot as bootHtml, Anchor, template as html, HtmlFragments, GlobalEvent, UIAttribute } from "@syndicate-lang/html";
|
||||
import { Dataspace, Ref, Sturdy, AnyValue, Reader, Schemas, isEmbedded } 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, ForceRelayDisconnect, ViaRelay, RelayAddress, fromForceRelayDisconnect, fromViaRelay } from "./wsRelay";
|
||||
import { boot as bootWsRelay, ForceRelayDisconnect, fromRelayAddress, RelayAddress, fromForceRelayDisconnect, Resolved } from "./wsRelay";
|
||||
import { fromSays, fromPresent, Present, Says } from './gen/simpleChatProtocol';
|
||||
|
||||
const Transport = Schemas.transportAddress;
|
||||
|
||||
export function main() {
|
||||
document.getElementById('chat_form')!.onsubmit = e => { e.preventDefault(); return false; };
|
||||
document.getElementById('nym_form')!.onsubmit = e => { e.preventDefault(); return false; };
|
||||
setWsurl();
|
||||
setUsernameIfUnset();
|
||||
document.getElementById('chat_input')!.focus();
|
||||
|
||||
Dataspace.boot(ds => {
|
||||
bootHtml(ds);
|
||||
bootWakeDetector(ds);
|
||||
bootWsRelay(ds, true);
|
||||
bootChat(ds);
|
||||
});
|
||||
}
|
||||
|
||||
function bootChat(ds: Ref) {
|
||||
spawn named 'chat' {
|
||||
at ds {
|
||||
field nym: string = '';
|
||||
on asserted UIChangeableProperty('#nym', 'value', $v: string) => nym.value = v;
|
||||
|
||||
during UIChangeableProperty('#wsurl', 'value', $wsurl: string) =>
|
||||
during UIChangeableProperty('#servercap', 'value', $servercapText: string) => {
|
||||
let servercap: AnyValue | null = null;
|
||||
try {
|
||||
const a = new Reader<Ref>(servercapText).next();
|
||||
Sturdy.asSturdyRef(a); // throws if invalid
|
||||
servercap = a;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
assert UIAttribute('#servercap', 'class', 'invalid') when (!servercap);
|
||||
if (wsurl && servercap) {
|
||||
contactRemote(wsurl, servercap);
|
||||
}
|
||||
}
|
||||
|
||||
function contactRemote(wsurl: string, servercap: AnyValue) {
|
||||
during Resolved({
|
||||
"addr": RelayAddress(Transport.WebSocket(wsurl)),
|
||||
"sturdyref": servercap,
|
||||
"resolved": $remoteDs_e,
|
||||
}) => {
|
||||
if (!isEmbedded(remoteDs_e)) return;
|
||||
const remoteDs = remoteDs_e.embeddedValue;
|
||||
|
||||
on message WakeEvent() =>
|
||||
send message fromForceRelayDisconnect(ForceRelayDisconnect(
|
||||
RelayAddress(Transport.WebSocket(wsurl))));
|
||||
|
||||
outputState('connected', 'connected to ' + wsurl);
|
||||
on stop outputState('disconnected', 'disconnected from ' + wsurl);
|
||||
|
||||
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 fromSays(Says({ who: nym.value, what: utterance }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
at remoteDs {
|
||||
assert fromPresent(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setWsurl() {
|
||||
const wsurl = document.getElementById('wsurl')! as HTMLInputElement;
|
||||
if (wsurl.value === '') {
|
||||
|
@ -23,119 +105,6 @@ function setUsernameIfUnset() {
|
|||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
document.getElementById('chat_form')!.onsubmit = e => { e.preventDefault(); return false; };
|
||||
document.getElementById('nym_form')!.onsubmit = e => { e.preventDefault(); return false; };
|
||||
setWsurl();
|
||||
setUsernameIfUnset();
|
||||
|
||||
Dataspace.boot(ds => {
|
||||
bootHtml(ds);
|
||||
bootWakeDetector(ds);
|
||||
bootWsRelay(ds);
|
||||
spawnInputChangeMonitor(ds);
|
||||
|
||||
spawn named 'chat' {
|
||||
at ds {
|
||||
const ui = new Anchor();
|
||||
|
||||
field nym: string = '';
|
||||
on asserted InputValue('#nym', $v: string) => nym.value = v;
|
||||
|
||||
// v Ideally, this would be of type "Sturdy.SturdyRef | null", but (1) the
|
||||
// current Dataflow implementation isn't bright enough to mark valuesas being
|
||||
// convertible to preserves values on demand, and (2) null isn't preservesable
|
||||
// in any case. If preserves were improved to be able to convert schema-parsed
|
||||
// values to preserves on demand, and to know it could do that at the type
|
||||
// level, then I could change Dataflow to support any value that could be
|
||||
// converted to preserves on demand, and I could special-case null and
|
||||
// undefined for the ergonomics.
|
||||
field servercap: AnyValue = false;
|
||||
on asserted InputValue('#servercap', $v: string) => {
|
||||
servercap.value = false;
|
||||
try {
|
||||
const a = new Reader<Ref>(v).next();
|
||||
if (Sturdy.toSturdyRef(a) !== void 0) servercap.value = a;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
assert UIAttribute('#servercap', 'class', 'invalid') when (!servercap.value);
|
||||
|
||||
during InputValue('#wsurl', $wsurl: string) => {
|
||||
const addr = RelayAddress(Transport.WebSocket(wsurl));
|
||||
assert fromViaRelay(ViaRelay({
|
||||
"addr": addr,
|
||||
"assertion": Schemas.gatekeeper.fromResolve(Schemas.gatekeeper.Resolve({
|
||||
"sturdyref": Sturdy.asSturdyRef(servercap.value),
|
||||
"observer": create ({
|
||||
assert(remoteDs_e: Embedded<Ref>) {
|
||||
const remoteDs = remoteDs_e.embeddedValue;
|
||||
console.log('Saw remoteDs', remoteDs);
|
||||
|
||||
on message WakeEvent() =>
|
||||
send message fromForceRelayDisconnect(ForceRelayDisconnect(addr));
|
||||
|
||||
outputState('connected', 'connected to ' + wsurl);
|
||||
on stop outputState('disconnected', 'disconnected from ' + wsurl);
|
||||
|
||||
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 fromSays(Says({ who: nym.value, what: utterance }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
at remoteDs {
|
||||
assert fromPresent(Present(nym.value));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
})),
|
||||
})) when (wsurl && servercap.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Input control value monitoring
|
||||
|
||||
assertion type InputValue(selector, value);
|
||||
|
||||
function spawnInputChangeMonitor(ds: Ref) {
|
||||
spawn {
|
||||
at ds {
|
||||
during Observe({
|
||||
"pattern": :pattern InputValue(\Q.lit($selector: string), \_)
|
||||
}) => spawn named `input(${selector})` {
|
||||
const element = document.querySelector(selector) as HTMLInputElement;
|
||||
if (element !== null) {
|
||||
field value: string = element.value;
|
||||
assert InputValue(selector, value.value);
|
||||
on message GlobalEvent(selector, 'change', $_e) => value.value = element.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Adding items to the transcript panel
|
||||
|
||||
function outputItem(cls: string, item0: HtmlFragments): ChildNode {
|
||||
const stamp = html`<span class="timestamp">${(new Date()).toUTCString()}</span>`;
|
||||
const item = html`<div class="${cls}">${stamp}${item0}</div>`;
|
||||
|
|
|
@ -1,22 +1,48 @@
|
|||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { Ref, Relay, Turn, Supervisor, SupervisorRestartPolicy } from "@syndicate-lang/core";
|
||||
import { QuasiValue as Q, Ref, Relay, Turn, Supervisor, SupervisorRestartPolicy, Observe, Schemas, assertionFacetObserver, isEmbedded } from "@syndicate-lang/core";
|
||||
import * as G from "./gen/wsRelay";
|
||||
export * from "./gen/wsRelay";
|
||||
|
||||
export function boot(ds: Ref) {
|
||||
export function boot(ds: Ref, debug: boolean = false) {
|
||||
spawn named 'wsRelay' {
|
||||
at ds {
|
||||
during Observe({ "pattern": :pattern G.Resolved({
|
||||
"addr": \Q.lit($addrValue),
|
||||
"sturdyref": \Q.lit($sturdyRefValue),
|
||||
"resolved": \Q.bind(),
|
||||
}) }) => {
|
||||
const addr = G.toRelayAddress(addrValue);
|
||||
const sturdyref = Schemas.sturdy.toSturdyRef(sturdyRefValue);
|
||||
if (addr && sturdyref) {
|
||||
assert G.fromViaRelay(G.ViaRelay({
|
||||
"addr": addr,
|
||||
"assertion": Schemas.gatekeeper.fromResolve(Schemas.gatekeeper.Resolve({
|
||||
"sturdyref": sturdyref,
|
||||
"observer": create assertionFacetObserver(e => {
|
||||
if (isEmbedded(e)) {
|
||||
assert G.fromResolved(G.Resolved({
|
||||
"addr": addr,
|
||||
"sturdyref": sturdyref,
|
||||
"resolved": e.embeddedValue,
|
||||
}));
|
||||
}
|
||||
}),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
during G.ViaRelay({ "addr": $addrValue }) => spawn named ['wsRelay', addrValue] {
|
||||
let counter = 0;
|
||||
new Supervisor({
|
||||
restartPolicy: SupervisorRestartPolicy.ALWAYS,
|
||||
}, () => ['wsRelay', addrValue, counter++], () => {
|
||||
const facet = Turn.activeFacet;
|
||||
facet.preventInertCheck();
|
||||
const addr = G.toRelayAddress(addrValue);
|
||||
if (addr !== void 0) {
|
||||
stop on message G.ForceRelayDisconnect(addrValue);
|
||||
const facet = Turn.activeFacet;
|
||||
const ws = new WebSocket(addr.url);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onclose = () => facet.turn(() => { stop {} });
|
||||
|
@ -24,7 +50,7 @@ export function boot(ds: Ref) {
|
|||
Turn.active.crash(new Error("WebSocket error")));
|
||||
ws.onopen = () => facet.turn(() => {
|
||||
const relay = new Relay.Relay({
|
||||
debug: true,
|
||||
debug,
|
||||
trustPeer: true,
|
||||
packetWriter: bs => ws.send(bs),
|
||||
setup(r: Relay.Relay) {
|
||||
|
|
Loading…
Reference in New Issue