webshare websocket implementation

This commit is contained in:
sawka
2023-03-29 17:51:42 -07:00
parent 394a2a9ab3
commit 414cd6bf0a
3 changed files with 214 additions and 6 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ type OArr<V> = mobx.IObservableArray<V>;
type OMap<K,V> = mobx.ObservableMap<K,V>;
// TODO selection
// TODO remotevars
// TODO websocket
function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string) : string {
if (isBlank(ownerName) && isBlank(name)) {
+23 -5
View File
@@ -6,6 +6,7 @@ import * as T from "./types";
import {TermWrap} from "./term";
import * as lineutil from "./lineutil";
import {windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows} from "./textmeasure";
import {WebShareWSControl} from "./webshare-ws";
type OV<V> = mobx.IObservableValue<V>;
type OArr<V> = mobx.IObservableArray<V>;
@@ -20,6 +21,10 @@ function getBaseUrl() {
return "https://ot2e112zx5.execute-api.us-west-2.amazonaws.com/dev";
}
function getBaseWSUrl() {
return "wss://5lfzlg5crl.execute-api.us-west-2.amazonaws.com/dev";
}
class WebShareModelClass {
viewKey : string;
screenId : string;
@@ -29,13 +34,14 @@ class WebShareModelClass {
renderers : Record<string, T.RendererModel> = {}; // lineid => RendererModel
contentHeightCache : Record<string, number> = {}; // lineid => height
selectedLine : OV<number> = mobx.observable.box(0, {name: "selectedLine"});
wsControl : WebShareWSControl;
constructor() {
let urlParams = new URLSearchParams(window.location.search);
this.viewKey = urlParams.get("viewkey");
this.screenId = urlParams.get("screenid");
setTimeout(() => this.loadFullScreenData(), 10);
this.wsControl = new WebShareWSControl(getBaseWSUrl(), this.screenId, this.viewKey, this.wsMessageCallback.bind(this));
}
setErrMessage(msg : string) : void {
@@ -52,6 +58,21 @@ class WebShareModelClass {
return 12;
}
wsMessageCallback(msg : any) {
console.log("ws message", msg);
}
setWebFullScreen(screen : T.WebFullScreen) {
mobx.action(() => {
this.screen.set(screen);
if (screen.lines != null && screen.lines.length > 0) {
this.selectedLine.set(screen.lines[screen.lines.length-1].linenum);
}
this.wsControl.reconnect(true);
})();
}
loadTerminalRenderer(elem : Element, line : T.WebLine, cmd : T.WebCmd, width : number) : void {
let lineId = cmd.lineid;
let termWrap = this.getTermWrap(lineId);
@@ -181,10 +202,7 @@ class WebShareModelClass {
fetch(url, {method: "GET", mode: "cors", cache: "no-cache"}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
mobx.action(() => {
let screen : T.WebFullScreen = data;
this.screen.set(screen);
if (screen.lines != null && screen.lines.length > 0) {
this.selectedLine.set(screen.lines[screen.lines.length-1].linenum);
}
this.setWebFullScreen(screen);
})();
}).catch((err) => {
this.errMessage.set("Cannot get screen: " + err.message);
+190
View File
@@ -0,0 +1,190 @@
import * as mobx from "mobx";
import {sprintf} from "sprintf-js";
import {boundMethod} from "autobind-decorator";
import {WatchScreenPacketType} from "./types";
import dayjs from "dayjs";
class WebShareWSControl {
wsConn : any;
open : mobx.IObservableValue<boolean>;
opening : boolean = false;
reconnectTimes : number = 0;
msgQueue : any[] = [];
messageCallback : (any) => void = null;
screenId : string = null;
viewKey : string = null;
wsUrl : string;
closed : boolean;
constructor(wsUrl : string, screenId : string, viewKey : string, messageCallback : (any) => void) {
this.wsUrl = wsUrl;
this.messageCallback = messageCallback;
this.screenId = screenId;
this.viewKey = viewKey;
this.open = mobx.observable.box(false, {name: "WSOpen"});
this.closed = true;
setInterval(this.sendPing, 15000);
}
close() : void {
this.closed = true;
if (this.wsConn != null) {
this.wsConn.close();
}
}
log(str : string) {
console.log("[wscontrol]", str);
}
@mobx.action
setOpen(val : boolean) {
mobx.action(() => {
this.open.set(val);
})();
}
connectNow(desc : string) {
this.closed = false;
if (this.open.get()) {
return;
}
this.log(sprintf("try reconnect (%s)", desc));
this.opening = true;
this.wsConn = new WebSocket(this.wsUrl);
this.wsConn.onopen = this.onopen;
this.wsConn.onmessage = this.onmessage;
this.wsConn.onclose = this.onclose;
// turns out onerror is not necessary (onclose always follows onerror)
// this.wsConn.onerror = this.onerror;
}
reconnect(forceClose? : boolean) {
this.closed = false;
if (this.open.get()) {
if (forceClose) {
this.wsConn.close(); // this will force a reconnect
}
return;
}
this.reconnectTimes++;
if (this.reconnectTimes > 20) {
this.log("cannot connect, giving up");
return;
}
let timeoutArr = [0, 5, 5, 15, 30, 60, 300, 3600];
let timeout = 60;
if (this.reconnectTimes < timeoutArr.length) {
timeout = timeoutArr[this.reconnectTimes];
}
if (timeout > 0) {
this.log(sprintf("sleeping %ds", timeout));
}
setTimeout(() => {
this.connectNow(String(this.reconnectTimes));
}, timeout*1000);
}
@boundMethod
onclose(event : any) {
// console.log("close", event);
if (event.wasClean) {
this.log("connection closed");
}
else {
this.log("connection error/disconnected");
}
if (this.open.get() || this.opening) {
this.setOpen(false);
this.opening = false;
if (!this.closed) {
this.reconnect();
}
}
}
@boundMethod
onopen() {
this.log("connection open");
this.setOpen(true);
this.opening = false;
this.runMsgQueue();
this.sendWebShareInit();
// reconnectTimes is reset in onmessage:hello
}
runMsgQueue() {
if (!this.open.get()) {
return;
}
if (this.msgQueue.length == 0) {
return;
}
let msg = this.msgQueue.shift();
this.sendMessage(msg);
setTimeout(() => {
this.runMsgQueue();
}, 100);
}
@boundMethod
onmessage(event : any) {
let eventData = null;
if (event.data != null) {
eventData = JSON.parse(event.data);
}
if (eventData == null) {
return;
}
if (eventData.type == "ping") {
this.wsConn.send(JSON.stringify({type: "pong", stime: Date.now()}));
return;
}
if (eventData.type == "pong") {
// nothing
return;
}
if (eventData.type == "hello") {
this.reconnectTimes = 0;
return;
}
if (this.messageCallback) {
try {
this.messageCallback(eventData);
}
catch (e) {
this.log("[error] messageCallback", e);
}
}
}
@boundMethod
sendPing() {
if (!this.open.get()) {
return;
}
this.wsConn.send(JSON.stringify({type: "ping", stime: Date.now()}));
}
sendMessage(data : any) {
if (!this.open.get()) {
return;
}
this.wsConn.send(JSON.stringify(data));
}
pushMessage(data : any) {
if (!this.open.get()) {
this.msgQueue.push(data);
return;
}
this.sendMessage(data);
}
sendWebShareInit() {
let pk : WatchScreenPacketType = {"type": "webshare", screenid: this.screenId, viewkey: this.viewKey};
this.pushMessage(pk);
}
}
export {WebShareWSControl};