switch to new model of ws notification -> screen update fetch

This commit is contained in:
sawka 2023-03-31 21:02:44 -07:00
parent 10da862942
commit 8599e2b57c
6 changed files with 211 additions and 68 deletions

View File

@ -36,7 +36,7 @@ class SimpleBlobRendererModel {
this.savedHeight = params.savedHeight;
this.ptyDataSource = params.ptyDataSource;
if (this.isDone.get()) {
this.reload(0);
setTimeout(() => this.reload(0), 10);
}
}
@ -74,6 +74,10 @@ class SimpleBlobRendererModel {
this.loading.set(true);
})();
let rtnp = this.ptyDataSource(this.context);
if (rtnp == null) {
console.log("no promise returned from ptyDataSource (simplerenderer)", this.context);
return;
}
rtnp.then((ptydata) => {
setTimeout(() => {
this.ptyData = ptydata;

View File

@ -109,7 +109,7 @@ class TermWrap {
if (opts.customKeyHandler != null) {
this.terminal.attachCustomKeyEventHandler((e) => opts.customKeyHandler(e, this));
}
this.reload(0);
setTimeout(() => this.reload(0), 10);
}
getUsedRows() : number {
@ -281,6 +281,10 @@ class TermWrap {
this.reloading = true;
this.terminal.reset();
let rtnp = this.ptyDataSource(this.termContext);
if (rtnp == null) {
console.log("no promise returned from ptyDataSource (termwrap)", this.termContext);
return;
}
rtnp.then((ptydata) => {
setTimeout(() => {
this._reloadThenHandler(ptydata);

View File

@ -535,9 +535,11 @@ type WebCmd = {
};
type WebFullScreen = {
screenid : string,
screen : WebScreen,
lines : WebLine[],
cmds : WebCmd[],
cmdptymap : Record<string, number>,
vts : number,
}
@ -548,16 +550,6 @@ type PtyDataUpdate = {
data : string,
};
type WebScreenUpdate = {
type : string,
screenid : string,
screen : WebScreen,
lines : WebLine[],
cmds : WebCmd[],
ptydata : PtyDataUpdate[],
removedlines : string[],
};
type WebShareWSMessage = {
type : string,
screenid : string,
@ -589,4 +581,4 @@ type RendererContainerType = {
type LineHeightChangeCallbackType = (lineNum : number, newHeight : number, oldHeight : number) => void;
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate, ClientMigrationInfo, WebShareOpts, RemoteStatusTypeStrs, WebFullScreen, WebScreen, WebLine, WebCmd, RemoteTermContext, TermContextUnion, WebRemote, WebScreenUpdate, PtyDataUpdate, WebShareWSMessage, LineHeightChangeCallbackType, LineFactoryProps, LineInterface, RendererContainerType};
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate, ClientMigrationInfo, WebShareOpts, RemoteStatusTypeStrs, WebFullScreen, WebScreen, WebLine, WebCmd, RemoteTermContext, TermContextUnion, WebRemote, PtyDataUpdate, WebShareWSMessage, LineHeightChangeCallbackType, LineFactoryProps, LineInterface, RendererContainerType};

View File

@ -24,7 +24,6 @@ type OMap<K,V> = mobx.ObservableMap<K,V>;
let foo = LinesView;
// TODO reshare
// TODO debounce some of the updates
function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string) : string {
if (isBlank(ownerName) && isBlank(name)) {
@ -624,7 +623,7 @@ class WebScreenView extends React.Component<{}, {}> {
}
render() {
let fullScreen = WebShareModel.screen.get();
let fullScreen = WebShareModel.fullScreen.get();
if (fullScreen == null || fullScreen.lines.length == 0) {
return this.renderEmpty();
}
@ -643,7 +642,7 @@ class WebShareMain extends React.Component<{}, {}> {
}
render() {
let screen = WebShareModel.screen.get();
let screen = WebShareModel.fullScreen.get();
let errMessage = WebShareModel.errMessage.get();
let shareName = "";
if (screen != null) {

View File

@ -14,6 +14,10 @@ type OArr<V> = mobx.IObservableArray<V>;
type OMap<K,V> = mobx.ObservableMap<K,V>;
type CV<V> = mobx.IComputedValue<V>;
type PtyListener = {
receiveData(ptyPos : number, data : Uint8Array, reason? : string);
};
function isBlank(s : string) {
return (s == null || s == "");
}
@ -30,7 +34,7 @@ class WebShareModelClass {
viewKey : string;
screenId : string;
errMessage : OV<string> = mobx.observable.box(null, {name: "errMessage"});
screen : OV<T.WebFullScreen> = mobx.observable.box(null, {name: "webScreen"});
fullScreen : OV<T.WebFullScreen> = mobx.observable.box(null, {name: "webScreen"});
terminals : Record<string, TermWrap> = {}; // lineid => TermWrap
renderers : Record<string, T.RendererModel> = {}; // lineid => RendererModel
contentHeightCache : Record<string, number> = {}; // lineid => height
@ -39,12 +43,17 @@ class WebShareModelClass {
selectedLine : OV<number> = mobx.observable.box(0, {name: "selectedLine"});
syncSelectedLine : OV<boolean> = mobx.observable.box(true, {name: "syncSelectedLine"});
lastScreenSize : T.WindowSize = null;
activePtyFetch : Record<string, boolean> = {}; // lineid -> active
localPtyOffsetMap : Record<string, number> = {};
remotePtyOffsetMap : Record<string, number> = {};
activeUpdateFetch : boolean = false;
remoteScreenVts : number = 0;
constructor() {
let urlParams = new URLSearchParams(window.location.search);
this.viewKey = urlParams.get("viewkey");
this.screenId = urlParams.get("screenid");
setTimeout(() => this.loadFullScreenData(), 10);
setTimeout(() => this.loadFullScreenData(false), 10);
this.wsControl = new WebShareWSControl(getBaseWSUrl(), this.screenId, this.viewKey, this.wsMessageCallback.bind(this));
document.addEventListener("keydown", this.docKeyDownHandler.bind(this));
}
@ -59,7 +68,7 @@ class WebShareModelClass {
mobx.action(() => {
this.syncSelectedLine.set(val);
if (val) {
let fullScreen = this.screen.get();
let fullScreen = this.fullScreen.get();
if (fullScreen != null) {
this.selectedLine.set(fullScreen.screen.selectedline);
}
@ -103,7 +112,7 @@ class WebShareModelClass {
}
getServerSelectedLine() : number {
let fullScreen = this.screen.get();
let fullScreen = this.fullScreen.get();
if (fullScreen != null) {
return fullScreen.screen.selectedline;
}
@ -116,7 +125,7 @@ class WebShareModelClass {
}
updateSelectedLineIndex(delta : number) : void {
let fullScreen = this.screen.get();
let fullScreen = this.fullScreen.get();
if (fullScreen == null) {
return;
}
@ -209,14 +218,20 @@ class WebShareModelClass {
fullScreen.cmds.push(newCmd);
}
mergeUpdate(msg : T.WebScreenUpdate) {
mergeUpdate(msg : T.WebFullScreen) {
if (msg.screenid != this.screenId) {
console.log("bad WebScreenUpdate, wrong screenid", msg.screenid);
console.log("bad WebFullScreen update, wrong screenid", msg.screenid);
return;
}
console.log("merge", msg);
console.log("merge screen-update", "vts=" + msg.vts);
// console.log("merge", "vts=" + msg.vts, msg);
mobx.action(() => {
let fullScreen = this.screen.get();
let fullScreen = this.fullScreen.get();
if (fullScreen.vts >= msg.vts) {
console.log("stale merge", "cur-vts=" + fullScreen.vts, "merge-vts=" + msg.vts);
return;
}
fullScreen.vts = msg.vts;
if (msg.screen) {
fullScreen.screen = msg.screen;
if (this.syncSelectedLine.get()) {
@ -225,6 +240,10 @@ class WebShareModelClass {
}
if (msg.lines != null && msg.lines.length > 0) {
for (let line of msg.lines) {
if (line.archived) {
this.removeLine(fullScreen, line.lineid);
continue;
}
this.mergeLine(fullScreen, line);
}
}
@ -233,33 +252,118 @@ class WebShareModelClass {
this.mergeCmd(fullScreen, cmd);
}
}
if (msg.ptydata != null && msg.ptydata.length > 0) {
for (let data of msg.ptydata) {
let termWrap = this.getTermWrap(data.lineid);
if (termWrap == null) {
continue;
}
let dataArr = base64ToArray(data.data);
termWrap.receiveData(data.ptypos, dataArr, "ws:ptydata");
}
this.handleCmdPtyMap(msg.cmdptymap);
})();
}
handleCmdPtyMap(ptyMap : Record<string, number>) {
if (ptyMap == null) {
return;
}
for (let lineId in ptyMap) {
let newOffset = ptyMap[lineId];
this.remotePtyOffsetMap[lineId] = newOffset;
let localOffset = this.localPtyOffsetMap[lineId];
if (localOffset != null && localOffset < newOffset) {
this.runPtyFetch(lineId);
}
if (msg.removedlines != null && msg.removedlines.length > 0) {
for (let lineid of msg.removedlines) {
this.removeLine(fullScreen, lineid);
}
}
runPtyFetch(lineId : string) {
let prtn = this.checkFetchPtyData(lineId, false);
let ptyListener = this.getPtyListener(lineId);
if (ptyListener != null) {
prtn.then((ptydata) => {
ptyListener.receiveData(ptydata.pos, ptydata.data, "model-fetch");
if (ptydata.data.length > 0) {
setTimeout(() => this.checkFetchPtyData(lineId, false), 100);
}
});
}
}
getPtyListener(lineId : string) {
let termWrap = this.getTermWrap(lineId);
if (termWrap != null) {
return termWrap;
}
let renderer = this.getRenderer(lineId);
if (renderer != null) {
return renderer;
}
return null;
}
receivePtyData(lineId : string, ptyPos : number, data : Uint8Array, reason? : string) : void {
let termWrap = this.getTermWrap(lineId);
if (termWrap != null) {
termWrap.receiveData(ptyPos, data, reason);
}
let renderer = this.getRenderer(lineId);
if (renderer != null) {
renderer.receiveData(ptyPos, data, reason);
}
}
checkFetchPtyData(lineId : string, reload : boolean) : Promise<T.PtyDataType> {
let lineNum = this.getLineNumFromId(lineId);
if (this.activePtyFetch[lineId]) {
// console.log("check-fetch", lineNum, "already running");
return;
}
if (reload) {
this.localPtyOffsetMap[lineId] = 0;
}
let ptyOffset = this.localPtyOffsetMap[lineId];
if (ptyOffset == null) {
// console.log("check-fetch", lineNum, "no local offset");
return;
}
let remotePtyOffset = this.remotePtyOffsetMap[lineId];
if (ptyOffset >= remotePtyOffset) {
// up to date
return Promise.resolve({pos: ptyOffset, data: new Uint8Array(0)});
}
this.activePtyFetch[lineId] = true;
let viewKey = WebShareModel.viewKey;
// console.log("fetch pty", lineNum, "pos=" + ptyOffset);
let usp = new URLSearchParams({screenid: this.screenId, viewkey: viewKey, lineid: lineId, pos: String(ptyOffset)});
let url = new URL(getBaseUrl() + "/webshare/ptydata?" + usp.toString());
return fetch(url, {method: "GET", mode: "cors", cache: "no-cache"}).then((resp) => {
if (!resp.ok) {
throw new Error(sprintf("Bad fetch response for /webshare/ptydata: %d %s", resp.status, resp.statusText));
}
})();
let ptyOffsetStr = resp.headers.get("X-PtyDataOffset");
if (ptyOffsetStr != null && !isNaN(parseInt(ptyOffsetStr))) {
ptyOffset = parseInt(ptyOffsetStr);
}
return resp.arrayBuffer();
}).then((buf) => {
let dataArr = new Uint8Array(buf);
let newOffset = ptyOffset + dataArr.length;
console.log("fetch pty success", lineNum, "len=" + dataArr.length, "pos => " + newOffset);
this.localPtyOffsetMap[lineId] = newOffset;
return {pos: ptyOffset, data: dataArr};
}).finally(() => {
this.activePtyFetch[lineId] = false;
});
}
wsMessageCallback(msg : any) {
if (msg.type == "webscreen:update") {
this.mergeUpdate(msg);
console.log("[ws] update vts", msg.vts);
if (msg.vts > this.remoteScreenVts) {
this.remoteScreenVts = msg.vts;
setTimeout(() => this.checkUpdateScreenData(), 10);
}
return;
}
console.log("ws message", msg);
console.log("[ws] unhandled message", msg);
}
setWebFullScreen(screen : T.WebFullScreen) {
console.log("got initial screen", "vts=" + screen.vts);
mobx.action(() => {
if (screen.lines == null) {
screen.lines = [];
@ -267,7 +371,9 @@ class WebShareModelClass {
if (screen.cmds == null) {
screen.cmds = [];
}
this.screen.set(screen);
this.handleCmdPtyMap(screen.cmdptymap);
screen.cmdptymap = null;
this.fullScreen.set(screen);
this.wsControl.reconnect(true);
if (this.syncSelectedLine.get()) {
this.selectedLine.set(screen.screen.selectedline);
@ -303,6 +409,10 @@ class WebShareModelClass {
onUpdateContentHeight: (termContext : T.RendererContext, height : number) => { this.setContentHeight(termContext, height); },
});
this.terminals[lineId] = termWrap;
if (this.localPtyOffsetMap[lineId] == null) {
this.localPtyOffsetMap[lineId] = 0;
}
this.localPtyOffsetMap[lineId] = 0;
if (this.getSelectedLine() == line.linenum) {
termWrap.giveFocus();
}
@ -358,6 +468,7 @@ class WebShareModelClass {
term.dispose();
delete this.terminals[lineId];
}
delete this.localPtyOffsetMap[lineId];
}
getUsedRows(context : T.RendererContext, line : T.WebLine, cmd : T.WebCmd, width : number) : number {
@ -391,11 +502,26 @@ class WebShareModelClass {
return this.renderers[lineId];
}
registerRenderer(cmdId : string, renderer : T.RendererModel) {
this.renderers[cmdId] = renderer;
registerRenderer(lineId : string, renderer : T.RendererModel) {
this.renderers[lineId] = renderer;
if (this.localPtyOffsetMap[lineId] == null) {
this.localPtyOffsetMap[lineId] = 0;
}
}
loadFullScreenData() : void {
checkUpdateScreenData() : void {
let fullScreen = this.fullScreen.get();
if (fullScreen == null) {
return;
}
// console.log("check-update", "vts=" + fullScreen.vts, "remote-vts=" + this.remoteScreenVts);
if (fullScreen.vts >= this.remoteScreenVts) {
return;
}
this.loadFullScreenData(true);
}
loadFullScreenData(update : boolean) : void {
if (isBlank(this.screenId)) {
this.setErrMessage("No ScreenId Specified, Cannot Load.");
return;
@ -404,20 +530,53 @@ class WebShareModelClass {
this.setErrMessage("No ViewKey Specified, Cannot Load.");
return;
}
let usp = new URLSearchParams({screenid: this.screenId, viewkey: this.viewKey});
if (this.activeUpdateFetch) {
// console.log("there is already an active update fetch");
return;
}
// console.log("running screen-data update");
this.activeUpdateFetch = true;
let urlParams : Record<string, string> = {screenid: this.screenId, viewkey: this.viewKey};
if (update) {
let fullScreen = this.fullScreen.get();
if (fullScreen != null) {
urlParams.vts = String(fullScreen.vts);
}
}
let usp = new URLSearchParams(urlParams);
let url = new URL(getBaseUrl() + "/webshare/screen?" + usp.toString());
fetch(url, {method: "GET", mode: "cors", cache: "no-cache"}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
mobx.action(() => {
let screen : T.WebFullScreen = data;
let screen : T.WebFullScreen = data;
if (update) {
this.mergeUpdate(screen);
}
else {
this.setWebFullScreen(screen);
})();
}
setTimeout(() => this.checkUpdateScreenData(), 300);
}).catch((err) => {
this.errMessage.set("Cannot get screen: " + err.message);
}).finally(() => {
this.activeUpdateFetch = false;
});
}
getLineNumFromId(lineId : string) : number {
let fullScreen = this.fullScreen.get();
if (fullScreen == null) {
return -1;
}
for (let i=0; i<fullScreen.lines.length; i++) {
let line = fullScreen.lines[i];
if (line.lineid == lineId) {
return line.linenum;
}
}
return -1;
}
getLineIndex(lineNum : number) : number {
let fullScreen = this.screen.get();
let fullScreen = this.fullScreen.get();
if (fullScreen == null) {
return -1;
}
@ -431,7 +590,7 @@ class WebShareModelClass {
}
getNumLines() : number {
let fullScreen = this.screen.get();
let fullScreen = this.fullScreen.get();
if (fullScreen == null) {
return 0;
}
@ -439,7 +598,7 @@ class WebShareModelClass {
}
getCmdById(lineId : string) : T.WebCmd {
let fullScreen = this.screen.get();
let fullScreen = this.fullScreen.get();
if (fullScreen == null) {
return null;
}
@ -468,22 +627,7 @@ function getTermPtyData(termContext : T.TermContextUnion) : Promise<T.PtyDataTyp
if ("remoteId" in termContext) {
throw new Error("remote term ptydata is not supported in webshare");
}
let ptyOffset = 0;
let viewKey = WebShareModel.viewKey;
let usp = new URLSearchParams({screenid: termContext.screenId, viewkey: viewKey, lineid: termContext.lineId});
let url = new URL(getBaseUrl() + "/webshare/ptydata?" + usp.toString());
return fetch(url, {method: "GET", mode: "cors", cache: "no-cache"}).then((resp) => {
if (!resp.ok) {
throw new Error(sprintf("Bad fetch response for /webshare/ptydata: %d %s", resp.status, resp.statusText));
}
let ptyOffsetStr = resp.headers.get("X-PtyDataOffset");
if (ptyOffsetStr != null && !isNaN(parseInt(ptyOffsetStr))) {
ptyOffset = parseInt(ptyOffsetStr);
}
return resp.arrayBuffer();
}).then((buf) => {
return {pos: ptyOffset, data: new Uint8Array(buf)};
});
return WebShareModel.checkFetchPtyData(termContext.lineId, true);
}
let WebShareModel : WebShareModelClass = null;

View File

@ -23,7 +23,7 @@ class WebShareWSControl {
this.viewKey = viewKey;
this.open = mobx.observable.box(false, {name: "WSOpen"});
this.closed = true;
setInterval(this.sendPing, 15000);
setInterval(this.sendPing, 20000);
}
close() : void {