diff --git a/README.md b/README.md index 904fdc9..209a769 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ ## Syndesizer -A Syndicate multitool. Includes a number of different actors that become active via configuration. +A Syndicate multitool that includes a number of different actors that become active via configuration. + +Think of it as a Busybox for Syndicate, if Busybox was created before POSIX. + Whether you use a single instance for many protocols or many specialized instances is up to you. ### Cache @@ -127,6 +130,28 @@ Request data is formated according to the http schema [defined in syndicate-prot ] ``` +### Websockets + +connects to a websocket endpoint. During the lifetime of the connection a `` assertion is made. Messages received from the server are sent to the dataspace wrapped in `` records and messages observed as `` are sent to the server. + +``` +# Configuration example +> + +let ?websocketspace = dataspace + +? ?cap> [ + $cap +] + +$websocketspace ? [ + $websocketspace #f> +] +``` + --- ## mintsturdyref diff --git a/config.prs b/config.prs index f164f21..527f5b8 100644 --- a/config.prs +++ b/config.prs @@ -23,5 +23,10 @@ WebhooksArguments = . +WebsocketArguments = . + # Reused from syndicate-protocols/transportAddress Tcp = . diff --git a/lock.json b/lock.json index af84289..cb4cf39 100644 --- a/lock.json +++ b/lock.json @@ -82,6 +82,18 @@ "sha256": "0n1gbwllwwilz9fp5zyp4054vzcq1p7ddzg02sw8d0vqb1wmpsqm", "srcDir": "src", "url": "https://git.syndicate-lang.org/ehmry/syndicate-nim/archive/3e11884a916c0452c90128c29940856e2d347cb7.tar.gz" + }, + { + "method": "fetchzip", + "packages": [ + "ws" + ], + "path": "/nix/store/zd51j4dphs6h1hyhdbzdv840c8813ai8-source", + "ref": "0.5.0", + "rev": "9536bf99ddf5948db221ccb7bb3663aa238a8e21", + "sha256": "0j8z9jlvzb1h60v7rryvh2wx6vg99lra6i62whf3fknc53l641fz", + "srcDir": "src", + "url": "https://github.com/treeform/ws/archive/9536bf99ddf5948db221ccb7bb3663aa238a8e21.tar.gz" } ] } diff --git a/src/schema/config.nim b/src/schema/config.nim index 0bba3a3..822c2f4 100644 --- a/src/schema/config.nim +++ b/src/schema/config.nim @@ -3,6 +3,13 @@ import preserves, std/tables type + WebsocketArgumentsField0* {.preservesDictionary.} = object + `dataspace`* {.preservesEmbedded.}: EmbeddedRef + `url`*: string + + WebsocketArguments* {.preservesRecord: "websocket".} = object + `field0`*: WebsocketArgumentsField0 + JsonTranslatorArgumentsField0* {.preservesDictionary.} = object `argv`*: seq[string] `dataspace`* {.preservesEmbedded.}: EmbeddedRef @@ -38,14 +45,16 @@ type `host`*: string `port`*: BiggestInt -proc `$`*(x: JsonTranslatorArguments | JsonTranslatorConnected | +proc `$`*(x: WebsocketArguments | JsonTranslatorArguments | + JsonTranslatorConnected | JsonSocketTranslatorArguments | WebhooksArguments | CacheArguments | Tcp): string = `$`(toPreserves(x)) -proc encode*(x: JsonTranslatorArguments | JsonTranslatorConnected | +proc encode*(x: WebsocketArguments | JsonTranslatorArguments | + JsonTranslatorConnected | JsonSocketTranslatorArguments | WebhooksArguments | CacheArguments | diff --git a/src/syndesizer.nim b/src/syndesizer.nim index 6d4287f..10d7049 100644 --- a/src/syndesizer.nim +++ b/src/syndesizer.nim @@ -6,7 +6,7 @@ import syndicate, syndicate/relays, syndicate/actors/timers import ./syndesizer/[ - cache_actor, json_socket_translator, json_translator, webhooks] + cache_actor, json_socket_translator, json_translator, webhooks, websockets] runActor("syndesizer") do (turn: var Turn; root: Cap): connectStdio(turn, root) @@ -15,3 +15,4 @@ runActor("syndesizer") do (turn: var Turn; root: Cap): discard spawnJsonSocketTranslator(turn, root) discard spawnJsonStdioTranslator(turn, root) discard spawnWebhookActor(turn, root) + discard spawnWebsocketActor(turn, root) diff --git a/src/syndesizer/websockets.nim b/src/syndesizer/websockets.nim new file mode 100644 index 0000000..76d4863 --- /dev/null +++ b/src/syndesizer/websockets.nim @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: ☭ Emery Hemingway +# SPDX-License-Identifier: Unlicense + +import std/[asyncdispatch, json] +import preserves, preserves/jsonhooks +import syndicate, syndicate/relays +import ws + +import ../schema/config, ../json_messages + +type WebSocket = ws.WebSocket + # not the object from the transportAddress schema + +proc spawnWebsocketActor*(turn: var Turn; root: Cap): Actor = + spawn("websocket-actor", turn) do (turn: var Turn): + during(turn, root, ?:WebsocketArguments) do (ds: Cap, url: string): + let facet = turn.facet + var + ws: WebSocket + connectedHandle: Handle + newWebSocket(url).addCallback(turn) do (turn: var Turn; sock: WebSocket): + ws = sock + let connectedHandle = publish(turn, ds, initRecord("connected", url.toPreserves)) + var fut: Future[(Opcode, string)] + proc recvMessage() {.gcsafe.} = + fut = receivePacket ws + addCallback(fut, facet) do (turn: var Turn): + let (opcode, data) = read fut + case opcode + of Text: + message(turn, ds, + RecvJson(data: data.parseJson)) + of Binary: + message(turn, ds, + initRecord("recv", cast[seq[byte]](data).toPreserves)) + of Ping: + asyncCheck(turn, ws.send(data, Pong)) + of Pong, Cont: + discard + of Close: + retract(turn, connectedHandle) + stderr.writeLine "closed connection with ", url + stop(turn) + return + recvMessage() + recvMessage() + onMessage(turn, ds, ?:SendJson) do (data: JsonNode): + asyncCheck(turn, ws.send($data, Text)) + do: + close(ws) + +when isMainModule: + runActor("main") do (turn: var Turn; root: Cap): + connectStdio(turn, root) + discard spawnWebsocketActor(turn, root) diff --git a/syndicate_utils.nimble b/syndicate_utils.nimble index 7ccd565..c4fbf65 100644 --- a/syndicate_utils.nimble +++ b/syndicate_utils.nimble @@ -1,6 +1,6 @@ # Package -version = "20240108" +version = "20240109" author = "Emery Hemingway" description = "Utilites for Syndicated Actors and Synit" license = "unlicense" @@ -10,4 +10,4 @@ bin = @["mintsturdyref", "mount_actor", "msg", "net_mapper", "preserve # Dependencies -requires "nim >= 2.0.0", "illwill", "syndicate >= 20240108" +requires "nim >= 2.0.0", "illwill", "syndicate >= 20240108", "ws"