forked from cory/tildefriends
doc: add JSDoc annotations in the core folder
start documenting a bit, mostly inconsequential changes
This commit is contained in:
parent
4f869252a2
commit
ce5ca1875b
29
core/app.js
29
core/app.js
@ -6,20 +6,37 @@ let g_calls = {};
|
|||||||
|
|
||||||
let gSessionIndex = 0;
|
let gSessionIndex = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function makeSessionId() {
|
function makeSessionId() {
|
||||||
return (gSessionIndex++).toString();
|
return (gSessionIndex++).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function App() {
|
function App() {
|
||||||
this._on_output = null;
|
this._on_output = null;
|
||||||
this._send_queue = [];
|
this._send_queue = [];
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} callback
|
||||||
|
*/
|
||||||
App.prototype.readOutput = function(callback) {
|
App.prototype.readOutput = function(callback) {
|
||||||
this._on_output = callback;
|
this._on_output = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} api
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
App.prototype.makeFunction = function(api) {
|
App.prototype.makeFunction = function(api) {
|
||||||
let self = this;
|
let self = this;
|
||||||
let result = function() {
|
let result = function() {
|
||||||
@ -43,6 +60,10 @@ App.prototype.makeFunction = function(api) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} message
|
||||||
|
*/
|
||||||
App.prototype.send = function(message) {
|
App.prototype.send = function(message) {
|
||||||
if (this._send_queue) {
|
if (this._send_queue) {
|
||||||
if (this._on_output) {
|
if (this._on_output) {
|
||||||
@ -57,11 +78,17 @@ App.prototype.send = function(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} request
|
||||||
|
* @param {*} response
|
||||||
|
* @param {*} client
|
||||||
|
*/
|
||||||
function socket(request, response, client) {
|
function socket(request, response, client) {
|
||||||
let process;
|
let process;
|
||||||
let options = {};
|
let options = {};
|
||||||
let credentials = auth.query(request.headers);
|
let credentials = auth.query(request.headers);
|
||||||
let refresh = auth.make_refresh(credentials);
|
let refresh = auth.makeRefresh(credentials);
|
||||||
|
|
||||||
response.onClose = async function() {
|
response.onClose = async function() {
|
||||||
if (process && process.task) {
|
if (process && process.task) {
|
||||||
|
128
core/auth.js
128
core/auth.js
@ -5,9 +5,15 @@ let gDatabase = new Database("auth");
|
|||||||
|
|
||||||
const kRefreshInterval = 1 * 7 * 24 * 60 * 60 * 1000;
|
const kRefreshInterval = 1 * 7 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a Base64 value URL safe
|
||||||
|
* @param {string} value
|
||||||
|
* @returns TODOC
|
||||||
|
*/
|
||||||
function b64url(value) {
|
function b64url(value) {
|
||||||
value = value.replaceAll('+', '-').replaceAll('/', '_');
|
value = value.replaceAll('+', '-').replaceAll('/', '_');
|
||||||
let equals = value.indexOf('=');
|
let equals = value.indexOf('=');
|
||||||
|
|
||||||
if (equals !== -1) {
|
if (equals !== -1) {
|
||||||
return value.substring(0, equals);
|
return value.substring(0, equals);
|
||||||
} else {
|
} else {
|
||||||
@ -15,9 +21,15 @@ function b64url(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {string} value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function unb64url(value) {
|
function unb64url(value) {
|
||||||
value = value.replaceAll('-', '+').replaceAll('_', '/');
|
value = value.replaceAll('-', '+').replaceAll('_', '/');
|
||||||
let remainder = value.length % 4;
|
let remainder = value.length % 4;
|
||||||
|
|
||||||
if (remainder == 3) {
|
if (remainder == 3) {
|
||||||
return value + '=';
|
return value + '=';
|
||||||
} else if (remainder == 2) {
|
} else if (remainder == 2) {
|
||||||
@ -27,31 +39,68 @@ function unb64url(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a JSON Web Token
|
||||||
|
* @param {object} payload Object: {"name": "username"}
|
||||||
|
* @returns the JWT
|
||||||
|
*/
|
||||||
function makeJwt(payload) {
|
function makeJwt(payload) {
|
||||||
let ids = ssb.getIdentities(':auth');
|
const ids = ssb.getIdentities(':auth');
|
||||||
let id;
|
let id;
|
||||||
|
|
||||||
if (ids?.length) {
|
if (ids?.length) {
|
||||||
id = ids[0];
|
id = ids[0];
|
||||||
} else {
|
} else {
|
||||||
id = ssb.createIdentity(':auth');
|
id = ssb.createIdentity(':auth');
|
||||||
}
|
}
|
||||||
|
|
||||||
let final_payload = b64url(base64Encode(JSON.stringify(Object.assign({}, payload, {exp: (new Date().valueOf()) + kRefreshInterval}))));
|
const final_payload = b64url(
|
||||||
let jwt = [b64url(base64Encode(JSON.stringify({alg: 'HS256', typ: 'JWT'}))), final_payload, b64url(ssb.hmacsha256sign(final_payload, ':auth', id))].join('.');
|
base64Encode(
|
||||||
|
JSON.stringify(
|
||||||
|
Object.assign({}, payload, {exp: (new Date().valueOf()) + kRefreshInterval}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const jwt = [
|
||||||
|
b64url(
|
||||||
|
base64Encode(
|
||||||
|
JSON.stringify({
|
||||||
|
alg: 'HS256',
|
||||||
|
typ: 'JWT'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
final_payload,
|
||||||
|
b64url(
|
||||||
|
ssb.hmacsha256sign(final_payload, ':auth', id)
|
||||||
|
)
|
||||||
|
].join('.');
|
||||||
|
|
||||||
return jwt;
|
return jwt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a JWT ?
|
||||||
|
* @param {*} session TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function readSession(session) {
|
function readSession(session) {
|
||||||
let jwt_parts = session?.split('.');
|
let jwt_parts = session?.split('.');
|
||||||
|
|
||||||
if (jwt_parts?.length === 3) {
|
if (jwt_parts?.length === 3) {
|
||||||
let [header, payload, signature] = jwt_parts;
|
let [header, payload, signature] = jwt_parts;
|
||||||
header = JSON.parse(utf8Decode(base64Decode(unb64url(header))));
|
header = JSON.parse(utf8Decode(base64Decode(unb64url(header))));
|
||||||
|
|
||||||
if (header.typ === 'JWT' && header.alg === 'HS256') {
|
if (header.typ === 'JWT' && header.alg === 'HS256') {
|
||||||
signature = unb64url(signature);
|
signature = unb64url(signature);
|
||||||
let id = ssb.getIdentities(':auth');
|
let id = ssb.getIdentities(':auth');
|
||||||
|
|
||||||
if (id?.length && ssb.hmacsha256verify(id[0], payload, signature)) {
|
if (id?.length && ssb.hmacsha256verify(id[0], payload, signature)) {
|
||||||
let result = JSON.parse(utf8Decode(base64Decode(unb64url(payload))));
|
const result = JSON.parse(utf8Decode(base64Decode(unb64url(payload))));
|
||||||
let now = new Date().valueOf()
|
const now = new Date().valueOf()
|
||||||
|
|
||||||
if (now < result.exp) {
|
if (now < result.exp) {
|
||||||
print(`JWT valid for another ${(result.exp - now) / 1000} seconds.`);
|
print(`JWT valid for another ${(result.exp - now) / 1000} seconds.`);
|
||||||
return result;
|
return result;
|
||||||
@ -67,21 +116,42 @@ function readSession(session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the provided password matches the hash
|
||||||
|
* @param {string} password
|
||||||
|
* @param {string} hash bcrypt hash
|
||||||
|
* @returns true if the password matches the hash
|
||||||
|
*/
|
||||||
function verifyPassword(password, hash) {
|
function verifyPassword(password, hash) {
|
||||||
return bCrypt.hashpw(password, hash) == hash;
|
return bCrypt.hashpw(password, hash) === hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hashes a password
|
||||||
|
* @param {string} password
|
||||||
|
* @returns {string} TODOC
|
||||||
|
*/
|
||||||
function hashPassword(password) {
|
function hashPassword(password) {
|
||||||
let salt = bCrypt.gensalt(12);
|
let salt = bCrypt.gensalt(12);
|
||||||
return bCrypt.hashpw(password, salt);
|
return bCrypt.hashpw(password, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is an administrator on the instance
|
||||||
|
* @returns TODOC
|
||||||
|
*/
|
||||||
function noAdministrator() {
|
function noAdministrator() {
|
||||||
return !core.globalSettings || !core.globalSettings.permissions || !Object.keys(core.globalSettings.permissions).some(function(name) {
|
return !core.globalSettings ||
|
||||||
|
!core.globalSettings.permissions ||
|
||||||
|
!Object.keys(core.globalSettings.permissions).some(function(name) {
|
||||||
return core.globalSettings.permissions[name].indexOf("administration") != -1;
|
return core.globalSettings.permissions[name].indexOf("administration") != -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a user an administrator
|
||||||
|
* @param {string} name the user's name
|
||||||
|
*/
|
||||||
function makeAdministrator(name) {
|
function makeAdministrator(name) {
|
||||||
if (!core.globalSettings.permissions) {
|
if (!core.globalSettings.permissions) {
|
||||||
core.globalSettings.permissions = {};
|
core.globalSettings.permissions = {};
|
||||||
@ -92,9 +162,15 @@ function makeAdministrator(name) {
|
|||||||
if (core.globalSettings.permissions[name].indexOf("administration") == -1) {
|
if (core.globalSettings.permissions[name].indexOf("administration") == -1) {
|
||||||
core.globalSettings.permissions[name].push("administration");
|
core.globalSettings.permissions[name].push("administration");
|
||||||
}
|
}
|
||||||
|
|
||||||
core.setGlobalSettings(core.globalSettings);
|
core.setGlobalSettings(core.globalSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} headers most likely an object
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function getCookies(headers) {
|
function getCookies(headers) {
|
||||||
let cookies = {};
|
let cookies = {};
|
||||||
|
|
||||||
@ -111,13 +187,27 @@ function getCookies(headers) {
|
|||||||
return cookies;
|
return cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a username
|
||||||
|
* @param {string} name
|
||||||
|
* @returns false | boolean[] ?
|
||||||
|
*/
|
||||||
function isNameValid(name) {
|
function isNameValid(name) {
|
||||||
|
// TODO(tasiaiso): convert this into a regex
|
||||||
let c = name.charAt(0);
|
let c = name.charAt(0);
|
||||||
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) && name.split().map(x => x >= ('a' && x <= 'z') || x >= ('A' && x <= 'Z') || x >= ('0' && x <= '9'));
|
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) && name.split().map(x => x >= ('a' && x <= 'z') || x >= ('A' && x <= 'Z') || x >= ('0' && x <= '9'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request handler ?
|
||||||
|
* @param {*} request TODOC
|
||||||
|
* @param {*} response
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function handler(request, response) {
|
function handler(request, response) {
|
||||||
|
// TODO(tasiaiso): split this function
|
||||||
let session = getCookies(request.headers).session;
|
let session = getCookies(request.headers).session;
|
||||||
|
|
||||||
if (request.uri == "/login") {
|
if (request.uri == "/login") {
|
||||||
let formData = form.decodeForm(request.query);
|
let formData = form.decodeForm(request.query);
|
||||||
if (query(request.headers)?.permissions?.authenticated) {
|
if (query(request.headers)?.permissions?.authenticated) {
|
||||||
@ -226,6 +316,11 @@ function handler(request, response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a user's permissions based on it's session ?
|
||||||
|
* @param {*} session TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function getPermissions(session) {
|
function getPermissions(session) {
|
||||||
let permissions;
|
let permissions;
|
||||||
let entry = readSession(session);
|
let entry = readSession(session);
|
||||||
@ -236,6 +331,11 @@ function getPermissions(session) {
|
|||||||
return permissions || {};
|
return permissions || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user's permissions ?
|
||||||
|
* @param {string} userName TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function getPermissionsForUser(userName) {
|
function getPermissionsForUser(userName) {
|
||||||
let permissions = {};
|
let permissions = {};
|
||||||
if (core.globalSettings && core.globalSettings.permissions && core.globalSettings.permissions[userName]) {
|
if (core.globalSettings && core.globalSettings.permissions && core.globalSettings.permissions[userName]) {
|
||||||
@ -246,6 +346,11 @@ function getPermissionsForUser(userName) {
|
|||||||
return permissions;
|
return permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} headers
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function query(headers) {
|
function query(headers) {
|
||||||
let session = getCookies(headers).session;
|
let session = getCookies(headers).session;
|
||||||
let entry;
|
let entry;
|
||||||
@ -258,7 +363,12 @@ function query(headers) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function make_refresh(credentials) {
|
/**
|
||||||
|
* Refreshes a JWT ?
|
||||||
|
* @param {*} credentials TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function makeRefresh(credentials) {
|
||||||
if (credentials?.session?.name) {
|
if (credentials?.session?.name) {
|
||||||
return {
|
return {
|
||||||
token: makeJwt({name: credentials.session.name}),
|
token: makeJwt({name: credentials.session.name}),
|
||||||
@ -267,4 +377,4 @@ function make_refresh(credentials) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { handler, query, make_refresh };
|
export { handler, query, makeRefresh };
|
||||||
|
269
core/client.js
269
core/client.js
@ -12,7 +12,7 @@ let gOriginalInput;
|
|||||||
let kErrorColor = "#dc322f";
|
let kErrorColor = "#dc322f";
|
||||||
let kStatusColor = "#fff";
|
let kStatusColor = "#fff";
|
||||||
|
|
||||||
/* Functions that server-side app code can call through the app object. */
|
// Functions that server-side app code can call through the app object.
|
||||||
const k_api = {
|
const k_api = {
|
||||||
setDocument: {args: ['content'], func: api_setDocument},
|
setDocument: {args: ['content'], func: api_setDocument},
|
||||||
postMessage: {args: ['message'], func: api_postMessage},
|
postMessage: {args: ['message'], func: api_postMessage},
|
||||||
@ -24,6 +24,7 @@ const k_api = {
|
|||||||
setHash: {args: ['hash'], func: api_setHash},
|
setHash: {args: ['hash'], func: api_setHash},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO(tasiaiso): this is only used once, move it down ?
|
||||||
const k_global_style = css`
|
const k_global_style = css`
|
||||||
a:link {
|
a:link {
|
||||||
color: #268bd2;
|
color: #268bd2;
|
||||||
@ -42,6 +43,9 @@ const k_global_style = css`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that represents the top bar
|
||||||
|
*/
|
||||||
class TfNavigationElement extends LitElement {
|
class TfNavigationElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
@ -63,6 +67,10 @@ class TfNavigationElement extends LitElement {
|
|||||||
this.spark_lines = {};
|
this.spark_lines = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
toggle_edit(event) {
|
toggle_edit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (editing()) {
|
if (editing()) {
|
||||||
@ -72,10 +80,20 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} key
|
||||||
|
*/
|
||||||
reset_permission(key) {
|
reset_permission(key) {
|
||||||
send({action: "resetPermission", permission: key});
|
send({action: "resetPermission", permission: key});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} key
|
||||||
|
* @param {*} options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
get_spark_line(key, options) {
|
get_spark_line(key, options) {
|
||||||
if (!this.spark_lines[key]) {
|
if (!this.spark_lines[key]) {
|
||||||
let spark_line = document.createElement('tf-sparkline');
|
let spark_line = document.createElement('tf-sparkline');
|
||||||
@ -94,6 +112,10 @@ class TfNavigationElement extends LitElement {
|
|||||||
return this.spark_lines[key];
|
return this.spark_lines[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
render_login() {
|
render_login() {
|
||||||
if (this?.credentials?.session?.name) {
|
if (this?.credentials?.session?.name) {
|
||||||
return html`<a id="login" href="/login/logout?return=${url() + hash()}">logout ${this.credentials.session.name}</a>`;
|
return html`<a id="login" href="/login/logout?return=${url() + hash()}">logout ${this.credentials.session.name}</a>`;
|
||||||
@ -102,6 +124,10 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
render_permissions() {
|
render_permissions() {
|
||||||
if (this.show_permissions) {
|
if (this.show_permissions) {
|
||||||
return html`
|
return html`
|
||||||
@ -122,6 +148,10 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
return html`
|
return html`
|
||||||
@ -157,8 +187,12 @@ class TfNavigationElement extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('tf-navigation', TfNavigationElement);
|
customElements.define('tf-navigation', TfNavigationElement);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
class TfFilesElement extends LitElement {
|
class TfFilesElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
@ -174,6 +208,10 @@ class TfFilesElement extends LitElement {
|
|||||||
this.dropping = 0;
|
this.dropping = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} file
|
||||||
|
*/
|
||||||
file_click(file) {
|
file_click(file) {
|
||||||
this.dispatchEvent(new CustomEvent('file_click', {
|
this.dispatchEvent(new CustomEvent('file_click', {
|
||||||
detail: {
|
detail: {
|
||||||
@ -184,6 +222,11 @@ class TfFilesElement extends LitElement {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} file
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
render_file(file) {
|
render_file(file) {
|
||||||
let classes = ['file'];
|
let classes = ['file'];
|
||||||
if (file == this.current) {
|
if (file == this.current) {
|
||||||
@ -195,6 +238,10 @@ class TfFilesElement extends LitElement {
|
|||||||
return html`<div class="${classes.join(' ')}" @click=${x => this.file_click(file)}>${file}</div>`;
|
return html`<div class="${classes.join(' ')}" @click=${x => this.file_click(file)}>${file}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
async drop(event) {
|
async drop(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -213,15 +260,27 @@ class TfFilesElement extends LitElement {
|
|||||||
updateFiles();
|
updateFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
drag_enter(event) {
|
drag_enter(event) {
|
||||||
this.dropping++;
|
this.dropping++;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
drag_leave(event) {
|
drag_leave(event) {
|
||||||
this.dropping--;
|
this.dropping--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
return html`
|
return html`
|
||||||
@ -258,8 +317,12 @@ class TfFilesElement extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('tf-files', TfFilesElement);
|
customElements.define('tf-files', TfFilesElement);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
class TfFilesPaneElement extends LitElement {
|
class TfFilesPaneElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
@ -275,11 +338,19 @@ class TfFilesPaneElement extends LitElement {
|
|||||||
this.files = {};
|
this.files = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} expanded
|
||||||
|
*/
|
||||||
set_expanded(expanded) {
|
set_expanded(expanded) {
|
||||||
this.expanded = expanded;
|
this.expanded = expanded;
|
||||||
window.localStorage.setItem('files', expanded ? '1' : '0');
|
window.localStorage.setItem('files', expanded ? '1' : '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
let expander = this.expanded ?
|
let expander = this.expanded ?
|
||||||
@ -302,8 +373,12 @@ class TfFilesPaneElement extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('tf-files-pane', TfFilesPaneElement);
|
customElements.define('tf-files-pane', TfFilesPaneElement);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
class TfSparkLineElement extends LitElement {
|
class TfSparkLineElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
@ -321,6 +396,11 @@ class TfSparkLineElement extends LitElement {
|
|||||||
this.k_values_max = 100;
|
this.k_values_max = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} key
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
append(key, value) {
|
append(key, value) {
|
||||||
let line = null;
|
let line = null;
|
||||||
for (let it of this.lines) {
|
for (let it of this.lines) {
|
||||||
@ -345,6 +425,11 @@ class TfSparkLineElement extends LitElement {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} line
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
render_line(line) {
|
render_line(line) {
|
||||||
if (line?.values?.length >= 2) {
|
if (line?.values?.length >= 2) {
|
||||||
let max = Math.max(this.max, ...line.values);
|
let max = Math.max(this.max, ...line.values);
|
||||||
@ -353,6 +438,10 @@ class TfSparkLineElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
render() {
|
render() {
|
||||||
let max = Math.round(10.0 * Math.max(...this.lines.map(line => line.values[line.values.length - 1]))) / 10.0;
|
let max = Math.round(10.0 * Math.max(...this.lines.map(line => line.values[line.values.length - 1]))) / 10.0;
|
||||||
return html`
|
return html`
|
||||||
@ -363,8 +452,10 @@ class TfSparkLineElement extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('tf-sparkline', TfSparkLineElement);
|
customElements.define('tf-sparkline', TfSparkLineElement);
|
||||||
|
|
||||||
|
// TODOC
|
||||||
window.addEventListener("keydown", function(event) {
|
window.addEventListener("keydown", function(event) {
|
||||||
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
||||||
if (editing()) {
|
if (editing()) {
|
||||||
@ -379,6 +470,12 @@ window.addEventListener("keydown", function(event) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} nodes
|
||||||
|
* @param {*} callback
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function ensureLoaded(nodes, callback) {
|
function ensureLoaded(nodes, callback) {
|
||||||
if (!nodes.length) {
|
if (!nodes.length) {
|
||||||
callback();
|
callback();
|
||||||
@ -416,14 +513,26 @@ function ensureLoaded(nodes, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function editing() {
|
function editing() {
|
||||||
return document.getElementById("editPane").style.display != 'none';
|
return document.getElementById("editPane").style.display != 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function is_edit_only() {
|
function is_edit_only() {
|
||||||
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async function edit() {
|
async function edit() {
|
||||||
if (editing()) {
|
if (editing()) {
|
||||||
return;
|
return;
|
||||||
@ -446,16 +555,30 @@ async function edit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function trace() {
|
function trace() {
|
||||||
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function guessMode(name) {
|
function guessMode(name) {
|
||||||
return name.endsWith(".js") ? "javascript" :
|
return name.endsWith(".js") ? "javascript" :
|
||||||
name.endsWith(".html") ? "htmlmixed" :
|
name.endsWith(".html") ? "htmlmixed" :
|
||||||
null;
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} name
|
||||||
|
* @param {*} id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function loadFile(name, id) {
|
function loadFile(name, id) {
|
||||||
return fetch('/' + id + '/view').then(function(response) {
|
return fetch('/' + id + '/view').then(function(response) {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -472,6 +595,11 @@ function loadFile(name, id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} path
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async function load(path) {
|
async function load(path) {
|
||||||
let response = await fetch((path || url()) + 'view');
|
let response = await fetch((path || url()) + 'view');
|
||||||
let json;
|
let json;
|
||||||
@ -509,16 +637,28 @@ async function load(path) {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function closeEditor() {
|
function closeEditor() {
|
||||||
window.localStorage.setItem('editing', '0');
|
window.localStorage.setItem('editing', '0');
|
||||||
document.getElementById("editPane").style.display = 'none';
|
document.getElementById("editPane").style.display = 'none';
|
||||||
document.getElementById('viewPane').style.display = 'flex';
|
document.getElementById('viewPane').style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function explodePath() {
|
function explodePath() {
|
||||||
return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname);
|
return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} save_to
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function save(save_to) {
|
function save(save_to) {
|
||||||
document.getElementById("save").disabled = true;
|
document.getElementById("save").disabled = true;
|
||||||
if (gCurrentFile) {
|
if (gCurrentFile) {
|
||||||
@ -603,6 +743,9 @@ function save(save_to) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function changeIcon() {
|
function changeIcon() {
|
||||||
let value = prompt('Enter a new app icon emoji:');
|
let value = prompt('Enter a new app icon emoji:');
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
@ -611,6 +754,9 @@ function changeIcon() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function deleteApp() {
|
function deleteApp() {
|
||||||
let name = document.getElementById("name");
|
let name = document.getElementById("name");
|
||||||
let path = name && name.value ? name.value : url();
|
let path = name && name.value ? name.value : url();
|
||||||
@ -627,6 +773,10 @@ function deleteApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function url() {
|
function url() {
|
||||||
let hash = window.location.href.indexOf('#');
|
let hash = window.location.href.indexOf('#');
|
||||||
let question = window.location.href.indexOf('?');
|
let question = window.location.href.indexOf('?');
|
||||||
@ -642,20 +792,36 @@ function url() {
|
|||||||
return end != -1 ? window.location.href.substring(0, end) : window.location.href;
|
return end != -1 ? window.location.href.substring(0, end) : window.location.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function hash() {
|
function hash() {
|
||||||
return window.location.hash != "#" ? window.location.hash : "";
|
return window.location.hash != "#" ? window.location.hash : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} content
|
||||||
|
*/
|
||||||
function api_setDocument(content) {
|
function api_setDocument(content) {
|
||||||
let iframe = document.getElementById("document");
|
let iframe = document.getElementById("document");
|
||||||
iframe.srcdoc = content;
|
iframe.srcdoc = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} message
|
||||||
|
*/
|
||||||
function api_postMessage(message) {
|
function api_postMessage(message) {
|
||||||
let iframe = document.getElementById("document");
|
let iframe = document.getElementById("document");
|
||||||
iframe.contentWindow.postMessage(message, "*");
|
iframe.contentWindow.postMessage(message, "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} error
|
||||||
|
*/
|
||||||
function api_error(error) {
|
function api_error(error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
if (typeof(error) == 'string') {
|
if (typeof(error) == 'string') {
|
||||||
@ -667,14 +833,30 @@ function api_error(error) {
|
|||||||
console.log('error', error);
|
console.log('error', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} key
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
function api_localStorageSet(key, value) {
|
function api_localStorageSet(key, value) {
|
||||||
window.localStorage.setItem('app:' + key, value);
|
window.localStorage.setItem('app:' + key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function api_localStorageGet(key) {
|
function api_localStorageGet(key) {
|
||||||
return window.localStorage.getItem('app:' + key);
|
return window.localStorage.getItem('app:' + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} permission
|
||||||
|
* @param {*} id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function api_requestPermission(permission, id) {
|
function api_requestPermission(permission, id) {
|
||||||
let outer = document.createElement('div');
|
let outer = document.createElement('div');
|
||||||
outer.classList.add('permissions');
|
outer.classList.add('permissions');
|
||||||
@ -739,14 +921,25 @@ function api_requestPermission(permission, id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function api_print() {
|
function api_print() {
|
||||||
console.log('app>', ...arguments);
|
console.log('app>', ...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} hash
|
||||||
|
*/
|
||||||
function api_setHash(hash) {
|
function api_setHash(hash) {
|
||||||
window.location.hash = hash;
|
window.location.hash = hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} message
|
||||||
|
*/
|
||||||
function _receive_websocket_message(message) {
|
function _receive_websocket_message(message) {
|
||||||
if (message && message.action == "session") {
|
if (message && message.action == "session") {
|
||||||
setStatusMessage("🟢 Executing...", kStatusColor);
|
setStatusMessage("🟢 Executing...", kStatusColor);
|
||||||
@ -827,10 +1020,19 @@ function _receive_websocket_message(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} message
|
||||||
|
* @param {*} color
|
||||||
|
*/
|
||||||
function setStatusMessage(message, color) {
|
function setStatusMessage(message, color) {
|
||||||
document.getElementsByTagName('tf-navigation')[0].status = {message: message, color: color};
|
document.getElementsByTagName('tf-navigation')[0].status = {message: message, color: color};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
function send(value) {
|
function send(value) {
|
||||||
try {
|
try {
|
||||||
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
||||||
@ -841,6 +1043,13 @@ function send(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} sourceData
|
||||||
|
* @param {*} maxWidth
|
||||||
|
* @param {*} maxHeight
|
||||||
|
* @param {*} callback
|
||||||
|
*/
|
||||||
function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
||||||
let result = sourceData;
|
let result = sourceData;
|
||||||
let image = new Image();
|
let image = new Image();
|
||||||
@ -864,16 +1073,26 @@ function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
|||||||
image.src = sourceData;
|
image.src = sourceData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} image
|
||||||
|
*/
|
||||||
function sendImage(image) {
|
function sendImage(image) {
|
||||||
fixImage(image, 320, 240, function(result) {
|
fixImage(image, 320, 240, function(result) {
|
||||||
send({image: result});
|
send({image: result});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function hashChange() {
|
function hashChange() {
|
||||||
send({event: 'hashChange', hash: window.location.hash});
|
send({event: 'hashChange', hash: window.location.hash});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function focus() {
|
function focus() {
|
||||||
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
||||||
connectSocket();
|
connectSocket();
|
||||||
@ -882,12 +1101,19 @@ function focus() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function blur() {
|
function blur() {
|
||||||
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
||||||
send({event: "blur"});
|
send({event: "blur"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
function message(event) {
|
function message(event) {
|
||||||
if (event.data && event.data.event == "resizeMe" && event.data.width && event.data.height) {
|
if (event.data && event.data.event == "resizeMe" && event.data.width && event.data.height) {
|
||||||
let iframe = document.getElementById("iframe_" + event.data.name);
|
let iframe = document.getElementById("iframe_" + event.data.name);
|
||||||
@ -916,6 +1142,10 @@ function message(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} path
|
||||||
|
*/
|
||||||
function reconnect(path) {
|
function reconnect(path) {
|
||||||
let oldSocket = gSocket;
|
let oldSocket = gSocket;
|
||||||
gSocket = null
|
gSocket = null
|
||||||
@ -928,6 +1158,10 @@ function reconnect(path) {
|
|||||||
connectSocket(path);
|
connectSocket(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} path
|
||||||
|
*/
|
||||||
function connectSocket(path) {
|
function connectSocket(path) {
|
||||||
if (!gSocket || gSocket.readyState != gSocket.OPEN) {
|
if (!gSocket || gSocket.readyState != gSocket.OPEN) {
|
||||||
if (gSocket) {
|
if (gSocket) {
|
||||||
@ -979,6 +1213,10 @@ function connectSocket(path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} name
|
||||||
|
*/
|
||||||
function openFile(name) {
|
function openFile(name) {
|
||||||
let newDoc = (name && gFiles[name]) ? gFiles[name].doc : cm6.EditorState.create({doc: "", extensions: cm6.extensions});
|
let newDoc = (name && gFiles[name]) ? gFiles[name].doc : cm6.EditorState.create({doc: "", extensions: cm6.extensions});
|
||||||
let oldDoc = gEditor.state;
|
let oldDoc = gEditor.state;
|
||||||
@ -995,6 +1233,9 @@ function openFile(name) {
|
|||||||
gEditor.focus();
|
gEditor.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function updateFiles() {
|
function updateFiles() {
|
||||||
let files = document.getElementsByTagName("tf-files-pane")[0];
|
let files = document.getElementsByTagName("tf-files-pane")[0];
|
||||||
if (files) {
|
if (files) {
|
||||||
@ -1006,6 +1247,10 @@ function updateFiles() {
|
|||||||
gEditor.focus();
|
gEditor.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} name
|
||||||
|
*/
|
||||||
function makeNewFile(name) {
|
function makeNewFile(name) {
|
||||||
gFiles[name] = {
|
gFiles[name] = {
|
||||||
doc: cm6.EditorState.create({extensions: cm6.extensions}),
|
doc: cm6.EditorState.create({extensions: cm6.extensions}),
|
||||||
@ -1013,6 +1258,9 @@ function makeNewFile(name) {
|
|||||||
openFile(name);
|
openFile(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function newFile() {
|
function newFile() {
|
||||||
let name = prompt("Name of new file:", "file.js");
|
let name = prompt("Name of new file:", "file.js");
|
||||||
if (name && !gFiles[name]) {
|
if (name && !gFiles[name]) {
|
||||||
@ -1020,6 +1268,9 @@ function newFile() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function removeFile() {
|
function removeFile() {
|
||||||
if (confirm("Remove " + gCurrentFile + "?")) {
|
if (confirm("Remove " + gCurrentFile + "?")) {
|
||||||
delete gFiles[gCurrentFile];
|
delete gFiles[gCurrentFile];
|
||||||
@ -1027,6 +1278,9 @@ function removeFile() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
async function appExport() {
|
async function appExport() {
|
||||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||||
let owner = window.location.pathname.split('/')[1].replace('~', '');
|
let owner = window.location.pathname.split('/')[1].replace('~', '');
|
||||||
@ -1049,6 +1303,12 @@ async function appExport() {
|
|||||||
a.click();
|
a.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} name
|
||||||
|
* @param {*} file
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async function save_file_to_blob_id(name, file) {
|
async function save_file_to_blob_id(name, file) {
|
||||||
console.log(`Saving ${name}.`);
|
console.log(`Saving ${name}.`);
|
||||||
let response = await fetch('/save', {
|
let response = await fetch('/save', {
|
||||||
@ -1068,6 +1328,9 @@ async function save_file_to_blob_id(name, file) {
|
|||||||
return blob_id;
|
return blob_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
async function appImport() {
|
async function appImport() {
|
||||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||||
let input = document.createElement('input');
|
let input = document.createElement('input');
|
||||||
@ -1119,6 +1382,9 @@ async function appImport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
async function sourcePretty() {
|
async function sourcePretty() {
|
||||||
let prettier = (await import('/prettier/standalone.mjs')).default;
|
let prettier = (await import('/prettier/standalone.mjs')).default;
|
||||||
let babel = (await import('/prettier/babel.mjs')).default;
|
let babel = (await import('/prettier/babel.mjs')).default;
|
||||||
@ -1140,6 +1406,7 @@ async function sourcePretty() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODOC
|
||||||
window.addEventListener("load", function() {
|
window.addEventListener("load", function() {
|
||||||
window.addEventListener("hashchange", hashChange);
|
window.addEventListener("hashchange", hashChange);
|
||||||
window.addEventListener("focus", focus);
|
window.addEventListener("focus", focus);
|
||||||
|
134
core/core.js
134
core/core.js
@ -91,6 +91,11 @@ let gGlobalSettings = {
|
|||||||
|
|
||||||
let kPingInterval = 60 * 1000;
|
let kPingInterval = 60 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} out
|
||||||
|
* @param {*} error
|
||||||
|
*/
|
||||||
function printError(out, error) {
|
function printError(out, error) {
|
||||||
if (error.stackTrace) {
|
if (error.stackTrace) {
|
||||||
out.print(error.fileName + ":" + error.lineNumber + ": " + error.message);
|
out.print(error.fileName + ":" + error.lineNumber + ": " + error.message);
|
||||||
@ -103,6 +108,12 @@ function printError(out, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} handlers
|
||||||
|
* @param {*} argv
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function invoke(handlers, argv) {
|
function invoke(handlers, argv) {
|
||||||
let promises = [];
|
let promises = [];
|
||||||
if (handlers) {
|
if (handlers) {
|
||||||
@ -119,6 +130,12 @@ function invoke(handlers, argv) {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} eventName
|
||||||
|
* @param {*} argv
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function broadcastEvent(eventName, argv) {
|
function broadcastEvent(eventName, argv) {
|
||||||
let promises = [];
|
let promises = [];
|
||||||
for (let process of Object.values(gProcesses)) {
|
for (let process of Object.values(gProcesses)) {
|
||||||
@ -128,7 +145,11 @@ function broadcastEvent(eventName, argv) {
|
|||||||
}
|
}
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} message
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function broadcast(message) {
|
function broadcast(message) {
|
||||||
let sender = this;
|
let sender = this;
|
||||||
let promises = [];
|
let promises = [];
|
||||||
@ -143,6 +164,12 @@ function broadcast(message) {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} caller
|
||||||
|
* @param {*} process
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function getUser(caller, process) {
|
function getUser(caller, process) {
|
||||||
return {
|
return {
|
||||||
key: process.key,
|
key: process.key,
|
||||||
@ -153,6 +180,12 @@ function getUser(caller, process) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} user
|
||||||
|
* @param {*} process
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function getApps(user, process) {
|
function getApps(user, process) {
|
||||||
if (process.credentials &&
|
if (process.credentials &&
|
||||||
process.credentials.session &&
|
process.credentials.session &&
|
||||||
@ -174,12 +207,26 @@ function getApps(user, process) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} from
|
||||||
|
* @param {*} to
|
||||||
|
* @param {*} message
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function postMessageInternal(from, to, message) {
|
function postMessageInternal(from, to, message) {
|
||||||
if (to.eventHandlers['message']) {
|
if (to.eventHandlers['message']) {
|
||||||
return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
|
return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} blobId
|
||||||
|
* @param {*} session
|
||||||
|
* @param {*} options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async function getSessionProcessBlob(blobId, session, options) {
|
async function getSessionProcessBlob(blobId, session, options) {
|
||||||
let actualOptions = {timeout: kPingInterval};
|
let actualOptions = {timeout: kPingInterval};
|
||||||
if (options) {
|
if (options) {
|
||||||
@ -190,7 +237,15 @@ async function getSessionProcessBlob(blobId, session, options) {
|
|||||||
return getProcessBlob(blobId, 'session_' + session, actualOptions);
|
return getProcessBlob(blobId, 'session_' + session, actualOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} blobId
|
||||||
|
* @param {*} key
|
||||||
|
* @param {*} options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async function getProcessBlob(blobId, key, options) {
|
async function getProcessBlob(blobId, key, options) {
|
||||||
|
// TODO(tasiaiso): break this down
|
||||||
let process = gProcesses[key];
|
let process = gProcesses[key];
|
||||||
if (!process
|
if (!process
|
||||||
&& !(options && "create" in options && !options.create)) {
|
&& !(options && "create" in options && !options.create)) {
|
||||||
@ -535,6 +590,11 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} settings
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function setGlobalSettings(settings) {
|
function setGlobalSettings(settings) {
|
||||||
gGlobalSettings = settings;
|
gGlobalSettings = settings;
|
||||||
try {
|
try {
|
||||||
@ -544,6 +604,12 @@ function setGlobalSettings(settings) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} data
|
||||||
|
* @param {*} bytes
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function startsWithBytes(data, bytes) {
|
function startsWithBytes(data, bytes) {
|
||||||
if (data.byteLength >= bytes.length) {
|
if (data.byteLength >= bytes.length) {
|
||||||
let dataBytes = new Uint8Array(data.slice(0, bytes.length));
|
let dataBytes = new Uint8Array(data.slice(0, bytes.length));
|
||||||
@ -556,11 +622,21 @@ function startsWithBytes(data, bytes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} path
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function guessTypeFromName(path) {
|
function guessTypeFromName(path) {
|
||||||
let extension = path.split('.').pop();
|
let extension = path.split('.').pop();
|
||||||
return k_mime_types[extension];
|
return k_mime_types[extension];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function guessTypeFromMagicBytes(data) {
|
function guessTypeFromMagicBytes(data) {
|
||||||
for (let magic of k_magic_bytes) {
|
for (let magic of k_magic_bytes) {
|
||||||
if (startsWithBytes(data, magic.bytes)) {
|
if (startsWithBytes(data, magic.bytes)) {
|
||||||
@ -569,6 +645,14 @@ function guessTypeFromMagicBytes(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} response
|
||||||
|
* @param {*} data
|
||||||
|
* @param {*} type
|
||||||
|
* @param {*} headers
|
||||||
|
* @param {*} status_code
|
||||||
|
*/
|
||||||
function sendData(response, data, type, headers, status_code) {
|
function sendData(response, data, type, headers, status_code) {
|
||||||
if (data) {
|
if (data) {
|
||||||
response.writeHead(status_code ?? 200, Object.assign({"Content-Type": type || guessTypeFromMagicBytes(data) || "application/binary", "Content-Length": data.byteLength}, headers || {}));
|
response.writeHead(status_code ?? 200, Object.assign({"Content-Type": type || guessTypeFromMagicBytes(data) || "application/binary", "Content-Length": data.byteLength}, headers || {}));
|
||||||
@ -579,6 +663,11 @@ function sendData(response, data, type, headers, status_code) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async function getBlobOrContent(id) {
|
async function getBlobOrContent(id) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
@ -590,6 +679,18 @@ async function getBlobOrContent(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let g_handler_index = 0;
|
let g_handler_index = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} response
|
||||||
|
* @param {*} handler_blob_id
|
||||||
|
* @param {*} path
|
||||||
|
* @param {*} query
|
||||||
|
* @param {*} headers
|
||||||
|
* @param {*} packageOwner
|
||||||
|
* @param {*} packageName
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async function useAppHandler(response, handler_blob_id, path, query, headers, packageOwner, packageName) {
|
async function useAppHandler(response, handler_blob_id, path, query, headers, packageOwner, packageName) {
|
||||||
print('useAppHandler', packageOwner, packageName);
|
print('useAppHandler', packageOwner, packageName);
|
||||||
let do_resolve;
|
let do_resolve;
|
||||||
@ -623,7 +724,16 @@ async function useAppHandler(response, handler_blob_id, path, query, headers, pa
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} request
|
||||||
|
* @param {*} response
|
||||||
|
* @param {*} blobId
|
||||||
|
* @param {*} uri
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async function blobHandler(request, response, blobId, uri) {
|
async function blobHandler(request, response, blobId, uri) {
|
||||||
|
// TODO(tasiaiso): break this down ?
|
||||||
for (let i in k_static_files) {
|
for (let i in k_static_files) {
|
||||||
if (uri === k_static_files[i].uri && k_static_files[i].path) {
|
if (uri === k_static_files[i].uri && k_static_files[i].path) {
|
||||||
let stat = await File.stat('core/' + k_static_files[i].path);
|
let stat = await File.stat('core/' + k_static_files[i].path);
|
||||||
@ -851,6 +961,9 @@ ssb.addEventListener('connections', function() {
|
|||||||
broadcastEvent('onConnectionsChanged', []);
|
broadcastEvent('onConnectionsChanged', []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
let data = {};
|
let data = {};
|
||||||
try {
|
try {
|
||||||
@ -869,6 +982,9 @@ async function loadSettings() {
|
|||||||
gGlobalSettings = data;
|
gGlobalSettings = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
function sendStats() {
|
function sendStats() {
|
||||||
let apps = Object.values(gProcesses).filter(process => process.app && process.stats).map(process => process.app);
|
let apps = Object.values(gProcesses).filter(process => process.app && process.stats).map(process => process.app);
|
||||||
if (apps.length) {
|
if (apps.length) {
|
||||||
@ -882,6 +998,11 @@ function sendStats() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} process
|
||||||
|
* @param {*} enabled
|
||||||
|
*/
|
||||||
function enableStats(process, enabled) {
|
function enableStats(process, enabled) {
|
||||||
process.stats = enabled;
|
process.stats = enabled;
|
||||||
if (!gStatsTimer) {
|
if (!gStatsTimer) {
|
||||||
@ -890,6 +1011,9 @@ function enableStats(process, enabled) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
*/
|
||||||
loadSettings().then(function() {
|
loadSettings().then(function() {
|
||||||
if (tildefriends.https_port && gGlobalSettings.http_redirect) {
|
if (tildefriends.https_port && gGlobalSettings.http_redirect) {
|
||||||
httpd.set_http_redirect(gGlobalSettings.http_redirect);
|
httpd.set_http_redirect(gGlobalSettings.http_redirect);
|
||||||
@ -955,6 +1079,14 @@ loadSettings().then(function() {
|
|||||||
exit(1);
|
exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} user
|
||||||
|
* @param {*} packageOwner
|
||||||
|
* @param {*} packageName
|
||||||
|
* @param {*} permission
|
||||||
|
* @param {*} allow
|
||||||
|
*/
|
||||||
function storePermission(user, packageOwner, packageName, permission, allow) {
|
function storePermission(user, packageOwner, packageName, permission, allow) {
|
||||||
if (!gGlobalSettings.userPermissions) {
|
if (!gGlobalSettings.userPermissions) {
|
||||||
gGlobalSettings.userPermissions = {};
|
gGlobalSettings.userPermissions = {};
|
||||||
|
11
core/form.js
11
core/form.js
@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} encoded
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function decode(encoded) {
|
function decode(encoded) {
|
||||||
let result = "";
|
let result = "";
|
||||||
for (let i = 0; i < encoded.length; i++) {
|
for (let i = 0; i < encoded.length; i++) {
|
||||||
@ -14,6 +19,12 @@ function decode(encoded) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} encoded
|
||||||
|
* @param {*} initial
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function decodeForm(encoded, initial) {
|
function decodeForm(encoded, initial) {
|
||||||
let result = initial || {};
|
let result = initial || {};
|
||||||
if (encoded) {
|
if (encoded) {
|
||||||
|
18
core/http.js
18
core/http.js
@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* TODO: document so we can make this improve
|
||||||
|
* @param {*} url
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function parseUrl(url) {
|
function parseUrl(url) {
|
||||||
// XXX: Hack.
|
// XXX: Hack.
|
||||||
let match = url.match(new RegExp("(\\w+)://([^/:]+)(?::(\\d+))?(.*)"));
|
let match = url.match(new RegExp("(\\w+)://([^/:]+)(?::(\\d+))?(.*)"));
|
||||||
@ -9,6 +15,11 @@ function parseUrl(url) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function parseResponse(data) {
|
function parseResponse(data) {
|
||||||
let firstLine;
|
let firstLine;
|
||||||
let headers = {};
|
let headers = {};
|
||||||
@ -28,6 +39,13 @@ function parseResponse(data) {
|
|||||||
return {body: data};
|
return {body: data};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} url
|
||||||
|
* @param {*} options
|
||||||
|
* @param {*} allowed_hosts
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export function fetch(url, options, allowed_hosts) {
|
export function fetch(url, options, allowed_hosts) {
|
||||||
let parsed = parseUrl(url);
|
let parsed = parseUrl(url);
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
|
@ -3,6 +3,10 @@ let g_api = {};
|
|||||||
let g_next_id = 1;
|
let g_next_id = 1;
|
||||||
let g_calls = {};
|
let g_calls = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function get_is_browser() {
|
function get_is_browser() {
|
||||||
try { return window !== undefined && console !== undefined; } catch { return false; }
|
try { return window !== undefined && console !== undefined; } catch { return false; }
|
||||||
}
|
}
|
||||||
@ -11,6 +15,13 @@ if (k_is_browser) {
|
|||||||
print = console.log;
|
print = console.log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} target
|
||||||
|
* @param {*} prop
|
||||||
|
* @param {*} receiver
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function make_rpc(target, prop, receiver) {
|
function make_rpc(target, prop, receiver) {
|
||||||
return function() {
|
return function() {
|
||||||
let id = g_next_id++;
|
let id = g_next_id++;
|
||||||
@ -29,6 +40,10 @@ function make_rpc(target, prop, receiver) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} response
|
||||||
|
*/
|
||||||
function send(response) {
|
function send(response) {
|
||||||
if (k_is_browser) {
|
if (k_is_browser) {
|
||||||
window.parent.postMessage(response, '*');
|
window.parent.postMessage(response, '*');
|
||||||
@ -37,6 +52,10 @@ function send(response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} message
|
||||||
|
*/
|
||||||
function call_rpc(message) {
|
function call_rpc(message) {
|
||||||
if (message && message.message === 'tfrpc') {
|
if (message && message.message === 'tfrpc') {
|
||||||
let id = message.id;
|
let id = message.id;
|
||||||
@ -85,6 +104,10 @@ if (k_is_browser) {
|
|||||||
|
|
||||||
export let rpc = new Proxy({}, {get: make_rpc});
|
export let rpc = new Proxy({}, {get: make_rpc});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODOC
|
||||||
|
* @param {*} method
|
||||||
|
*/
|
||||||
export function register(method) {
|
export function register(method) {
|
||||||
g_api[method.name] = method;
|
g_api[method.name] = method;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user