mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-30 05:19:57 +00:00
Add server sent events (#5279)
* Create server-sent-events.js * Create sse-change-listener.js * Implement server sent events * Convert to ES5 and wrap in function * Use the host string from tiddlyweb * Improve comments in sse-server.js * Can't use object reference as key * Add retry timeout * Fix a bug * bug fix * Fix formatting * Fix ES5 compat * capitalize comments * more fixes * Refactor tiddlywek/sse-server.js * Extract helper functions for handling wikis and connections. * Replace JSDoc comments. * Fix formatting according to TW core. * Simplify the logic for adding and removing connections. * Fix formatting of tiddlyweb/sse-client.js Fix formatting according to TW core. * Fix formatting of server-sent-events.js Fix formatting and comments following TW core guidelines. * Extract a debounce function in sse-client.js * Avoid using startsWith in server-sent-events.js startsWith is part of ES2015, while TiddlyWiki uses the 5.1 dialect. * New sse-enabled WebServer parameter * If not set to "yes", disabled SSE request handling. * Add documentation for the parameter in core/language/en-GB/Help/listen.tid * Add new tiddler editions/tw5.com/tiddlers/webserver/WebServer Parameter_ sse-enabled.tid * Disable polling for changes if SSE is enabled * Add sse_enabled to /status JSON response * Store syncer polling status in $:/config/SyncDisablePolling * Handled disabling polling in core/modules/syncer.js * Simply boolean logic in syncer.js * Delete trailing whitespaces in syncer.js Co-authored-by: Arlen22 <arlenbee@gmail.com>
This commit is contained in:
parent
ca95f1069f
commit
17b4f53ba2
@ -22,6 +22,7 @@ All parameters are optional with safe defaults, and can be specified in any orde
|
|||||||
* ''readers'' - comma separated list of principals allowed to read from this wiki
|
* ''readers'' - comma separated list of principals allowed to read from this wiki
|
||||||
* ''writers'' - comma separated list of principals allowed to write to this wiki
|
* ''writers'' - comma separated list of principals allowed to write to this wiki
|
||||||
* ''csrf-disable'' - set to "yes" to disable CSRF checks (defaults to "no")
|
* ''csrf-disable'' - set to "yes" to disable CSRF checks (defaults to "no")
|
||||||
|
* ''sse-enabled'' - set to "yes" to enable Server-sent events (defaults to "no")
|
||||||
* ''root-tiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
|
* ''root-tiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
|
||||||
* ''root-render-type'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
|
* ''root-render-type'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
|
||||||
* ''root-serve-type'' - the content type with which the root tiddler should be served (defaults to "text/html")
|
* ''root-serve-type'' - the content type with which the root tiddler should be served (defaults to "text/html")
|
||||||
|
@ -22,6 +22,7 @@ exports.handler = function(request,response,state) {
|
|||||||
username: state.authenticatedUsername || state.server.get("anon-username") || "",
|
username: state.authenticatedUsername || state.server.get("anon-username") || "",
|
||||||
anonymous: !state.authenticatedUsername,
|
anonymous: !state.authenticatedUsername,
|
||||||
read_only: !state.server.isAuthorized("writers",state.authenticatedUsername),
|
read_only: !state.server.isAuthorized("writers",state.authenticatedUsername),
|
||||||
|
sse_enabled: state.server.get("sse-enabled") === "yes",
|
||||||
space: {
|
space: {
|
||||||
recipe: "default"
|
recipe: "default"
|
||||||
},
|
},
|
||||||
|
70
core/modules/server/server-sent-events.js
Normal file
70
core/modules/server/server-sent-events.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*\
|
||||||
|
title: $:/core/modules/server/server-sent-events.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: library
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/*
|
||||||
|
parameters:
|
||||||
|
prefix - usually the plugin path, such as `plugins/tiddlywiki/tiddlyweb`. The
|
||||||
|
route will match `/events/${prefix}` exactly.
|
||||||
|
|
||||||
|
handler - a function that will be called each time a request comes in with the
|
||||||
|
request and state from the route and an emit function to call.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var ServerSentEvents = function ServerSentEvents(prefix, handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.prefix = prefix;
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerSentEvents.prototype.getExports = function() {
|
||||||
|
return {
|
||||||
|
bodyFormat: "stream",
|
||||||
|
method: "GET",
|
||||||
|
path: new RegExp("^/events/" + this.prefix + "$"),
|
||||||
|
handler: this.handleEventRequest.bind(this)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerSentEvents.prototype.handleEventRequest = function(request,response,state) {
|
||||||
|
if(ServerSentEvents.prototype.isEventStreamRequest(request)) {
|
||||||
|
response.writeHead(200, {
|
||||||
|
"Content-Type": "text/event-stream",
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"Connection": "keep-alive"
|
||||||
|
});
|
||||||
|
this.handler(request,state,this.emit.bind(this,response),this.end.bind(this,response));
|
||||||
|
} else {
|
||||||
|
response.writeHead(406,"Not Acceptable",{});
|
||||||
|
response.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerSentEvents.prototype.isEventStreamRequest = function(request) {
|
||||||
|
return request.headers.accept &&
|
||||||
|
request.headers.accept.match(/^text\/event-stream/);
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerSentEvents.prototype.emit = function(response,event,data) {
|
||||||
|
if(typeof event !== "string" || event.indexOf("\n") !== -1) {
|
||||||
|
throw new Error("Type must be a single-line string");
|
||||||
|
}
|
||||||
|
if(typeof data !== "string" || data.indexOf("\n") !== -1) {
|
||||||
|
throw new Error("Data must be a single-line string");
|
||||||
|
}
|
||||||
|
response.write("event: " + event + "\ndata: " + data + "\n\n", "utf8");
|
||||||
|
};
|
||||||
|
|
||||||
|
ServerSentEvents.prototype.end = function(response) {
|
||||||
|
response.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.ServerSentEvents = ServerSentEvents;
|
||||||
|
|
||||||
|
})();
|
@ -20,6 +20,7 @@ Syncer.prototype.titleIsAnonymous = "$:/status/IsAnonymous";
|
|||||||
Syncer.prototype.titleIsReadOnly = "$:/status/IsReadOnly";
|
Syncer.prototype.titleIsReadOnly = "$:/status/IsReadOnly";
|
||||||
Syncer.prototype.titleUserName = "$:/status/UserName";
|
Syncer.prototype.titleUserName = "$:/status/UserName";
|
||||||
Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter";
|
Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter";
|
||||||
|
Syncer.prototype.titleSyncDisablePolling = "$:/config/SyncDisablePolling";
|
||||||
Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval";
|
Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval";
|
||||||
Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading";
|
Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading";
|
||||||
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
|
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
|
||||||
@ -89,7 +90,7 @@ function Syncer(options) {
|
|||||||
if(filteredChanges.length > 0) {
|
if(filteredChanges.length > 0) {
|
||||||
self.processTaskQueue();
|
self.processTaskQueue();
|
||||||
} else {
|
} else {
|
||||||
// Look for deletions of tiddlers we're already syncing
|
// Look for deletions of tiddlers we're already syncing
|
||||||
var outstandingDeletion = false
|
var outstandingDeletion = false
|
||||||
$tw.utils.each(changes,function(change,title,object) {
|
$tw.utils.each(changes,function(change,title,object) {
|
||||||
if(change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) {
|
if(change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) {
|
||||||
@ -121,7 +122,7 @@ function Syncer(options) {
|
|||||||
self.login(username,password,function() {});
|
self.login(username,password,function() {});
|
||||||
} else {
|
} else {
|
||||||
// No username and password, so we display a prompt
|
// No username and password, so we display a prompt
|
||||||
self.handleLoginEvent();
|
self.handleLoginEvent();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$tw.rootWidget.addEventListener("tm-logout",function() {
|
$tw.rootWidget.addEventListener("tm-logout",function() {
|
||||||
@ -138,7 +139,7 @@ function Syncer(options) {
|
|||||||
if(!this.disableUI && this.wiki.getTiddlerText(this.titleSyncDisableLazyLoading) !== "yes") {
|
if(!this.disableUI && this.wiki.getTiddlerText(this.titleSyncDisableLazyLoading) !== "yes") {
|
||||||
this.wiki.addEventListener("lazyLoad",function(title) {
|
this.wiki.addEventListener("lazyLoad",function(title) {
|
||||||
self.handleLazyLoadEvent(title);
|
self.handleLazyLoadEvent(title);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Get the login status
|
// Get the login status
|
||||||
this.getStatus(function(err,isLoggedIn) {
|
this.getStatus(function(err,isLoggedIn) {
|
||||||
@ -173,8 +174,8 @@ Syncer.prototype.getTiddlerRevision = function(title) {
|
|||||||
if(this.syncadaptor && this.syncadaptor.getTiddlerRevision) {
|
if(this.syncadaptor && this.syncadaptor.getTiddlerRevision) {
|
||||||
return this.syncadaptor.getTiddlerRevision(title);
|
return this.syncadaptor.getTiddlerRevision(title);
|
||||||
} else {
|
} else {
|
||||||
return this.wiki.getTiddler(title).fields.revision;
|
return this.wiki.getTiddler(title).fields.revision;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -267,7 +268,7 @@ Syncer.prototype.getStatus = function(callback) {
|
|||||||
// Mark us as not logged in
|
// Mark us as not logged in
|
||||||
this.wiki.addTiddler({title: this.titleIsLoggedIn,text: "no"});
|
this.wiki.addTiddler({title: this.titleIsLoggedIn,text: "no"});
|
||||||
// Get login status
|
// Get login status
|
||||||
this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous) {
|
this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous,isPollingDisabled) {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.logger.alert(err);
|
self.logger.alert(err);
|
||||||
} else {
|
} else {
|
||||||
@ -278,6 +279,9 @@ Syncer.prototype.getStatus = function(callback) {
|
|||||||
if(isLoggedIn) {
|
if(isLoggedIn) {
|
||||||
self.wiki.addTiddler({title: self.titleUserName,text: username || ""});
|
self.wiki.addTiddler({title: self.titleUserName,text: username || ""});
|
||||||
}
|
}
|
||||||
|
if(isPollingDisabled) {
|
||||||
|
self.wiki.addTiddler({title: self.titleSyncDisablePolling, text: "yes"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Invoke the callback
|
// Invoke the callback
|
||||||
if(callback) {
|
if(callback) {
|
||||||
@ -301,12 +305,15 @@ Syncer.prototype.syncFromServer = function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
triggerNextSync = function() {
|
triggerNextSync = function() {
|
||||||
self.pollTimerId = setTimeout(function() {
|
if(pollingEnabled) {
|
||||||
self.pollTimerId = null;
|
self.pollTimerId = setTimeout(function() {
|
||||||
self.syncFromServer.call(self);
|
self.pollTimerId = null;
|
||||||
},self.pollTimerInterval);
|
self.syncFromServer.call(self);
|
||||||
|
},self.pollTimerInterval);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
syncSystemFromServer = (self.wiki.getTiddlerText("$:/config/SyncSystemTiddlersFromServer") === "yes" ? true : false);
|
syncSystemFromServer = (self.wiki.getTiddlerText("$:/config/SyncSystemTiddlersFromServer") === "yes"),
|
||||||
|
pollingEnabled = (self.wiki.getTiddlerText(self.titleSyncDisablePolling) !== "yes");
|
||||||
if(this.syncadaptor && this.syncadaptor.getUpdatedTiddlers) {
|
if(this.syncadaptor && this.syncadaptor.getUpdatedTiddlers) {
|
||||||
this.logger.log("Retrieving updated tiddler list");
|
this.logger.log("Retrieving updated tiddler list");
|
||||||
cancelNextSync();
|
cancelNextSync();
|
||||||
@ -329,7 +336,7 @@ Syncer.prototype.syncFromServer = function() {
|
|||||||
});
|
});
|
||||||
if(updates.modifications.length > 0 || updates.deletions.length > 0) {
|
if(updates.modifications.length > 0 || updates.deletions.length > 0) {
|
||||||
self.processTaskQueue();
|
self.processTaskQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
|
} else if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
|
||||||
@ -509,7 +516,7 @@ Syncer.prototype.processTaskQueue = function() {
|
|||||||
} else {
|
} else {
|
||||||
self.updateDirtyStatus();
|
self.updateDirtyStatus();
|
||||||
// Process the next task
|
// Process the next task
|
||||||
self.processTaskQueue.call(self);
|
self.processTaskQueue.call(self);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -517,11 +524,11 @@ Syncer.prototype.processTaskQueue = function() {
|
|||||||
this.updateDirtyStatus();
|
this.updateDirtyStatus();
|
||||||
// And trigger a timeout if there is a pending task
|
// And trigger a timeout if there is a pending task
|
||||||
if(task === true) {
|
if(task === true) {
|
||||||
this.triggerTimeout();
|
this.triggerTimeout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.updateDirtyStatus();
|
this.updateDirtyStatus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -555,7 +562,7 @@ Syncer.prototype.chooseNextTask = function() {
|
|||||||
isReadyToSave = !tiddlerInfo || !tiddlerInfo.timestampLastSaved || tiddlerInfo.timestampLastSaved < thresholdLastSaved;
|
isReadyToSave = !tiddlerInfo || !tiddlerInfo.timestampLastSaved || tiddlerInfo.timestampLastSaved < thresholdLastSaved;
|
||||||
if(hasChanged) {
|
if(hasChanged) {
|
||||||
if(isReadyToSave) {
|
if(isReadyToSave) {
|
||||||
return new SaveTiddlerTask(this,title);
|
return new SaveTiddlerTask(this,title);
|
||||||
} else {
|
} else {
|
||||||
havePending = true;
|
havePending = true;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
caption: sse-enabled
|
||||||
|
created: 20210113204602693
|
||||||
|
modified: 20210113205535065
|
||||||
|
tags: [[WebServer Parameters]]
|
||||||
|
title: WebServer Parameter: sse-enabled
|
||||||
|
type: text/vnd.tiddlywiki
|
||||||
|
|
||||||
|
The [[web server configuration parameter|WebServer Parameters]] ''sse-enabled'' enabled [[Server sent events|https://en.wikipedia.org/wiki/Server-sent_events]], allowing changes to be propagated in almost real time to all browser windows or tabs.
|
||||||
|
|
||||||
|
Setting ''sse-enabled'' to `yes` enables Server-sent events; `no`, or any other value, disables them.
|
||||||
|
|
53
plugins/tiddlywiki/tiddlyweb/sse-client.js
Normal file
53
plugins/tiddlywiki/tiddlyweb/sse-client.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*\
|
||||||
|
title: $:/plugins/tiddlywiki/tiddlyweb/sse-client.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: startup
|
||||||
|
|
||||||
|
GET /recipes/default/tiddlers/:title
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
exports.name = "/events/plugins/tiddlywiki/tiddlyweb";
|
||||||
|
exports.after = ["startup"];
|
||||||
|
exports.synchronous = true;
|
||||||
|
exports.platforms = ["browser"];
|
||||||
|
exports.startup = function() {
|
||||||
|
// Make sure we're actually being used
|
||||||
|
if($tw.syncadaptor.name !== "tiddlyweb") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Get the mount point in case a path prefix is used
|
||||||
|
var host = $tw.syncadaptor.getHost();
|
||||||
|
// Make sure it ends with a slash (it usually does)
|
||||||
|
if(host[host.length - 1] !== "/") {
|
||||||
|
host += "/";
|
||||||
|
}
|
||||||
|
// Setup the event listener
|
||||||
|
setupEvents(host);
|
||||||
|
};
|
||||||
|
|
||||||
|
function debounce(callback) {
|
||||||
|
var timeout = null;
|
||||||
|
return function() {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(callback,$tw.syncer.throttleInterval);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupEvents(host) {
|
||||||
|
var events = new EventSource(host + "events/plugins/tiddlywiki/tiddlyweb");
|
||||||
|
var debouncedSync = debounce($tw.syncer.syncFromServer.bind($tw.syncer));
|
||||||
|
events.addEventListener("change",debouncedSync);
|
||||||
|
events.onerror = function() {
|
||||||
|
events.close();
|
||||||
|
setTimeout(function() {
|
||||||
|
setupEvents(host);
|
||||||
|
},$tw.syncer.errorRetryInterval);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
94
plugins/tiddlywiki/tiddlyweb/sse-server.js
Normal file
94
plugins/tiddlywiki/tiddlyweb/sse-server.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*\
|
||||||
|
title: $:/plugins/tiddlywiki/tiddlyweb/sse-server.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: route
|
||||||
|
|
||||||
|
GET /events/plugins/tiddlywiki/tiddlyweb
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var wikis = [];
|
||||||
|
var connections = [];
|
||||||
|
|
||||||
|
/*
|
||||||
|
Setup up the array for this wiki and add the change listener
|
||||||
|
*/
|
||||||
|
function setupWiki(wiki) {
|
||||||
|
var index = wikis.length;
|
||||||
|
// Add a new array for this wiki (object references work as keys)
|
||||||
|
wikis.push(wiki);
|
||||||
|
connections.push([]);
|
||||||
|
// Listen to change events for this wiki
|
||||||
|
wiki.addEventListener("change",function(changes) {
|
||||||
|
var jsonChanges = JSON.stringify(changes);
|
||||||
|
getWikiConnections(wiki).forEach(function(item) {
|
||||||
|
item.emit("change",jsonChanges);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Setup this particular wiki if we haven't seen it before
|
||||||
|
*/
|
||||||
|
function ensureWikiSetup(wiki) {
|
||||||
|
if(wikis.indexOf(wiki) === -1) {
|
||||||
|
setupWiki(wiki);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return the array of connections for a particular wiki
|
||||||
|
*/
|
||||||
|
function getWikiConnections(wiki) {
|
||||||
|
return connections[wikis.indexOf(wiki)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addWikiConnection(wiki,connection) {
|
||||||
|
getWikiConnections(wiki).push(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeWikiConnection(wiki,connection) {
|
||||||
|
var wikiConnections = getWikiConnections(wiki);
|
||||||
|
var index = wikiConnections.indexOf(connection);
|
||||||
|
if(index !== -1) {
|
||||||
|
wikiConnections.splice(index,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConnection(request,state,emit,end) {
|
||||||
|
if(isDisabled(state)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureWikiSetup(state.wiki);
|
||||||
|
// Add the connection to the list of connections for this wiki
|
||||||
|
var connection = {
|
||||||
|
request: request,
|
||||||
|
state: state,
|
||||||
|
emit: emit,
|
||||||
|
end: end
|
||||||
|
};
|
||||||
|
addWikiConnection(state.wiki,connection);
|
||||||
|
request.on("close",function() {
|
||||||
|
removeWikiConnection(state.wiki,connection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDisabled(state) {
|
||||||
|
return state.server.get("sse-enabled") !== "yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import the ServerSentEvents class
|
||||||
|
var ServerSentEvents = require("$:/core/modules/server/server-sent-events.js").ServerSentEvents;
|
||||||
|
// Instantiate the class
|
||||||
|
var events = new ServerSentEvents("plugins/tiddlywiki/tiddlyweb", handleConnection);
|
||||||
|
// Export the route definition for this server sent events instance
|
||||||
|
module.exports = events.getExports();
|
||||||
|
|
||||||
|
})();
|
@ -91,10 +91,12 @@ TiddlyWebAdaptor.prototype.getStatus = function(callback) {
|
|||||||
self.isLoggedIn = json.username !== "GUEST";
|
self.isLoggedIn = json.username !== "GUEST";
|
||||||
self.isReadOnly = !!json["read_only"];
|
self.isReadOnly = !!json["read_only"];
|
||||||
self.isAnonymous = !!json.anonymous;
|
self.isAnonymous = !!json.anonymous;
|
||||||
|
|
||||||
|
var isSseEnabled = !!json.sse_enabled;
|
||||||
}
|
}
|
||||||
// Invoke the callback if present
|
// Invoke the callback if present
|
||||||
if(callback) {
|
if(callback) {
|
||||||
callback(null,self.isLoggedIn,json.username,self.isReadOnly,self.isAnonymous);
|
callback(null,self.isLoggedIn,json.username,self.isReadOnly,self.isAnonymous,isSseEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user