2021-12-12 22:59:49 +00:00
|
|
|
/// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
|
|
|
2023-01-31 15:26:43 +00:00
|
|
|
import { fromJS, Bytes, Dataspace, Ref, Sturdy, AnyValue, Reader, Schemas, Embedded, stringify } from "@syndicate-lang/core";
|
2021-12-13 11:21:29 +00:00
|
|
|
import { boot as bootHtml, Anchor, template as html, HtmlFragments, GlobalEvent, UIAttribute, UIChangeableProperty } from "@syndicate-lang/html";
|
2021-12-12 22:59:49 +00:00
|
|
|
import { boot as bootWakeDetector, WakeEvent } from "./wake-detector";
|
2023-01-31 15:26:43 +00:00
|
|
|
import { boot as bootWsRelay, ForceRelayDisconnect, RelayAddress, Resolved, Noise } from "@syndicate-lang/ws-relay";
|
2022-01-24 13:11:41 +00:00
|
|
|
import { Present, Says } from './gen/simpleChatProtocol';
|
2021-12-12 22:59:49 +00:00
|
|
|
|
|
|
|
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; };
|
2023-01-31 15:26:43 +00:00
|
|
|
setDataspaceAddress();
|
2021-12-12 22:59:49 +00:00
|
|
|
setUsernameIfUnset();
|
2021-12-13 11:21:29 +00:00
|
|
|
document.getElementById('chat_input')!.focus();
|
2021-12-12 22:59:49 +00:00
|
|
|
|
|
|
|
Dataspace.boot(ds => {
|
|
|
|
bootHtml(ds);
|
|
|
|
bootWakeDetector(ds);
|
2021-12-13 11:21:29 +00:00
|
|
|
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;
|
|
|
|
|
2023-01-31 15:26:43 +00:00
|
|
|
during UIChangeableProperty('#route', 'value', $routeText: string) => {
|
|
|
|
let route: Noise.Route<Ref> | null = null;
|
|
|
|
try {
|
|
|
|
route = Noise.asRoute(new Reader<Ref>(routeText).next());
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
2021-12-12 22:59:49 +00:00
|
|
|
}
|
2023-01-31 15:26:43 +00:00
|
|
|
assert UIAttribute('#route', 'class', 'invalid') when (!route);
|
|
|
|
if (route) contactRemote(route);
|
|
|
|
}
|
2021-12-12 22:59:49 +00:00
|
|
|
|
2023-01-31 15:26:43 +00:00
|
|
|
function contactRemote(route: Noise.Route<Ref>) {
|
|
|
|
console.log('contactRemote', route);
|
2021-12-13 11:21:29 +00:00
|
|
|
during Resolved({
|
2023-01-31 15:26:43 +00:00
|
|
|
"route": route,
|
|
|
|
"addr": $addr: RelayAddress,
|
2022-01-24 13:11:41 +00:00
|
|
|
"resolved": $remoteDs_e: Embedded,
|
2021-12-13 11:21:29 +00:00
|
|
|
}) => {
|
|
|
|
const remoteDs = remoteDs_e.embeddedValue;
|
|
|
|
|
2023-01-31 15:26:43 +00:00
|
|
|
on message WakeEvent() => send message ForceRelayDisconnect(addr);
|
2021-12-13 11:21:29 +00:00
|
|
|
|
2023-01-31 15:26:43 +00:00
|
|
|
outputState('connected', 'connected to ' + stringify(addr));
|
|
|
|
on stop outputState('disconnected', 'disconnected from ' + stringify(addr));
|
2021-12-13 11:21:29 +00:00
|
|
|
|
|
|
|
on message GlobalEvent('#send_chat', 'click', _) => {
|
|
|
|
const inp = document.getElementById("chat_input") as HTMLInputElement;
|
|
|
|
var utterance = inp.value;
|
|
|
|
inp.value = '';
|
|
|
|
if (utterance) {
|
|
|
|
at remoteDs {
|
2022-01-24 13:11:41 +00:00
|
|
|
send message Says({ who: nym.value, what: utterance });
|
2021-12-13 11:21:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-12-12 22:59:49 +00:00
|
|
|
|
2021-12-13 11:21:29 +00:00
|
|
|
at remoteDs {
|
2022-01-24 13:11:41 +00:00
|
|
|
assert Present(nym.value);
|
2021-12-12 22:59:49 +00:00
|
|
|
|
2021-12-13 11:21:29 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2021-12-12 22:59:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-31 15:26:43 +00:00
|
|
|
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(RelayAddress(Transport.WebSocket(u))));
|
|
|
|
|
|
|
|
route.value = stringify(Noise.Route<Ref>({
|
|
|
|
"transports": transports,
|
|
|
|
"steps": [Noise.RouteStep.NoiseStep(Noise.NoiseStep(Noise.NoiseSpec({
|
|
|
|
"service": "syndicate",
|
|
|
|
"key": Bytes.fromHex("21f6cd4e11e7e37711d6b3084ff18cded8fc8abf293aa47d43e8bb86dda65516"),
|
|
|
|
"protocol": Noise.NoiseProtocol.absent(),
|
|
|
|
"preSharedKeys": Noise.NoisePreSharedKeys.absent(),
|
|
|
|
})))],
|
|
|
|
}));
|
|
|
|
|
|
|
|
// To use a sturdyref instead:
|
|
|
|
//
|
|
|
|
// route.value = stringify(Noise.Route<Ref>({
|
|
|
|
// "transports": transports,
|
|
|
|
// "steps": [Noise.RouteStep.GatekeeperStep(
|
|
|
|
// Sturdy.SturdyRef({
|
|
|
|
// "oid": "syndicate",
|
|
|
|
// "caveatChain": [],
|
|
|
|
// "sig": Bytes.fromHex('a6480df5306611ddd0d3882b546e1977'),
|
|
|
|
// }))],
|
|
|
|
// }));
|
|
|
|
//
|
|
|
|
// ... and of course you can chain these things, depending on server setup.
|
2021-12-13 11:21:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setUsernameIfUnset() {
|
|
|
|
const nym = document.getElementById('nym')! as HTMLInputElement;
|
|
|
|
if (nym.value === '') {
|
|
|
|
nym.value = "nym" + Math.floor(Math.random() * 65536);
|
|
|
|
}
|
|
|
|
}
|
2021-12-12 22:59:49 +00:00
|
|
|
|
|
|
|
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>`);
|
|
|
|
}
|