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