159 lines
6.2 KiB
TypeScript
159 lines
6.2 KiB
TypeScript
/// 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, Embedded, 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 { Present, Says } from './gen/simpleChatProtocol';
|
|
import G = Schemas.gatekeeper;
|
|
import N = Schemas.noise;
|
|
|
|
export function main() {
|
|
document.getElementById('chat_form')!.onsubmit = e => { e.preventDefault(); return false; };
|
|
document.getElementById('nym_form')!.onsubmit = e => { e.preventDefault(); return false; };
|
|
setDataspaceAddress();
|
|
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('#route', 'value', $routeText: string) => {
|
|
let route: G.Route<Ref> | null = null;
|
|
try {
|
|
route = G.asRoute(new Reader<Ref>(routeText).next());
|
|
} catch (e) {
|
|
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_e: Embedded,
|
|
"resolved": G.Resolved.accepted($remoteDs_e: Embedded),
|
|
}) => {
|
|
const remoteDs = remoteDs_e.embeddedValue;
|
|
const control = control_e.embeddedValue;
|
|
|
|
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>`);
|
|
}
|
|
|
|
on message Says({ "who": $who: string, "what": $what: string }) => {
|
|
outputUtterance(who, what);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function setDataspaceAddress() {
|
|
const route = document.getElementById('route')! as HTMLInputElement;
|
|
if (route.value === '') {
|
|
const isSecure = (document.location.protocol ?? '').toLowerCase().endsWith('s:');
|
|
const localWs = isSecure ? 'wss' : 'ws';
|
|
const wsurls = [
|
|
`wss://generic-dataspace.demo.leastfixedpoint.com/`,
|
|
`${localWs}://${document.location.hostname}:9001/`,
|
|
];
|
|
const transports: AnyValue[] =
|
|
wsurls.map(u => fromJS(Schemas.transportAddress.WebSocket(u)));
|
|
|
|
route.value = stringify(G.Route<Ref>({
|
|
"transports": transports,
|
|
"pathSteps": [G.PathStep({
|
|
"stepType": N.$noise,
|
|
"detail": fromJS(N.NoiseSpec({
|
|
"service": "syndicate",
|
|
"key": Bytes.fromHex("21f6cd4e11e7e37711d6b3084ff18cded8fc8abf293aa47d43e8bb86dda65516"),
|
|
"protocol": N.NoiseProtocol.absent(),
|
|
"preSharedKeys": N.NoisePreSharedKeys.absent(),
|
|
})),
|
|
})],
|
|
}));
|
|
|
|
// To use a sturdyref instead:
|
|
//
|
|
// route.value = stringify(G.Route<Ref>({
|
|
// "transports": transports,
|
|
// "pathSteps": [G.PathStep({
|
|
// "stepType": Sturdy.$ref,
|
|
// "detail": fromJS(Sturdy.Parameters({
|
|
// "oid": "syndicate",
|
|
// "sig": Bytes.fromHex('69ca300c1dbfa08fba692102dd82311a'),
|
|
// "caveats": Sturdy.CaveatsField.absent(),
|
|
// })),
|
|
// })],
|
|
// }));
|
|
//
|
|
// ... and of course you can chain these things, depending on server setup.
|
|
}
|
|
}
|
|
|
|
function setUsernameIfUnset() {
|
|
const nym = document.getElementById('nym')! as HTMLInputElement;
|
|
if (nym.value === '') {
|
|
nym.value = "nym" + Math.floor(Math.random() * 65536);
|
|
}
|
|
}
|
|
|
|
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>`;
|
|
const n = item.node();
|
|
const o = document.getElementById('chat_output')!;
|
|
o.appendChild(n);
|
|
o.scrollTop = o.scrollHeight;
|
|
return n;
|
|
}
|
|
|
|
function outputState(cls: string, state: string) {
|
|
outputItem(`state_${cls}`, html`<span class="state ${cls}">${state}</span>`)
|
|
}
|
|
|
|
function outputUtterance(who: string, what: string) {
|
|
outputItem('utterance',
|
|
html`<span class="nym">${who}</span><span class="utterance">${what}</span>`);
|
|
}
|