First attempt at retargetting the %server demo onto %light channels.

This currently has a bunch of issues around canceling the channel, but
we have a minimal demo which is able to use Gall subscriptions from the
webpage over an EventSource.
This commit is contained in:
Elliot Glaysher 2019-01-09 15:43:43 -08:00
parent bee2b01fb6
commit 412a182c3e
2 changed files with 143 additions and 104 deletions

View File

@ -8,6 +8,7 @@
$% [%connect wire [(unit @t) (list @t)] %server] $% [%connect wire [(unit @t) (list @t)] %server]
[%wait wire @da] [%wait wire @da]
[%http-response =raw-http-response:light] [%http-response =raw-http-response:light]
[%diff %json json]
== ==
-- --
:: utilities: :: utilities:
@ -34,8 +35,10 @@
;h1:"Hello, {(trip name)}" ;h1:"Hello, {(trip name)}"
;p ;p
; Time is ; Time is
;span#time; ;span#time:"?????"
== ==
;button#start:"Start Timer"
;button#poke:"Random Poke"
;script(type "module", src "/~server/hello.js"); ;script(type "module", src "/~server/hello.js");
== ==
== ==
@ -47,76 +50,33 @@
import * as urb from '/~/channel/channel.js'; import * as urb from '/~/channel/channel.js';
var c = urb.newChannel(); var c = urb.newChannel();
c.poke("zod", "server", "json", 5,
// The poke button just sends a poke
document.getElementById("poke").addEventListener("click", function(){
c.poke("zod", "server", "json", 5,
function() { function() {
console.log("Poke worked"); console.log("Poke worked");
}, },
function(err) { function(err) {
console.log("Poke failed: " + err); console.log("Poke failed: " + err);
}); });
});
var evtSource = new EventSource("/~server/stream", // The subscription sends the time which makes the thing work.
{ withCredentials: true } ); //
c.subscribe("zod", "server", "/timer",
evtSource.onmessage = function(e) { function(err) {
var message = document.getElementById("time"); console.log("Failed initial connection: " + err);
message.innerHTML = e.data; },
} function(json) {
console.log("Subscription update: ", json);
var message = document.getElementById("time");
message.innerHTML = json;
},
function() {
console.log("Subscription quit");
});
''' '''
:: helper library that lets an app handle an EventSource.
::
:: TODO: This doesn't even attempt to deal with sequence numbers.
::
++ event-source
|_ m=(map =bone last-id=@ud)
++ abet m
:: +start-session: called by app to start a session and send first event
::
:: This creates a new session where we
::
++ start-session
|= [session=@ud =bone data=wall]
^- [(list move) _m]
::
:- :~ :* bone %http-response
%start 200
:~ ['content-type' 'text/event-stream']
['cache-control' 'no-cache']
==
(wall-to-output data)
complete=%.n
== ==
(~(put by m) bone 0)
:: +session-stopped: external notification that a session ended
::
++ session-stopped
|= =bone
^- _m
::
(~(del by m) bone)
:: +send-message: sends a message based on the continuation
::
++ send-message
|= [=bone data=wall]
^- [(list move) _m]
:- [bone %http-response %continue (wall-to-output data) complete=%.n]~
(~(jab by m) bone |=(a=@ud +(a)))
:: +wall-to-output: changes our raw text lines to a text/event-stream
::
++ wall-to-output
|= =wall
^- (unit octs)
:- ~
%- as-octs:mimes:html
%- crip
%- zing
%+ weld
%+ turn wall
|= t=tape
"data: {t}\0a"
::
[`tape`['\0a' ~] ~]
--
:: +require-authorization: redirect to the login page when unauthenticated :: +require-authorization: redirect to the login page when unauthenticated
:: ::
++ require-authorization ++ require-authorization
@ -138,7 +98,7 @@
|% |%
:: ::
+$ state +$ state
$: events=(map =bone last-id=@ud) $: next-timer=(unit @da)
== ==
-- --
:: ::
@ -161,37 +121,30 @@
~& [%bound success] ~& [%bound success]
[~ this] [~ this]
:: ::
++ handle-start-stream
|= =inbound-request:light
^- (quip move _this)
:: Start a session sending the current time
::
=^ moves events
(~(start-session event-source events) 0 ost.bow ["{<now.bow>}" ~])
::
:_ this
:- ^- move
[ost.bow %wait /timer (add now.bow ~s1)]
::
moves
:: +wake: responds to a %wait send from +handle-start-stream :: +wake: responds to a %wait send from +handle-start-stream
:: ::
++ wake ++ wake
|= [wir=wire ~] |= [wir=wire ~]
^- (quip move _this) ^- (quip move _this)
?. (~(has by events) ost.bow)
~& [%closed wir now.bow]
[~ this]
:: ::
~& [%timer-tick wir now.bow] ~& [%timer-tick wir now.bow]
:: ::
=^ moves events =/ moves=(list move)
(~(send-message event-source events) ost.bow ["{<now.bow>}" ~]) %+ turn (prey:pubsub:userlib /timer bow)
|= [=bone ^]
[bone %diff %json %s (scot %da now.bow)]
:: if we have outbound moves, say that we have another timer.
:: ::
:_ this =. next-timer
:- ^- move ?: ?=(^ moves)
[ost.bow %wait /timer (add now.bow ~s1)] `(add now.bow ~s1)
moves ~
:: if we have any subscribers, add another timer for the future
::
=? moves ?=(^ moves)
[[ost.bow %wait /timer (add now.bow ~s1)] moves]
::
[moves this]
:: +poke-handle-http-request: received on a new connection established :: +poke-handle-http-request: received on a new connection established
:: ::
++ poke-handle-http-request ++ poke-handle-http-request
@ -206,9 +159,6 @@
?~ back-path ?~ back-path
'World' 'World'
i.back-path i.back-path
?: =(name 'stream')
(handle-start-stream inbound-request)
~& [%name name]
:: ::
?: =(name 'hello') ?: =(name 'hello')
:_ this :_ this
@ -233,8 +183,22 @@
^- (quip move _this) ^- (quip move _this)
:: the only long lived connections we keep state about are the stream ones. :: the only long lived connections we keep state about are the stream ones.
:: ::
=. events
(~(session-stopped event-source events) ost.bow)
::
[~ this] [~ this]
::
++ poke-json
|= =json
^- (quip move _this)
~& [%poke-json json]
[~ this]
::
++ peer-timer
|= pax/path
^- (quip move _this)
:: if we don't have a timer, set a timer.
?: ?=(^ next-timer)
~& [%already-have-a-timer next-timer]
[~ this]
::
:- [ost.bow %wait /timer (add now.bow ~s1)]~
this(next-timer `(unit @da)`[~ (add now.bow ~s1)])
-- --

View File

@ -283,7 +283,7 @@
== ==
:: channel-timeout: the delay before a channel should be reaped :: channel-timeout: the delay before a channel should be reaped
:: ::
++ channel-timeout ~h12 ++ channel-timeout ~s45
-- --
:: utilities :: utilities
:: ::
@ -417,6 +417,8 @@
== ==
:: +channel-js: the urbit javascript interface :: +channel-js: the urbit javascript interface
:: ::
:: TODO: Must send 'acks' to the server.
::
++ channel-js ++ channel-js
^- octs ^- octs
%- as-octs:mimes:html %- as-octs:mimes:html
@ -431,7 +433,14 @@
Math.random().toString(16).slice(-6); Math.random().toString(16).slice(-6);
this.requestId = 1; this.requestId = 1;
this.connection = null;
// the currently connected EventSource
//
this.eventSource = null;
// the id of the last EventSource event we received
//
this.lastEventId = 0;
// a registry of requestId to successFunc/failureFunc // a registry of requestId to successFunc/failureFunc
// //
@ -442,7 +451,7 @@
// //
this.outstandingPokes = new Map(); this.outstandingPokes = new Map();
// a registry of requestId to eventFunc/disconnectFunc // a registry of requestId to subscription functions.
// //
// These functions are registered during a +subscribe and are // These functions are registered during a +subscribe and are
// executed in the onServerEvent()/onServerError() callbacks. The // executed in the onServerEvent()/onServerError() callbacks. The
@ -460,37 +469,91 @@
this.outstandingPokes.set( this.outstandingPokes.set(
id, {"success": successFunc, "fail": failureFunc}); id, {"success": successFunc, "fail": failureFunc});
var req = new XMLHttpRequest(); this.sendJSONToChannel({
req.open("PUT", this.channelURL());
req.setRequestHeader("Content-Type", "application/json");
// TODO: Need to stuff an "ack" in here, too.
var x = JSON.stringify([{
"id": id, "id": id,
"action": "poke", "action": "poke",
"ship": ship, "ship": ship,
"app": app, "app": app,
"mark": mark, "mark": mark,
"json": json "json": json
}]); });
}
// subscribes to a path on an
//
subscribe(ship, app, path, connectionErrFunc, eventFunc, quitFunc) {
var id = this.nextId();
this.outstandingSubscriptions.set(
id, {"err": connectionErrFunc, "event": eventFunc, "quit": quitFunc});
this.sendJSONToChannel({
"id": id,
"action": "subscribe",
"ship": ship,
"app": app,
"path": path
});
}
// sends a JSON command command to the server.
//
// TODO: This should also bundle an acknowledgment of the last received
// request id.
//
sendJSONToChannel(j) {
var req = new XMLHttpRequest();
req.open("PUT", this.channelURL());
req.setRequestHeader("Content-Type", "application/json");
// TODO: Need to stuff an "ack" in here, too.
var x = JSON.stringify([j]);
req.send(x); req.send(x);
this.connectIfDisconnected(); this.connectIfDisconnected();
} }
// connects to the EventSource if we are not currently connected
//
connectIfDisconnected() { connectIfDisconnected() {
if (this.connection) if (this.eventSource)
return; return;
this.eventSource = new EventSource(this.channelURL(), {withCredentials:true}); this.eventSource = new EventSource(this.channelURL(), {withCredentials:true});
this.eventSource.onmessage = e => { this.eventSource.onmessage = e => {
this.lastEventId = e.id;
var obj = JSON.parse(e.data); var obj = JSON.parse(e.data);
if (obj.response == "poke") { if (obj.response == "poke") {
var funcs = this.outstandingPokes.get(obj.id); var funcs = this.outstandingPokes.get(obj.id);
if (obj.hasOwnProperty("ok")) if (obj.hasOwnProperty("ok"))
funcs["success"]() funcs["success"]()
else else if (obj.hasOwnProperty("err"))
funcs["fail"](obj.err) funcs["fail"](obj.err)
else
console.log("Invalid poke response: ", obj);
this.outstandingPokes.delete(obj.id); this.outstandingPokes.delete(obj.id);
} else if (obj.response == "subscribe") {
// on a response to a subscribe, we only notify the caller on err
//
var funcs = this.outstandingSubscriptions.get(obj.id);
if (obj.hasOwnProperty("err")) {
funcs["err"](obj.err);
this.outstandingSubscriptions.delete(obj.id);
} else {
console.log("Subscription establisthed");
}
} else if (obj.response == "diff") {
console.log("Diff: ", obj);
var funcs = this.outstandingSubscriptions.get(obj.id);
funcs["event"](obj.json);
} else if (obj.response == "quit") {
var funcs = this.outstandingSubscriptions.get(obj.id);
funcs["quit"](obj.err);
this.outstandingSubscriptions.delete(obj.id);
} else { } else {
console.log("Unrecognized response: ", e); console.log("Unrecognized response: ", e);
} }
@ -911,6 +974,8 @@
:: ::
++ on-cancel-request ++ on-cancel-request
^- [(list move) server-state] ^- [(list move) server-state]
::
~& [%channel-on-cancel-request duct]
:: lookup the session id by duct :: lookup the session id by duct
:: ::
?~ maybe-channel-id=(~(get by duct-to-key.channel-state.state) duct) ?~ maybe-channel-id=(~(get by duct-to-key.channel-state.state) duct)
@ -1214,6 +1279,16 @@
== ==
:: ::
(emit-event channel-id [(en-json:html json)]~) (emit-event channel-id [(en-json:html json)]~)
::
%quit
=/ =json
=, enjs:format
%- pairs :~
['response' [%s 'quit']]
['id' (numb request-id)]
==
::
(emit-event channel-id [(en-json:html json)]~)
:: ::
%reap %reap
=/ =json =/ =json