build: Add prettier to the project
This commit is contained in:
98
core/app.js
98
core/app.js
@ -16,18 +16,18 @@ function App() {
|
||||
return this;
|
||||
}
|
||||
|
||||
App.prototype.readOutput = function(callback) {
|
||||
App.prototype.readOutput = function (callback) {
|
||||
this._on_output = callback;
|
||||
}
|
||||
};
|
||||
|
||||
App.prototype.makeFunction = function(api) {
|
||||
App.prototype.makeFunction = function (api) {
|
||||
let self = this;
|
||||
let result = function() {
|
||||
let result = function () {
|
||||
let id = g_next_id++;
|
||||
while (!id || g_calls[id]) {
|
||||
id = g_next_id++;
|
||||
}
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
let promise = new Promise(function (resolve, reject) {
|
||||
g_calls[id] = {resolve: resolve, reject: reject};
|
||||
});
|
||||
let message = {
|
||||
@ -41,12 +41,12 @@ App.prototype.makeFunction = function(api) {
|
||||
};
|
||||
Object.defineProperty(result, 'name', {value: api[0], writable: false});
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
App.prototype.send = function(message) {
|
||||
App.prototype.send = function (message) {
|
||||
if (this._send_queue) {
|
||||
if (this._on_output) {
|
||||
this._send_queue.forEach(x => this._on_output(x));
|
||||
this._send_queue.forEach((x) => this._on_output(x));
|
||||
this._send_queue = null;
|
||||
} else if (message) {
|
||||
this._send_queue.push(message);
|
||||
@ -55,7 +55,7 @@ App.prototype.send = function(message) {
|
||||
if (message && this._on_output) {
|
||||
this._on_output(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function socket(request, response, client) {
|
||||
let process;
|
||||
@ -63,43 +63,48 @@ function socket(request, response, client) {
|
||||
let credentials = auth.query(request.headers);
|
||||
let refresh = auth.make_refresh(credentials);
|
||||
|
||||
response.onClose = async function() {
|
||||
response.onClose = async function () {
|
||||
if (process && process.task) {
|
||||
process.task.kill();
|
||||
}
|
||||
if (process) {
|
||||
process.timeout = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
response.onMessage = async function(event) {
|
||||
response.onMessage = async function (event) {
|
||||
if (event.opCode == 0x1 || event.opCode == 0x2) {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
print("ERROR", error, event.data, event.data.length, event.opCode);
|
||||
print('ERROR', error, event.data, event.data.length, event.opCode);
|
||||
return;
|
||||
}
|
||||
if (message.action == "hello") {
|
||||
if (message.action == 'hello') {
|
||||
let packageOwner;
|
||||
let packageName;
|
||||
let blobId;
|
||||
let match;
|
||||
let parentApp;
|
||||
if (match = /^\/([&%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(message.path)) {
|
||||
if (
|
||||
(match = /^\/([&%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(message.path))
|
||||
) {
|
||||
blobId = match[1];
|
||||
} else if (match = /^\/\~([^\/]+)\/([^\/]+)\/$/.exec(message.path)) {
|
||||
} else if ((match = /^\/\~([^\/]+)\/([^\/]+)\/$/.exec(message.path))) {
|
||||
packageOwner = match[1];
|
||||
packageName = match[2];
|
||||
blobId = await new Database(packageOwner).get('path:' + packageName);
|
||||
if (!blobId) {
|
||||
response.send(JSON.stringify({
|
||||
message: 'tfrpc',
|
||||
method: "error",
|
||||
params: [message.path + ' not found'],
|
||||
id: -1,
|
||||
}), 0x1);
|
||||
response.send(
|
||||
JSON.stringify({
|
||||
message: 'tfrpc',
|
||||
method: 'error',
|
||||
params: [message.path + ' not found'],
|
||||
id: -1,
|
||||
}),
|
||||
0x1
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (packageOwner != 'core') {
|
||||
@ -110,12 +115,15 @@ function socket(request, response, client) {
|
||||
};
|
||||
}
|
||||
}
|
||||
response.send(JSON.stringify({
|
||||
action: "session",
|
||||
credentials: credentials,
|
||||
parentApp: parentApp,
|
||||
id: blobId,
|
||||
}), 0x1);
|
||||
response.send(
|
||||
JSON.stringify({
|
||||
action: 'session',
|
||||
credentials: credentials,
|
||||
parentApp: parentApp,
|
||||
id: blobId,
|
||||
}),
|
||||
0x1
|
||||
);
|
||||
|
||||
options.api = message.api || [];
|
||||
options.credentials = credentials;
|
||||
@ -125,19 +133,26 @@ function socket(request, response, client) {
|
||||
let sessionId = makeSessionId();
|
||||
if (blobId) {
|
||||
if (message.edit_only) {
|
||||
response.send(JSON.stringify({action: 'ready', edit_only: true}), 0x1);
|
||||
response.send(
|
||||
JSON.stringify({action: 'ready', edit_only: true}),
|
||||
0x1
|
||||
);
|
||||
} else {
|
||||
process = await core.getSessionProcessBlob(blobId, sessionId, options);
|
||||
process = await core.getSessionProcessBlob(
|
||||
blobId,
|
||||
sessionId,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
if (process) {
|
||||
process.app.readOutput(function(message) {
|
||||
process.app.readOutput(function (message) {
|
||||
response.send(JSON.stringify(message), 0x1);
|
||||
});
|
||||
process.app.send();
|
||||
}
|
||||
|
||||
let ping = function() {
|
||||
let ping = function () {
|
||||
let now = Date.now();
|
||||
let again = true;
|
||||
if (now - process.lastActive < process.timeout) {
|
||||
@ -150,14 +165,14 @@ function socket(request, response, client) {
|
||||
again = false;
|
||||
} else {
|
||||
// Idle. Ping them.
|
||||
response.send("", 0x9);
|
||||
response.send('', 0x9);
|
||||
process.lastPing = now;
|
||||
}
|
||||
|
||||
if (again && process.timeout) {
|
||||
setTimeout(ping, process.timeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (process && process.timeout > 0) {
|
||||
setTimeout(ping, process.timeout);
|
||||
@ -197,11 +212,16 @@ function socket(request, response, client) {
|
||||
if (process) {
|
||||
process.lastActive = Date.now();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
response.upgrade(100, refresh ? {
|
||||
'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`,
|
||||
} : {});
|
||||
response.upgrade(
|
||||
100,
|
||||
refresh
|
||||
? {
|
||||
'Set-Cookie': `session=${refresh.token}; path=/; Max-Age=${refresh.interval}; Secure; SameSite=Strict`,
|
||||
}
|
||||
: {}
|
||||
);
|
||||
}
|
||||
|
||||
export { socket, App };
|
||||
export {socket, App};
|
||||
|
@ -1,15 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tilde Friends Sign-in</title>
|
||||
<link type="text/css" rel="stylesheet" href="/static/style.css">
|
||||
<link type="image/png" rel="shortcut icon" href="/static/favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link type="text/css" rel="stylesheet" href="/static/style.css" />
|
||||
<link type="image/png" rel="shortcut icon" href="/static/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="text-align: center">Tilde Friends Sign-in</h1>
|
||||
<tf-auth id="auth"></tf-auth>
|
||||
<script>window.litDisableBundleWarning = true;</script>
|
||||
<script>
|
||||
window.litDisableBundleWarning = true;
|
||||
</script>
|
||||
<script type="module">
|
||||
import {LitElement, html} from '/lit/lit-all.min.js';
|
||||
let g_data = $AUTH_DATA;
|
||||
|
167
core/auth.js
167
core/auth.js
@ -1,7 +1,7 @@
|
||||
import * as core from './core.js';
|
||||
import * as form from './form.js';
|
||||
|
||||
let gDatabase = new Database("auth");
|
||||
let gDatabase = new Database('auth');
|
||||
|
||||
const kRefreshInterval = 1 * 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
@ -36,8 +36,20 @@ function makeJwt(payload) {
|
||||
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('.');
|
||||
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('.');
|
||||
return jwt;
|
||||
}
|
||||
|
||||
@ -51,7 +63,7 @@ function readSession(session) {
|
||||
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()
|
||||
let now = new Date().valueOf();
|
||||
if (now < result.exp) {
|
||||
print(`JWT valid for another ${(result.exp - now) / 1000} seconds.`);
|
||||
return result;
|
||||
@ -77,9 +89,15 @@ function hashPassword(password) {
|
||||
}
|
||||
|
||||
function noAdministrator() {
|
||||
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 ||
|
||||
!core.globalSettings.permissions ||
|
||||
!Object.keys(core.globalSettings.permissions).some(function (name) {
|
||||
return (
|
||||
core.globalSettings.permissions[name].indexOf('administration') != -1
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function makeAdministrator(name) {
|
||||
@ -89,8 +107,8 @@ function makeAdministrator(name) {
|
||||
if (!core.globalSettings.permissions[name]) {
|
||||
core.globalSettings.permissions[name] = [];
|
||||
}
|
||||
if (core.globalSettings.permissions[name].indexOf("administration") == -1) {
|
||||
core.globalSettings.permissions[name].push("administration");
|
||||
if (core.globalSettings.permissions[name].indexOf('administration') == -1) {
|
||||
core.globalSettings.permissions[name].push('administration');
|
||||
}
|
||||
core.setGlobalSettings(core.globalSettings);
|
||||
}
|
||||
@ -101,7 +119,7 @@ function getCookies(headers) {
|
||||
if (headers.cookie) {
|
||||
let parts = headers.cookie.split(/,|;/);
|
||||
for (let i in parts) {
|
||||
let equals = parts[i].indexOf("=");
|
||||
let equals = parts[i].indexOf('=');
|
||||
let name = parts[i].substring(0, equals).trim();
|
||||
let value = parts[i].substring(equals + 1).trim();
|
||||
cookies[name] = value;
|
||||
@ -113,18 +131,34 @@ function getCookies(headers) {
|
||||
|
||||
function isNameValid(name) {
|
||||
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')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function handler(request, response) {
|
||||
let session = getCookies(request.headers).session;
|
||||
if (request.uri == "/login") {
|
||||
if (request.uri == '/login') {
|
||||
let formData = form.decodeForm(request.query);
|
||||
if (query(request.headers)?.permissions?.authenticated) {
|
||||
if (formData.return) {
|
||||
response.writeHead(303, {"Location": formData.return});
|
||||
response.writeHead(303, {Location: formData.return});
|
||||
} else {
|
||||
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + request.headers.host + '/', "Content-Length": "0"});
|
||||
response.writeHead(303, {
|
||||
Location:
|
||||
(request.client.tls ? 'https://' : 'http://') +
|
||||
request.headers.host +
|
||||
'/',
|
||||
'Content-Length': '0',
|
||||
});
|
||||
}
|
||||
response.end();
|
||||
return;
|
||||
@ -133,22 +167,23 @@ function handler(request, response) {
|
||||
let sessionIsNew = false;
|
||||
let loginError;
|
||||
|
||||
if (request.method == "POST" || formData.submit) {
|
||||
if (request.method == 'POST' || formData.submit) {
|
||||
sessionIsNew = true;
|
||||
formData = form.decodeForm(utf8Decode(request.body), formData);
|
||||
if (formData.submit == "Login") {
|
||||
let account = gDatabase.get("user:" + formData.name);
|
||||
if (formData.submit == 'Login') {
|
||||
let account = gDatabase.get('user:' + formData.name);
|
||||
account = account ? JSON.parse(account) : account;
|
||||
if (formData.register == '1') {
|
||||
if (!account &&
|
||||
if (
|
||||
!account &&
|
||||
isNameValid(formData.name) &&
|
||||
formData.password == formData.confirm) {
|
||||
formData.password == formData.confirm
|
||||
) {
|
||||
let users = new Set();
|
||||
let users_original = gDatabase.get('users');
|
||||
try {
|
||||
users = new Set(JSON.parse(users_original));
|
||||
} catch {
|
||||
}
|
||||
} catch {}
|
||||
if (!users.has(formData.name)) {
|
||||
users.add(formData.name);
|
||||
}
|
||||
@ -166,10 +201,12 @@ function handler(request, response) {
|
||||
loginError = 'Error registering account.';
|
||||
}
|
||||
} else if (formData.change == '1') {
|
||||
if (account &&
|
||||
if (
|
||||
account &&
|
||||
isNameValid(formData.name) &&
|
||||
formData.new_password == formData.confirm &&
|
||||
verifyPassword(formData.password, account.password)) {
|
||||
verifyPassword(formData.password, account.password)
|
||||
) {
|
||||
session = makeJwt({name: formData.name});
|
||||
account = {password: hashPassword(formData.new_password)};
|
||||
gDatabase.set('user:' + formData.name, JSON.stringify(account));
|
||||
@ -177,9 +214,11 @@ function handler(request, response) {
|
||||
loginError = 'Error changing password.';
|
||||
}
|
||||
} else {
|
||||
if (account &&
|
||||
if (
|
||||
account &&
|
||||
account.password &&
|
||||
verifyPassword(formData.password, account.password)) {
|
||||
verifyPassword(formData.password, account.password)
|
||||
) {
|
||||
session = makeJwt({name: formData.name});
|
||||
if (noAdministrator()) {
|
||||
makeAdministrator(formData.name);
|
||||
@ -197,32 +236,52 @@ function handler(request, response) {
|
||||
let cookie = `session=${session}; path=/; Max-Age=${kRefreshInterval}; ${request.client.tls ? 'Secure; ' : ''}SameSite=Strict; HttpOnly`;
|
||||
let entry = readSession(session);
|
||||
if (entry && formData.return) {
|
||||
response.writeHead(303, {"Location": formData.return, "Set-Cookie": cookie});
|
||||
response.writeHead(303, {
|
||||
Location: formData.return,
|
||||
'Set-Cookie': cookie,
|
||||
});
|
||||
response.end();
|
||||
} else {
|
||||
File.readFile("core/auth.html").then(function(data) {
|
||||
let html = utf8Decode(data);
|
||||
let auth_data = {
|
||||
session_is_new: sessionIsNew,
|
||||
name: entry?.name,
|
||||
error: loginError,
|
||||
code_of_conduct: core.globalSettings.code_of_conduct,
|
||||
have_administrator: !noAdministrator(),
|
||||
};
|
||||
html = utf8Encode(html.replace('$AUTH_DATA', JSON.stringify(auth_data)));
|
||||
response.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "Set-Cookie": cookie, "Content-Length": html.length});
|
||||
response.end(html);
|
||||
}).catch(function(error) {
|
||||
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"});
|
||||
response.end("404 File not found");
|
||||
});
|
||||
File.readFile('core/auth.html')
|
||||
.then(function (data) {
|
||||
let html = utf8Decode(data);
|
||||
let auth_data = {
|
||||
session_is_new: sessionIsNew,
|
||||
name: entry?.name,
|
||||
error: loginError,
|
||||
code_of_conduct: core.globalSettings.code_of_conduct,
|
||||
have_administrator: !noAdministrator(),
|
||||
};
|
||||
html = utf8Encode(
|
||||
html.replace('$AUTH_DATA', JSON.stringify(auth_data))
|
||||
);
|
||||
response.writeHead(200, {
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Set-Cookie': cookie,
|
||||
'Content-Length': html.length,
|
||||
});
|
||||
response.end(html);
|
||||
})
|
||||
.catch(function (error) {
|
||||
response.writeHead(404, {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
Connection: 'close',
|
||||
});
|
||||
response.end('404 File not found');
|
||||
});
|
||||
}
|
||||
} else if (request.uri == "/login/logout") {
|
||||
response.writeHead(303, {"Set-Cookie": `session=; path=/; ${request.client.tls ? 'Secure; ' : ''}SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly`, "Location": "/login" + (request.query ? "?" + request.query : "")});
|
||||
} else if (request.uri == '/login/logout') {
|
||||
response.writeHead(303, {
|
||||
'Set-Cookie': `session=; path=/; ${request.client.tls ? 'Secure; ' : ''}SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly`,
|
||||
Location: '/login' + (request.query ? '?' + request.query : ''),
|
||||
});
|
||||
response.end();
|
||||
} else {
|
||||
response.writeHead(200, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"});
|
||||
response.end("Hello, " + request.client.peerName + ".");
|
||||
response.writeHead(200, {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
Connection: 'close',
|
||||
});
|
||||
response.end('Hello, ' + request.client.peerName + '.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,14 +290,18 @@ function getPermissions(session) {
|
||||
let entry = readSession(session);
|
||||
if (entry) {
|
||||
permissions = getPermissionsForUser(entry.name);
|
||||
permissions.authenticated = entry.name !== "guest";
|
||||
permissions.authenticated = entry.name !== 'guest';
|
||||
}
|
||||
return permissions || {};
|
||||
}
|
||||
|
||||
function getPermissionsForUser(userName) {
|
||||
let permissions = {};
|
||||
if (core.globalSettings && core.globalSettings.permissions && core.globalSettings.permissions[userName]) {
|
||||
if (
|
||||
core.globalSettings &&
|
||||
core.globalSettings.permissions &&
|
||||
core.globalSettings.permissions[userName]
|
||||
) {
|
||||
for (let i in core.globalSettings.permissions[userName]) {
|
||||
permissions[core.globalSettings.permissions[userName][i]] = true;
|
||||
}
|
||||
@ -250,10 +313,12 @@ function query(headers) {
|
||||
let session = getCookies(headers).session;
|
||||
let entry;
|
||||
let autologin = tildefriends.args.autologin;
|
||||
if (entry = autologin ? {name: autologin} : readSession(session)) {
|
||||
if ((entry = autologin ? {name: autologin} : readSession(session))) {
|
||||
return {
|
||||
session: entry,
|
||||
permissions: autologin ? getPermissionsForUser(autologin) : getPermissions(session),
|
||||
permissions: autologin
|
||||
? getPermissionsForUser(autologin)
|
||||
: getPermissions(session),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -267,4 +332,4 @@ function make_refresh(credentials) {
|
||||
}
|
||||
}
|
||||
|
||||
export { handler, query, make_refresh };
|
||||
export {handler, query, make_refresh};
|
||||
|
794
core/client.js
794
core/client.js
File diff suppressed because it is too large
Load Diff
812
core/core.js
812
core/core.js
File diff suppressed because it is too large
Load Diff
10
core/form.js
10
core/form.js
@ -1,10 +1,10 @@
|
||||
function decode(encoded) {
|
||||
let result = "";
|
||||
let result = '';
|
||||
for (let i = 0; i < encoded.length; i++) {
|
||||
let c = encoded[i];
|
||||
if (c == "+") {
|
||||
result += " ";
|
||||
} else if (c == "%") {
|
||||
if (c == '+') {
|
||||
result += ' ';
|
||||
} else if (c == '%') {
|
||||
result += String.fromCharCode(parseInt(encoded.slice(i + 1, i + 3), 16));
|
||||
i += 2;
|
||||
} else {
|
||||
@ -30,4 +30,4 @@ function decodeForm(encoded, initial) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export { decodeForm };
|
||||
export {decodeForm};
|
||||
|
101
core/http.js
101
core/http.js
@ -1,11 +1,11 @@
|
||||
function parseUrl(url) {
|
||||
// XXX: Hack.
|
||||
let match = url.match(new RegExp("(\\w+)://([^/:]+)(?::(\\d+))?(.*)"));
|
||||
let match = url.match(new RegExp('(\\w+)://([^/:]+)(?::(\\d+))?(.*)'));
|
||||
return {
|
||||
protocol: match[1],
|
||||
host: match[2],
|
||||
path: match[4],
|
||||
port: match[3] ? parseInt(match[3]) : match[1] == "http" ? 80 : 443,
|
||||
port: match[3] ? parseInt(match[3]) : match[1] == 'http' ? 80 : 443,
|
||||
};
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ function parseResponse(data) {
|
||||
} else if (!firstLine) {
|
||||
firstLine = line;
|
||||
} else {
|
||||
let colon = line.indexOf(":");
|
||||
let colon = line.indexOf(':');
|
||||
headers[line.substring(colon)] = line.substring(colon + 1);
|
||||
}
|
||||
}
|
||||
@ -30,57 +30,66 @@ function parseResponse(data) {
|
||||
|
||||
export function fetch(url, options, allowed_hosts) {
|
||||
let parsed = parseUrl(url);
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if ((allowed_hosts ?? []).indexOf(parsed.host) == -1) {
|
||||
throw new Error(`fetch() request to host ${parsed.host} is not allowed.`);
|
||||
}
|
||||
let socket = new Socket();
|
||||
let buffer = new Uint8Array(0);
|
||||
|
||||
return socket.connect(parsed.host, parsed.port).then(function() {
|
||||
socket.read(function(data) {
|
||||
if (data && data.length) {
|
||||
let newBuffer = new Uint8Array(buffer.length + data.length);
|
||||
newBuffer.set(buffer, 0);
|
||||
newBuffer.set(data, buffer.length);
|
||||
buffer = newBuffer;
|
||||
} else {
|
||||
let result = parseHttpResponse(buffer);
|
||||
if (!result) {
|
||||
reject(new Exception('Parse failed.'));
|
||||
}
|
||||
if (typeof result == 'number') {
|
||||
if (result == -2) {
|
||||
reject('Incomplete request.');
|
||||
} else {
|
||||
reject('Bad request.');
|
||||
}
|
||||
} else if (typeof result == 'object') {
|
||||
resolve({
|
||||
body: buffer.slice(result.bytes_parsed),
|
||||
status: result.status,
|
||||
message: result.message,
|
||||
headers: result.headers,
|
||||
});
|
||||
return socket
|
||||
.connect(parsed.host, parsed.port)
|
||||
.then(function () {
|
||||
socket.read(function (data) {
|
||||
if (data && data.length) {
|
||||
let newBuffer = new Uint8Array(buffer.length + data.length);
|
||||
newBuffer.set(buffer, 0);
|
||||
newBuffer.set(data, buffer.length);
|
||||
buffer = newBuffer;
|
||||
} else {
|
||||
reject(new Exception('Unexpected parse result.'));
|
||||
let result = parseHttpResponse(buffer);
|
||||
if (!result) {
|
||||
reject(new Exception('Parse failed.'));
|
||||
}
|
||||
if (typeof result == 'number') {
|
||||
if (result == -2) {
|
||||
reject('Incomplete request.');
|
||||
} else {
|
||||
reject('Bad request.');
|
||||
}
|
||||
} else if (typeof result == 'object') {
|
||||
resolve({
|
||||
body: buffer.slice(result.bytes_parsed),
|
||||
status: result.status,
|
||||
message: result.message,
|
||||
headers: result.headers,
|
||||
});
|
||||
} else {
|
||||
reject(new Exception('Unexpected parse result.'));
|
||||
}
|
||||
resolve(parseResponse(utf8Decode(buffer)));
|
||||
}
|
||||
resolve(parseResponse(utf8Decode(buffer)));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (parsed.port == 443) {
|
||||
return socket.startTls();
|
||||
}
|
||||
}).then(function() {
|
||||
let body = typeof options?.body == 'string' ? utf8Encode(options.body) : (options.body || new Uint8Array(0));
|
||||
let headers = utf8Encode(`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n`);
|
||||
let fullRequest = new Uint8Array(headers.length + body.length);
|
||||
fullRequest.set(headers, 0);
|
||||
fullRequest.set(body, headers.length);
|
||||
socket.write(fullRequest);
|
||||
}).catch(function(error) {
|
||||
reject(error);
|
||||
});
|
||||
if (parsed.port == 443) {
|
||||
return socket.startTls();
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
let body =
|
||||
typeof options?.body == 'string'
|
||||
? utf8Encode(options.body)
|
||||
: options.body || new Uint8Array(0);
|
||||
let headers = utf8Encode(
|
||||
`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n`
|
||||
);
|
||||
let fullRequest = new Uint8Array(headers.length + body.length);
|
||||
fullRequest.set(headers, 0);
|
||||
fullRequest.set(body, headers.length);
|
||||
socket.write(fullRequest);
|
||||
})
|
||||
.catch(function (error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
139
core/index.html
139
core/index.html
@ -1,46 +1,145 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tilde Friends</title>
|
||||
<link type="text/css" rel="stylesheet" href="/static/style.css">
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css">
|
||||
<link type="image/png" rel="shortcut icon" href="/static/favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link type="text/css" rel="stylesheet" href="/static/style.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||
<link type="image/png" rel="shortcut icon" href="/static/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script>
|
||||
function set_access_key_title(event) {
|
||||
if (!event.srcElement.title) {
|
||||
event.srcElement.title = `${event.srcElement.dataset.tip} [${(event.srcElement.accessKeyLabel || ('⌨️' + event.srcElement.accessKey)).toUpperCase()}]`;
|
||||
event.srcElement.title = `${event.srcElement.dataset.tip} [${(event.srcElement.accessKeyLabel || '⌨️' + event.srcElement.accessKey).toUpperCase()}]`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body style="display: flex; flex-flow: column; width: 100vw; height: 100vh; position: absolute; max-width: 100%; max-height: 100%">
|
||||
<body
|
||||
style="
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
"
|
||||
>
|
||||
<tf-navigation></tf-navigation>
|
||||
<div id="content" class="hbox" style="flex: 1 0; overflow: auto">
|
||||
<div id="editPane" class="vbox" style="flex: 0 1 100%; display: none; overflow: auto">
|
||||
<div
|
||||
id="editPane"
|
||||
class="vbox"
|
||||
style="flex: 0 1 100%; display: none; overflow: auto"
|
||||
>
|
||||
<div class="navigation w3-bar" style="display: flex">
|
||||
<button class="w3-bar-item w3-button w3-blue" id="closeEditor" name="closeEditor" accesskey="c" onmouseover="set_access_key_title(event)" data-tip="Close the editor">Close</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="save" name="save" accesskey="s" onmouseover="set_access_key_title(event)" data-tip="Save the app under the given path">Save</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="icon" name="icon" accesskey="i" onmouseover="set_access_key_title(event)" data-tip="Set an icon/emoji for the app">📦</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="export" name="export" accesskey="e" onmouseover="set_access_key_title(event)" data-tip="Export app to .zip file">Export</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="import" name="import" accesskey="i" onmouseover="set_access_key_title(event)" data-tip="Import app from .zip file">Import</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="pretty" name="pretty" accesskey="p" onmouseover="set_access_key_title(event)" data-tip="Clean up source formatting">🧼</button>
|
||||
<input class="w3-bar-item w3-input w3-border w3-blue" type="text" id="name" name="name" style="flex: 1 1; min-width: 1em"></input>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="delete" name="delete" accesskey="d" onmouseover="set_access_key_title(event)" data-tip="Delete the app">Delete</button>
|
||||
<button class="w3-bar-item w3-button w3-blue" id="trace_button" accesskey="t" onmouseover="set_access_key_title(event)" data-tip="Open a performance trace for the server">Trace</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-blue"
|
||||
id="closeEditor"
|
||||
name="closeEditor"
|
||||
accesskey="c"
|
||||
onmouseover="set_access_key_title(event)"
|
||||
data-tip="Close the editor"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-blue"
|
||||
id="save"
|
||||
name="save"
|
||||
accesskey="s"
|
||||
onmouseover="set_access_key_title(event)"
|
||||
data-tip="Save the app under the given path"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-blue"
|
||||
id="icon"
|
||||
name="icon"
|
||||
accesskey="i"
|
||||
onmouseover="set_access_key_title(event)"
|
||||
data-tip="Set an icon/emoji for the app"
|
||||
>
|
||||
📦
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-blue"
|
||||
id="export"
|
||||
name="export"
|
||||
accesskey="e"
|
||||
onmouseover="set_access_key_title(event)"
|
||||
data-tip="Export app to .zip file"
|
||||
>
|
||||
Export
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-blue"
|
||||
id="import"
|
||||
name="import"
|
||||
accesskey="i"
|
||||
onmouseover="set_access_key_title(event)"
|
||||
data-tip="Import app from .zip file"
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-blue"
|
||||
id="pretty"
|
||||
name="pretty"
|
||||
accesskey="p"
|
||||
onmouseover="set_access_key_title(event)"
|
||||
data-tip="Clean up source formatting"
|
||||
>
|
||||
🧼
|
||||
</button>
|
||||
<input
|
||||
class="w3-bar-item w3-input w3-border w3-blue"
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
style="flex: 1 1; min-width: 1em"
|
||||
/>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-blue"
|
||||
id="delete"
|
||||
name="delete"
|
||||
accesskey="d"
|
||||
onmouseover="set_access_key_title(event)"
|
||||
data-tip="Delete the app"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-blue"
|
||||
id="trace_button"
|
||||
accesskey="t"
|
||||
onmouseover="set_access_key_title(event)"
|
||||
data-tip="Open a performance trace for the server"
|
||||
>
|
||||
Trace
|
||||
</button>
|
||||
</div>
|
||||
<div class="hbox" style="flex: 1 1; overflow: auto">
|
||||
<div style="overflow: auto">
|
||||
<tf-files-pane style="overflow: auto"></tf-files-pane>
|
||||
</div>
|
||||
<div style="flex: 1 1; overflow: auto"><div id="editor" style="width: 100%; height: 100%"></div></div>
|
||||
<div style="flex: 1 1; overflow: auto">
|
||||
<div id="editor" style="width: 100%; height: 100%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="viewPane" class="vbox" style="flex: 0 1 100%; overflow: auto">
|
||||
<iframe id="document" sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads" style="width: 100%; height: 100%; border: 0"></iframe>
|
||||
<iframe
|
||||
id="document"
|
||||
sandbox="allow-forms allow-scripts allow-top-navigation allow-modals allow-popups allow-downloads"
|
||||
style="width: 100%; height: 100%; border: 0"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<script>window.litDisableBundleWarning = true;</script>
|
||||
<script>
|
||||
window.litDisableBundleWarning = true;
|
||||
</script>
|
||||
<script src="/static/client.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -102,22 +102,54 @@ a:active {
|
||||
}
|
||||
|
||||
/* Solarized Color Scheme Colors */
|
||||
.base03 { color: #002b36; }
|
||||
.base02 { color: #073642; }
|
||||
.base01 { color: #586e75; }
|
||||
.base00 { color: #657b83; }
|
||||
.base0 { color: #839496; }
|
||||
.base1 { color: #93a1a1; }
|
||||
.base2 { color: #eee8d5; }
|
||||
.base3 { color: #fdf6e3; }
|
||||
.yellow { color: #b58900; }
|
||||
.orange { color: #cb4b16; }
|
||||
.red { color: #dc322f; }
|
||||
.magenta { color: #d33682; }
|
||||
.violet { color: #6c71c4; }
|
||||
.blue { color: #268bd2; }
|
||||
.cyan { color: #2aa198; }
|
||||
.green { color: #859900; }
|
||||
.base03 {
|
||||
color: #002b36;
|
||||
}
|
||||
.base02 {
|
||||
color: #073642;
|
||||
}
|
||||
.base01 {
|
||||
color: #586e75;
|
||||
}
|
||||
.base00 {
|
||||
color: #657b83;
|
||||
}
|
||||
.base0 {
|
||||
color: #839496;
|
||||
}
|
||||
.base1 {
|
||||
color: #93a1a1;
|
||||
}
|
||||
.base2 {
|
||||
color: #eee8d5;
|
||||
}
|
||||
.base3 {
|
||||
color: #fdf6e3;
|
||||
}
|
||||
.yellow {
|
||||
color: #b58900;
|
||||
}
|
||||
.orange {
|
||||
color: #cb4b16;
|
||||
}
|
||||
.red {
|
||||
color: #dc322f;
|
||||
}
|
||||
.magenta {
|
||||
color: #d33682;
|
||||
}
|
||||
.violet {
|
||||
color: #6c71c4;
|
||||
}
|
||||
.blue {
|
||||
color: #268bd2;
|
||||
}
|
||||
.cyan {
|
||||
color: #2aa198;
|
||||
}
|
||||
.green {
|
||||
color: #859900;
|
||||
}
|
||||
|
||||
.permissions {
|
||||
position: absolute;
|
||||
|
@ -4,7 +4,11 @@ let g_next_id = 1;
|
||||
let g_calls = {};
|
||||
|
||||
function get_is_browser() {
|
||||
try { return window !== undefined && console !== undefined; } catch { return false; }
|
||||
try {
|
||||
return window !== undefined && console !== undefined;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (k_is_browser) {
|
||||
@ -12,21 +16,31 @@ if (k_is_browser) {
|
||||
}
|
||||
|
||||
function make_rpc(target, prop, receiver) {
|
||||
return function() {
|
||||
return function () {
|
||||
let id = g_next_id++;
|
||||
while (!id || g_calls[id] !== undefined) {
|
||||
id = g_next_id++;
|
||||
}
|
||||
let promise = new Promise(function(resolve, reject) {
|
||||
let promise = new Promise(function (resolve, reject) {
|
||||
g_calls[id] = {resolve: resolve, reject: reject};
|
||||
});
|
||||
if (k_is_browser) {
|
||||
window.parent.postMessage({message: 'tfrpc', method: prop, params: [...arguments], id: id}, '*');
|
||||
window.parent.postMessage(
|
||||
{message: 'tfrpc', method: prop, params: [...arguments], id: id},
|
||||
'*'
|
||||
);
|
||||
return promise;
|
||||
} else {
|
||||
return app.postMessage({message: 'tfrpc', method: prop, params: [...arguments], id: id}).then(x => promise);
|
||||
return app
|
||||
.postMessage({
|
||||
message: 'tfrpc',
|
||||
method: prop,
|
||||
params: [...arguments],
|
||||
id: id,
|
||||
})
|
||||
.then((x) => promise);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function send(response) {
|
||||
@ -44,16 +58,22 @@ function call_rpc(message) {
|
||||
let method = g_api[message.method];
|
||||
if (method) {
|
||||
try {
|
||||
Promise.resolve(method(...message.params)).then(function(result) {
|
||||
send({message: 'tfrpc', id: id, result: result});
|
||||
}).catch(function(error) {
|
||||
send({message: 'tfrpc', id: id, error: error});
|
||||
});
|
||||
Promise.resolve(method(...message.params))
|
||||
.then(function (result) {
|
||||
send({message: 'tfrpc', id: id, result: result});
|
||||
})
|
||||
.catch(function (error) {
|
||||
send({message: 'tfrpc', id: id, error: error});
|
||||
});
|
||||
} catch (error) {
|
||||
send({message: 'tfrpc', id: id, error: error});
|
||||
}
|
||||
} else {
|
||||
send({message: 'tfrpc', id: id, error: `Method '${message.method}' not found.`});
|
||||
send({
|
||||
message: 'tfrpc',
|
||||
id: id,
|
||||
error: `Method '${message.method}' not found.`,
|
||||
});
|
||||
}
|
||||
} else if (message.error !== undefined) {
|
||||
if (g_calls[id]) {
|
||||
@ -74,11 +94,11 @@ function call_rpc(message) {
|
||||
}
|
||||
|
||||
if (k_is_browser) {
|
||||
window.addEventListener('message', function(event) {
|
||||
window.addEventListener('message', function (event) {
|
||||
call_rpc(event.data);
|
||||
});
|
||||
} else {
|
||||
core.register('message', function(message) {
|
||||
core.register('message', function (message) {
|
||||
call_rpc(message?.message);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user